構造体(struct) 配列では、複数のデータをひとまとまりにして操作する ことが出来る。しかし、それぞれのデータは同じ型(例えば 整数、あるいは浮動小数点数)出なければならない。 型の違うデータをひとまとまりにして扱う方法に、構造体 がある。 構造体 配列 文字 文字 文字 文字 文字 名前 (文字列) 0 1 2 3 整数 成績(理科)(整数) 学籍番号(整数) 成績(数学)(整数) 浮動小数点数 成績(英語)(整数) 身長(小数点数)
構造体の宣言 構造体を宣言するには、struct宣言を使う。 構造体の要素(メンバ)について、それぞれ変数の型と 名前を付ける。 struct four{ int a; int b; double c; double d; }; struct four x; typedef struct four Four; Four y; 構造体としてfourを定義 変数の型としてFourを定義 毎回 struct four と書くのは 面倒だが、このようにすると Four という型が定義される。
typedef宣言 構造体を値としてもつ変数を宣言するには、 struct 構造体名 変数名; のように、structをつける必要がある。 struct 構造体名 までがintやdoubleのような変数の型名となる。毎回structを つけるのは面倒ですが、これを簡略する方法としてtypedef宣言がある。 typedef 型名 別名; の様に、ある型名に対して、別名をつけることが出来る。 struct complex{ double real; double imag; }; typedef struct complex Complex; Complexをintやdoubleの様に 変数の型として使用することが出来る Complex a, b; Complex x[10];
typedef 宣言で別名をつける型としては、構造体の名前でなくとも よい。例えば、 typedef int int32; int32 a, b, c; とすれば、整数型(int)に別名(int32)をつけることができる。 また、 typedef int vector[3]; と宣言すると、vectorはintの配列(サイズ3)になる。 vector a, b, c; /* a,b,cは配列として宣言される*/ a[1] = 10;
構造体の参照 構造体の要素を参照するには、構造体の変数名にドットつけて、 そのあと要素名をつける。 構造体としてfourを定義 struct four{ int a; int b; double c; double d; }; struct four x、y; x.a = 10; x.d = 3.1415926; printf(“a = %d\n”, x.a); y = x; 構造体としてfourを定義 構造体を値とする変数x、yを宣言 構造体の要素aとdに値を代入 構造体 x全体をyに代入する
配列との違い 配列は、要素として同じ型の変数しか取れない。 配列の要素の参照は、添字を付けて a[0], a[1],... 構造体では要素名を指定して、 x.a, x.c, ... 構造体では、全体を代入する事が出来る。 int a[5] = {1,2,3,3,5}; int b[5]; a = b; これはダメ! 構造体へ初期値を 代入するのは配列 と同じように{}で 囲む。要素の型と 対応するように指定 する。 struct foo { int a,b,c; } x, y={1,2,3}; x = y; これはOK!
構造体の例 struct complex{ double real; double imag; }; struct complex x = {1.0, 0.0}; struct complex y = {0.0, 1.0}; struct complex z; z.real = x.real * y.real - x.imag * y.imag; z.imag = x.real * y.imag + x.imag * y.real; struct complex xx[10]; 10 個の構造体からなる配列
メンバに構造体を持つ構造体 構造体のメンバとしては、int型やdouble型だけでなく、 配列や構造体、あるいはそれらのポインタであっても構わない。 例えば、(x,y)で点(point)を表し、3点で三角形を 表すことを考えると struct point { double x, y; }; struct triangle { struct point a, b, c; struct triangle A = {{0.0,0.0},{1.0, 0.0}, {0.0, 1.0}}; 3点a,b,cで三角形 を表す
自分自身をメンバに持つ構造体 構造体のメンバとして、既に宣言されている変数型 や構造体以外のものは許されない。特に、自分自身を メンバとして持つことはゆるされない。 しかし、ポインタ型としてのメンバであれば、許される。 struct cell { int data; struct cell next_cell; }; これは許されない struct cell { int data; struct cell *next_cell; }; これなら許される
関数への構造体の受け渡し 構造体を関数の引数として渡すには、値渡しと参照渡しの二つの方法がある。 複素数の絶対値を計算する関数 (値渡し) struct complex { double real, imag; }; typedef struct complex Complex; double cabs(Complex a) { double d; d = a.real * a.real + a.imag*a.imag; d = sqrt(d); return d; } Complex conjugate(Complex a) { Complex b; b.real = a.real; b.imag = -a.imag; return b;
参照渡し v->x は (*v).x と書くのと同じ。 構造体のポインタはよく使われる ので、要素を参照する為に特別な struct vector { double x,y,z; }; typedef struct vector Vector; main() { Vector v={0.0, 1.1, 3.2} double sum; sum = fn(&v); printf("%f\n", sum); } double fn(Vector *v) double sum=0; sum = v->x + v->y + v->z; return sum; ベクトル要素の合計を返却する関数 v->x は (*v).x と書くのと同じ。 構造体のポインタはよく使われる ので、要素を参照する為に特別な 記号を用意してある。
ファイルへの入出力 これまでは、データの入出力は標準入出力(キーボード及びコンソール)を用いてきた(シェルのリダイレクションを含む)が、入出力はファイル経由でも可能。 外部記憶装置(ハードディスクなど)に格納されたデータの集合をファイルという。ファイルを扱うために FILE という型が stdio.h に定義されている。 テキストファイル(text file)とバイナリファイル(binary file) 内容が文字と改行文字などそのまま表示可能なファイルをテキストファイル、データがバイト列となっているものをバイナリファイルという。ここではテキストファイルのみを取り扱う。 ファイルの入出力を行うには、事前、事後の操作が必要。 1) ファイルのオープン(ファイルを開く) 2) ファイルへの入出力 3) ファイルのクローズ(ファイルを閉じる)
ファイルのオープン ファイル型へのポインタ変数宣言 FILE *fp; ファイルのオープンには、ライブラリ関数 fopen() を用いる。 宣言しただけでは具体的なファイルを指していない。 ファイルのオープンには、ライブラリ関数 fopen() を用いる。 fp = fopen("data", "w"); ファイル名 モード "w" 書き込みモード(ファイルを新規作成) "r" 読み込みモード(ファイルが存在しないとエラー) 上記の例では、新規に data という名前のファイルが作成され、ファイルへのポインタ fp はこのファイルを指す。以後は fp を介してファイル入出力等を行う。ファイル data が既存の場合、内容は消去されて新規ファイル が作成される。
ファイル名の指定 文字列リテラルとしてファイル名を指定し、ファイルをオープンする例 FILE *fp; char file_name[]="data"; fp = fopen(file_name, "w"); 文字型配列として宣言と同時に初期化 FILE *fp; char file_name[100]; scanf("%s", file_name); fp = fopen(file_name, "w"); 文字型配列として宣言 scanf() で文字列として入力 文字列=文字型配列の操作により、ファイル名をプログラム内で変更可能。
ファイルオープン時のエラー処理 ファイルオープンに失敗すると fopen() は特殊な値 NULL を返す。下例のように、オープンに失敗した場合、ライブラリ関数 exit() でプログラムを正常に終了。 if( (fp = fopen("data", "w")) == NULL ){ printf("Can’t open file!\n"); exit(); } 書き込みモードでファイルを開く場合、ディスク容量が一杯、もしくは、読み込みモードで指定したファイルが存在しない等の理由でファイルオープンが失敗する場合がある。エラー処理を行わないと、ファイルへの入出力の時点で Bus error 等のプログラムの異常終了が起こる。 ライブラリ関数 exit() はプログラムの実行を終了する。
ファイルへの入出力 ライブラリ関数 fprintf(), fscanf() を用いた入出力 それぞれ、標準入出力に関する printf(), scanf() に相当する fprintf(fp, "socre is %d\n", i); ファイルポインタ fp が指すファイルへ書式付き出力 fscanf(fp, "%d", &data); ファイルポインタ fp が指すファイルからデータを整数値として読み込み、変数 data に格納。 使い方は printf(), scanf() と同じ。
ファイルのクローズ オープンしたファイルはプログラム終了時にクローズする必要がある ファイルのクローズ fclose(fp); 一般にプログラムが終了する直前でファイルを閉じる
具体例 1 main() { FILE *fp; fp = fopen("data", "w"); fprintf(fp, "Hello!\n"); fclose(fp); } data という名前のファイルを新規作成し、 文字列 “Hello!\n” を書き込む。 開いたファイルはちゃんと閉じること。 実行結果は左の通り。data というファイルが出来ていて、内容は Hello!\n である。 % ./a.out % cat data Hello! %
具体例 2 main() { FILE *fp; int score; fp = fopen("data", "r"); while( fscanf(fp, "%d", &score)!=EOF ){ printf("%d\n", score); } fclose(fp); 既存のファイル data を読み込みモード "r" で開き、ファイルに書かれているデータを整数値として読み込んで表示。 書き込みモード "w" でファイルを開くと、ファイルが新規作成されて中身が消えてしまうので注意! ファイルオープンの際、エラー処理をしていないので、data というファイルが存在しないと実行時エラーで異常終了する。
ファイル入出力続き ファイルから 1 文字ずつ文字を読み込むライブラリ関数 getc(), putc() 標準入出力に関する getchar(), putchar() に相当 int getc(FILE *fp); fp が指すファイルから 1 文字読み込み、文字コード(int)を返す int putc(int c, FILE *fp); fp が指すファイルへ文字コード c に対応する文字を書き込む。書き込み時にエラーが起こると EOF を返す
具体例 3 テキストファイル data_original の内容を別のファイル data_copy にコピーするプログラム main() { FILE *fp_in, *fp_out; int code; fp_in =fopen("data_original", "r"); fp_out=fopen("data_copy", "w"); while( (code=getc(fp_in)) != EOF )} putc(code, fp_out); } fclose(fp_in); fclose(fp_out); コピー元のファイルは読込みモードでオープンすること。 コピー元のファイルから 1 文字ずつ読み込んで、コピー先のファイルへ書きだす。
記憶クラス 変数をどのような記憶領域に割り当てるかを指定するのが記憶クラス 記憶クラスには、自動変数、静的変数、外部変数などがある。 今までの変数宣言はすべて、変数を自動変数( auto型)として宣言 auto int a; を省略して int a; と宣言 記憶クラス指定子 auto で自動変数を指定(省略可) 自動変数は、それが宣言されている関数が呼び出された時点で生成され、 関数の呼び出しが終了した時点で消滅する。 自動変数はメモリ上のスタックに格納される。
変数の生成と消滅 y y x x j j i i main() { int i,j; func_1(); ... func_2(); } void func_1(void) int x, y; .... void func_2(void) 局所変数 x, y の生成 局所変数 x, y の生成 消滅 消滅 y y x x メモリ空間 func_1 呼出 func_1 終了 func_2 呼出 func_2 終了 j j i i 時間 変数 i, j の寿命 変数 x, y の寿命 変数 x, y の寿命 main 文の開始 局所変数 i, j の生成 main 文の終了 局所変数 i, j の消滅
静的変数 static int x; 記憶クラス指定子 static を用いて、変数が静的変数であることを指定する 静的変数はメモリの静的領域に格納される。 静的変数は初期化の指定がない場合、ゼロで初期化される。 静的変数の初期化はコンパイル時に一度だけ行われる。 静的変数に対して、自動変数は関数の終了で消滅するため、プログラムの 実行中、値を保持することが出来ない。 プログラムの実行中常に値を保持するには静的変数を用いる。
静的変数の寿命 main() { int i; func_1(); func_2(); } void func_1(void) 自動変数 static int a; int b; .... void func_2(void) static int x; int y; main 文の局所変数 i main 文開始 生成 main 文終了 消滅 自動変数 func_1 の局所変数 b func_2 の局所変数 y func_1 呼び出し func_1 終了 func_2 呼び出し func_2 終了 func_1 の局所変数 a func_2 の局所変数 x 静的変数 main 文開始 生成 main 文終了 消滅
静的変数を用いた例 void count(void) % ./a.out main() 1 { 2 count(); % } static int x=0; printf(“%d\n”, x); x++; % ./a.out 1 2 % 関数 count 中の静的変数 x の値は、関数呼び出しが終わっても消滅しないで保持されている。 従って、呼び出されるごとに x の値は 1 増えるが変数 x は消滅しない。 静的変数の初期化はコンパイル時に一度だけ実行される % ./a.out % x を自動変数として宣言すると、関数呼びだし毎に x は生成され値は 0 に初期化される。実行結果は、
外部変数 関数の外で定義される変数を外部変数(大域変数)と呼ぶ。 外部変数のスコープは広域的(プログラム全体)。全ての関数で参照可能。 int a=10; double x=3.14159; main() { printf(“a = %d\n”, a); sample(); printf(“%f\n”, x); } void sample(void) x += 1.0; 外部変数 a, x の宣言。変数 a, x はどの関数(main 文を含む)からも利用可能。 外部変数はどの関数からも参照可能なの で、関数を独立したブラックボックスと して定義することが困難になる。 変数の隠ぺいができないため、外部変数の乱用は避けるべき。
問題 1 複素数を構造体として定義し、二つの複素数の積(結果は複素数)を返す 関数 cmult を定義せよ。 #include <stdio.h> struct complex { double real, imag; }; typedef struct complex Complex; Complex cmult(Complex, Complex); main() { Complex x = {1.0, 1.0}; Complex y = {1.414, 1.414}; Complex z; z = cmult(x,y); printf(“z = %f + %fi\n”, z.real, z.imag); }
問題 2 学生のデータを扱う為の構造体を定義せよ。この構造体の値を設定する ための入力関数read_dataと出力する関数write_dataを作れ。 データは各自適当に決めてよい。名前は、文字型の配列とし、サイズは20文字 にする。 ./a.out 名前は: kako-fujio 学籍番号は: 999999 年齢:20 身長:163.4 体重:72.5 登録データ kako-fujio 999999 20歳 163.4 cm 72.5kg #include <stdio.h> struct student { 必要な変数を定義; }; main() { struct student s; read_data(&s); printf(“登録データ\n”); write_data(s); }
問題 3 出力の関数 void print(struct time a) { printf(“%d時%d分%d秒”, a.hour, 問題 3 時(hour)、分(minute)、秒(second)からなる構造体(time)を定義し、 時刻の和と差をそれぞれ求める関数 struct time timeadd(struct time a, struct time b); struct time timesub(struct time a, struct time b); を作成せよ。 時は、0から23までで、24時は0、また−1時は23時とする。 12時23分10秒+5時40分15秒=18時3分25秒 12時23分10秒−5時40分15秒=6時42分55秒 出力の関数 void print(struct time a) { printf(“%d時%d分%d秒”, a.hour, a.minute, a.second); }
問題 4 ヘロンの公式 平面での座標ベクトル(x、y)を構造体として定義し、 2点間の距離を求める関数をプログラムせよ。 また、この関数を用いて与えられた3点を頂点とする 三角形の面積を求めるプログラムを作成せよ。 ヘロンの公式
問題 5 キーボードから文字を 1 文字ずつ読み込み、Ctrl-D で中断する。読み込んだ文字をファイルに書き出すプログラムを作れ。なお書きだすファイルの名前もキーボードから入力するとする。 % ./a.out ファイル名:text_file 文字を入力せよ(Ctrl-Dで終了) Summer vacation is close at hand! Ctrl-D % % cat text_file 書き出したファイルの内容を確認せよ。