「計算数学b」では、 「構造体」は、時間の余った人のために提供しただけだった。 学んでいない者は 220112.html で学べ。最後の発展練習はしなくてもよい。
そこでは説明し忘れたが、構造体の成分のことを「メンバー」と呼ぶ。
構造体は、関数に引数として渡せる。 また、関数から返り値として受け取ることもできる。
次は、やたらに大きい構造体を関数の引数として渡し、 また返り値として受け取るプログラムである。 そしてそれを一億回繰り返す。
実行すると、数秒かかる。 それは、引数として渡すときに時間がかかり、 また、返り値として受け取るときに時間がかかるからである。
#include <stdio.h> struct Misc { int a; int b[256]; /* やたらに大きい構造体 */ }; struct Misc f(struct Misc x); int main() { int i; struct Misc x; x.a = 0; for (i = 0; i < 10000*10000; i++) { x = f(x); } printf("%d\n", x.a); } /* メンバー a を 1 だけ増すだけの関数 */ struct Misc f(struct Misc x) { x.a++; return x; }
次は、同じことを、構造体のアドレスを関数に渡すように書いてみたもの。 こちらは一瞬で終わる。 渡すものが 4 バイトだからである。
ここで p->a++; は新しい。 「->」は二文字で一つの演算子である。 p->a は、 その前に置かれたポインタ p の指す構造体のメンバー a を意味する。 そして ++ が続いているからそれを 1 だけ増す、という意味になる。
p->a は (*p).a と同じである。 この小かっこは省略できない。 よって、p->a という書き方がよく用いられる。
#include <stdio.h> struct Misc { int a; int b[256]; /* やたらに大きい構造体 */ }; void f(struct Misc *p); int main() { int i; struct Misc x; x.a = 0; for (i = 0; i < 10000*10000; i++) { f(&x); } printf("%d\n", x.a); } /* メンバー a を 1 だけ増すだけの関数 */ void f(struct Misc *p) { p->a++; }
次のプログラムで、構造体 Aaa のメンバーは char と int が一つずつなので、サイズは 1 + 4 = 5 バイトかと思うかもしれないが、 ここの gcc では 8 バイトである。 それは、int 型は「mod 4 で 0 である番地からの 4 バイト」 として置くことで実行時の効率が上がるから、であろうと思われる。
#include <stdio.h> struct Aaa { char c; int n; }; int main() { struct Aaa a; printf("%p.\n", &a); printf("%d バイト.\n", sizeof(struct Aaa)); }
構造体 struct Aaa 型が struct Aaa 型のメンバーを持つのは、 集合論で「x ∈ x」が成り立つ集合 x を考えるようなもので、不合理である。
#include <stdio.h> struct Aaa { /* 間違い */ int n; struct Aaa a; };
しかし、 構造体 struct Aaa 型が struct Aaa 型へのポインタをメンバーとして持つのは合法である。
#include <stdio.h> struct Aaa { /* 正しい */ int n; struct Aaa *a; };
次のような構造体 struct Lnode を定義する。 (Lnode は line と node に由来すると思う。 prev は英単語 previous に由来する。)
struct Lnode { char *text; /* テキスト */ struct Lnode *prev; /* 前の行の struct Lnode のアドレス */ struct Lnode *next; /* 次の行の struct Lnode のアドレス */ };
入力行を一行ごとに分け、その数だけこの構造体を用意し、 行はメンバー text に置く。 そして、 prev は前の行の Lnode を、 next は次の行の Lnode を、指す。
すると一列につながった Lnode の列ができる。 最初の行の prev と最後の行の next は特別な Lnode を指すことにし、その Lnode をもってこの一連の文字列の代表とする。 そこから順に next をたどれば、文字列全体にアクセスできる。
#include <stdio.h> #include <stdlib.h> #include <string.h> struct Lnode { char *text; /* テキスト */ struct Lnode *prev; /* 前の行の struct Lnode のアドレス */ struct Lnode *next; /* 次の行の struct Lnode のアドレス */ }; #define MAXLINE 1024 /* 一行の長さの限度 */ int main() { int len, n; char line[MAXLINE]; /* とりあえず一行を入れるところ */ struct Lnode textlines; /* 最初にして最後となる、特別な Lnode */ struct Lnode *pivot, *lp, *lpnew; pivot = &textlines; lpnew = pivot; /* lpnew をこうおくのは入力が空の場合のため */ for (lp = pivot; gets(line) != NULL; lp = lpnew) { /* gets() は【お勧めしません!】*/ len = strlen(line); lpnew = malloc(sizeof(struct Lnode)); /* 厳密にはメモリ不足のときの処理が必要 */ lpnew->text = malloc(len + 1); /* 厳密にはメモリ不足のときの処理が必要 */ strcpy(lpnew->text, line); lp->next = lpnew; lpnew->prev = lp; } lpnew->next = pivot; /* 最後と最初とをつなぐ */ pivot->prev = lpnew; n = 1; for (lp = pivot->next; lp != pivot; lp = lp->next) { /* 行番号をつけて出力してみる */ printf("%6d: %s\n", n, lp->text); n++; } }
前の節のプログラムを改変し、 最後、ユーザーが自然数 n を入力すると第 n 行だけを出力するようにせよ。
レポートの本文冒頭に、氏名を、なるべく大学に届けるある通りの文字で書け。 次に、プログラムソースファイルを、添付ファイルではなく、本文に貼りつけよ。 それから、このプログラムを使った実験結果を貼れ。
件名は「??? kadai3」(←全て半角文字、小文字、 ??? は自分の履修者番号、その後ろに半角スペース一つ、 kadai と 3 の間にはスペースを入れない)とせよ。