8 構造体と供用体 ( 教科書 P.71) 構造体は様々なデータ型,int 型,float 型や char 型などが混在したデータを一つのまとまり, 単位として扱える.( 配列は一つのデータ型しか扱えない.) 構造体は柔軟なデータ構造を扱えるので, プログラムを効率よく開発できる. つまり構造体を使用すると, コード量を抑え, バグを少なくし, 開発時間を短くし, 簡潔なプログラムが作れる. 共用体は, 構造体と同様の機能をもつが, 変数の記憶領域の制限がある. 8.1 構造体 ( 構造体と配列の違い ) 個人データとして学生番号, 身長, 体重, 名前の 4 種類のデータを扱うことを考える. それらを配列で扱う場合,4 種類の配列を管理することになる ( 図 1, 左 ). 一方, それらを構造体で扱う場合, 1 種類の構造体,STUDENT のみを管理することになる ( 図 1, 右 ). 後者はデータを一つの単位,STUDENT で扱えるので, プログラムを簡潔に表現できる. int id; double height; double weight; char name[12]; struct STUDENT{ int id; double height; double weight; char name[12]; 図 1 配列はデータ構造を複雑にする ( 左 ). 構造体はデータ構造を簡単にする ( 右 ). STUDENT はタグ名と呼ばれ, 一種のデータ型である.{ の中の変数はメンバ変数と 呼ばれる. 構造体は,(1) 型の定義をし,(2) 変数の宣言をし,(3) 初期値を設定して使用する. 8.1.1 構造体の定義 (1) 構造体の型定義 文法は次のとおり. struct 構造体タグ名 { データ型変数 1; データ型変数 2;... /* 最後にセミコロン ; を忘れない */
(2) 構造体変数の宣言 文法は次のとおり. struct 構造体タグ名構造体変数名 ; (1) と (2) は同時に行える. struct 構造体タグ名 { データ型変数 1; データ型変数 2;... 構造体変数名 ; 例 : struct STUDENT{ stdata; int id; double height; double weight; char name[12]; 8.1.2 メモリ配置と初期化 データを構造体変数にセットする方法は二つある. 文法は次のとおり. 構造体変数の初期化 ( その 1) struct 構造体タグ名 構造体変数名 = { 値 1, 値 2, 例 : struct STUDENT stdata = { 2018, 175.5, 57.0, John 構造体変数の初期化 ( その 2) struct 構造体タグ名 { メンバ名 1;... メンバ名 n; 構造体変数名 ={ 初期値 1, 初期値 2,... 例 : struct STUDENT{ int id; /*id=2018 となる */ double height; /*height=175.5 となる */ double weight; /*weight=57.0 となる */ char name[12]; /*name[]=jhon となる */ stdata = { 2018, 175.5, 57.0, "Jhon"
データはメモリ上に次のように保存される. id 2018 4 バイト,int 型 低番地 height 175.5 8 バイト,double 型 weight 57.0 8 バイト,double 型 name[12] Jhon 12 バイト,char 型配列 高番地 図 2 メモリ上に展開されるデータ. 8.1.3 各メンバへのアクセス ( メンバ変数のアクセスの仕方 ) データを構造体のメンバ変数に代入するときは次の表現を使う. 構造体変数名. メンバ変数名 = データ 例 : stdata.height = 175.5; /* 175.5 を stdata のメンバ変数 height へ代入 */ また構造体のメンバ変数からデータを取り出すときは次の表現を使う. 変数 = 構造体変数名. メンバ変数名 例 : h = stdata.height; /* stdata のメンバ変数 height の値を h へ代入 */
ここで例題を示す. このプログラムはまず構造体,STUDENT の型を定義する. 次にそ れぞれの構造体変数,stdata0,stdata1,stdata2 にデータをセットする. 次にそれらのデー タを画面に表示する. プログラム 8-1 #include<stdio.h> void main(void) { struct STUDENT{ /* 構造体 STUDENT の型を定義する */ int id; /* 学生番号 */ double height; /* 身長 */ double weight; /* 体重 */ char name[12]; /* 名前 */ struct STUDENT stdata0 = {2018, 175.5, 57.0, "Jhon", /* 構造体変数を定義し,*/ stdata1 = {2032, 155.5, 47.5, "Julia", /* 同時にメンバ変数 */ stdata2 = {2037, 160.0, 70.0, "Mike" /* へ値を代入する */ stdata0.id, stdata0.height, stdata0.weight, stdata0.name); stdata1.id, stdata1.height, stdata1.weight, stdata1.name); stdata2.id, stdata2.height, stdata2.weight, stdata2.name); コンパイル, リンクと実行の手順は次のとおり. >gcc o p8_1 p8_1.c[enter] >./ p8_1 [Enter] 8.1.4 構造体の配列 構造体を配列として扱うことができる. 文法は次のとおり. struct 構造体タグ名配列名 [ 要素の個数 ]; 例 :STUDENT 型の構造体配列変数 stlist[] の宣言. struct STUDENT stlist[3];
8.1.5 構造体の入れ子 ( 構造体の中の構造体 )( 略 ) 構造体の中に構造体を含めることができる. 下のリストでは, 構造体,STUDENTの中で構造体変数 birthdayを宣言している. もし扱うデータの項目が増えたとしても, この様にいつでもデータ構造を柔軟に変更できる. struct DATE{ /* 生年月日を扱うための構造体の型の定義 */ int year; /* 年 */ int month; /* 月 */ int day; /* 日 */ struct STUDENT{ /* 学生の情報を扱うための構造体の型の定義 */ int id; /* 学生番号 */ struct DATE birthday; /* 構造体 DATEの構造体変数の宣言 */ 8.1.6 略 8.1.7 構造体とポインタ 構造体もポインタによるデータの操作ができる. 文法は次のとおり. struct 構造体タグ名 * 構造体ポインタ変数 ; 構造体ポインタ変数 = & 構造体変数 ; 例 : struct STUDENT * stptr; /* 構造体ポインタ変数 stprt を宣言する */ stptr = &stdata; /* stprt に構造体変数のアドレスを与える */ 構造体のメンバ変数にアクセスするときには次の表現を使う. 例 : stptr->id = 2018; /* 2018 を構造体 stptr のメンバ変数 id にセットする */ 例 : myid = stptr->id; /* 構造体 stptr のメンバ変数 id の値を変数 myid にセットする */ 注意 : 構造体の各メンバ変数にアクセスするとき, ピリオド,. を使用した. しか しポインタを介して各メンバ変数にアクセスするときは記号,-> を使用す る.(-> はハイフン >)
また構造体を引数として関数に渡すことができる. 次のプログラムはまず構造体, STUDENT の型を定義する. 次にメンバ変数 (id,height,weight, name) にデータをセットする. 関数,print_data で現在のメンバ変数のデータを画面に表示する. 次に関数,up_data で,height と weight をキーボードから入力したデータで書き換える. 最後に最新のデータを画面に表示する. プログラム 8-4 #include<stdio.h> struct STUDENT{ /* 構造体 STUDENT の宣言 */ int id; /* 学生番号 */ double height; /* 身長 */ double weight; /* 体重 */ char name[12]; /* 名前 */ void print_data(struct STUDENT data); /* 関数のプロトタイプ宣言 */ void up_data(struct STUDENT *ptr); /* 関数のプロトタイプ宣言 */ void main(void) { struct STUDENT stdata = {2018, 175.5, 57.0, "John"/* 構造体の宣言と初期値のセット */ print_data(stdata); /* 構造体 stdata を引数として, 関数を呼び出す */ up_data(&stdata); /* 構造体 stdata のアドレスを引数として, 関数を呼び出す */ print_data(stdata); /* 構造体 stdata を引数として, 関数を呼び出す */ void print_data(struct STUDENT data) /* 構造体 stdata を構造体 data で受けとる */ { /* 構造体のメンバ変数へのアクセスはドット (.) を使う */ data.id, data.height, data.weight, data.name); void up_data(struct STUDENT *ptr) /* 構造体 stdata のアドレスをポインタ変数 ptr で受けとる */ { double h, w; printf("height, Weight? n"); /* 画面に入力を促すメッセージを表示する */ scanf("%lf, %lf", &h, &w); /* キーボードからデータを入力する */ ptr->height = h; /* メンバ変数へのアクセスは-> を使用する */ ptr->weight = w; /* 入力例 : 170.3, 63.0[Enter] */
8.2 供用体 供用体の文法は構造体のそれと同様である. ただし struct の代わりに union を使用す る. struct UNI_DATA{ union UNI_DATA{ char c_data; char c_data; int d_data; int d_data; double f_data; double f_data; udata; udata; 構造体はメンバ変数の領域が独立してメモリに確保されるが, 供用体はメンバ変数の領 じゅうふく域が重複してメモリに確保される. 構造体と供用体について, メンバ変数のメモリ上での 配置の違いを次に示す. 低番地 構造体 共用体 char c_data int d_data char c_data int d_data 矢印の出発点は 3 つとも同じ double f_data double f_data 高番地 供用体のそれぞれのメンバ変数はメモリを供用している. つまり供用体では 1 つのメン バ変数の値のみ保持される. 供用体はメモリ容量が制限される環境において, 構造体に近 いデータ構造を使用したいときに使われる ( 例えばマイクロコントローラの開発環境 ).