8 構造体と供用体(教科書 P.71)
構造体は様々なデータ型,int 型,float 型や char 型などが混在したデータを一つのまと まり,単位として扱える.(配列は一つのデータ型しか扱えない.)構造体は柔軟なデー タ構造を扱えるので,プログラムを効率よく開発できる.つまり構造体を使用すると,コ ード量を抑え,バグを少なくし,開発時間を短くし,簡潔なプログラムが作れる. 共用体は,構造体と同様の機能をもつが,変数の記憶領域の制限がある. 8.1 構造体(構造体と配列の違い) 個人データとして学生番号,身長,体重,名前の 4 種類のデータを扱うことを考える. それらを配列で扱う場合,4 種類の配列を管理することになる(図 1,左).一方,それ らを構造体で扱う場合, 1 種類の構造体,STUDENT のみを管理することになる(図 1, 右).後者はデータを一つの単位,STUDENT で扱えるので,プログラムを簡潔に表現で きる. 図 1 配列はデータ構造を複雑にする(左).構造体はデータ構造を簡単にする(右). STUDENT はタグ名と呼ばれ,一種のデータ型である.{}の中の変数はメンバ変数と 呼ばれる. 構造体は,(1)型の定義をし,(2)変数の宣言をし,(3)初期値を設定して使用する. 8.1.1 構造体の定義 (1)構造体の型定義 文法は次のとおり. struct 構造体タグ名 { データ型 変数 1; データ型 変数 2; ... }; /*最後にセミコロン;を忘れない*/ struct STUDENT{ int id; double height; double weight; char name[12]; }; int id; double height; double weight; char name[12];
(2)構造体変数の宣言 文法は次のとおり. struct 構造体タグ名 構造体変数名; (1)と(2)は同時に行える. struct 構造体タグ名 { データ型 変数 1; データ型 変数 2; ... } 構造体変数名; 例: struct STUDENT{ int id; double height; double weight; char name[12]; } stdata; 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 height weight name[12] 低番地 高番地 4バイト,int型 8バイト,double型 8バイト,double型 12バイト,char型配列 2018 175.5 57.0 Jhon 図 2 メモリ上に展開されるデータ. 8.1.3 各メンバへのアクセス(メンバ変数のアクセスの仕方) データを構造体のメンバ変数に代入するときは次の表現を使う. 構造体変数名 . メンバ変数名 = データ
例: stdata.height = 175.5; /* 175.5 を stdata のメンバ変数 height へ代入*/
また構造体のメンバ変数からデータを取り出すときは次の表現を使う.
変数 = 構造体変数名 . メンバ変数名
ここで例題を示す.このプログラムはまず構造体,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"}; /* へ値を代入する */ printf("ID = %4d, Height = %6.1f, Weight = %5.1f, Name = %s¥n",
stdata0.id, stdata0.height, stdata0.weight, stdata0.name); printf("ID = %4d, Height = %6.1f, Weight = %5.1f, Name = %s¥n",
stdata1.id, stdata1.height, stdata1.weight, stdata1.name); printf("ID = %4d, Height = %6.1f, Weight = %5.1f, Name = %s¥n",
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[]の宣言.
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 で受けとる */ { /* 構造体のメンバ変数へのアクセスはドット(.)を使う */
printf("ID = %4d, Height = %6.1f, Weight = %5.1f, Name = %s¥n", 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 double f_data 高番地 char c_data int d_data double f_data 供用体のそれぞれのメンバ変数はメモリを供用している.つまり供用体では 1 つのメン バ変数の値のみ保持される.供用体はメモリ容量が制限される環境において,構造体に近 いデータ構造を使用したいときに使われる(例えばマイクロコントローラの開発環境). 矢印の出発点は 3 つとも同じ