プログラミング1 第 9 回 構 造 体 (2) 応 用 構 造 体 へのポインタ 構 造 体 のネスト 関 数 と 構 造 体 ポインタ 良 くあるミス この この 資 料 にあるサンプルプログラムは /home/course/rog1/ublic_html/2007/hw/lec/sources/ 下 に 置 いてありますから いてありますから 各 自 自 分 のディレクトリに のディレクトリに コピーして コンパイル 実 行 してみてください してみてください Prog-1 2007 Lec 09-1
構 造 体 へのポインタ(1) 構 造 体 も 変 数 ですから そのポインタは 以 下 のように 宣 言 できる 構 造 体 タグ 名 *ポインタ 変 数 名 ; ( 例 ) *; 構 造 体 のアドレス 参 照 は 以 下 の 形 式 で 行 なう & 構 造 体 変 数 名 構 造 体 ポインタへのアドレスの 格 納 は 従 来 のポインタ 処 理 と 同 じで ある 即 ち 以 下 のように 行 う 構 造 体 ポインタ 変 数 & 構 造 体 変 数 名 ; ( 例 ) &my_data; この 代 入 以 降 には my_dataのアドレスが 保 持 される Prog-1 2007 Lec 09-2
構 造 体 へのポインタ(2) 構 造 体 ポインタがある 時 に *でそのポインタが 指 し 示 す 構 造 体 の 内 容 を 得 ることが 出 来 る * を 間 接 演 算 子 と 呼 ぶ 構 造 体 メンバーをポインタでアクセスする 場 合 には (*).name (*).birth (*).address のように 書 く &my_data である 時 これは my_data.name my_data.birth my_data.address と 同 じ 意 味 になる 何 故 (*)のように 括 弧 が 必 要 かと かと 言 うと. 演 算 子 が * より 優 先 順 位 が 高 いためで *.nameとすると *(.name) の 事 になってしまうからである (ちなみにこれはコンパイルエラーとなる 何 故 ならは ならは 構 造 体 変 数 ではなく 構 造 体 型 のポインタであるから その 後 ろに. で 続 けてメンバー 名 が 来 るのはあり 得 ないからである) Prog-1 2007 Lec 09-3
構 造 体 へのポインタ(3) メンバー 参 照 の2つ 目 の 方 法 は アロー 演 算 子 -> を 用 いて 以 下 の 形 式 で 行 なう ( - と > 続 けて 書 く) 構 造 体 ポインタ 変 数 名 ->メンバ 名 &mydata である 時 以 下 の3つは 同 一 の 物 である ->name (*).name my_data.name my_data Prog-1 2007 Lec 09-4
構 造 体 へのポインタ(4) 構 造 体 メンバの 出 力 を 行 うサンプルプログラムを 示 す 3 種 類 の 方 法 での 出 力 は 同 じ 結 果 となる name[30]; name[30]; birth; birth; address[80]; address[80]; gender; gender; ; ; *,my_data *,my_data 初 期 化 データ; データ; &my_data; &my_data; ポインタ ポインタ 間 接 演 算 子 ピリオド ピリオド (*).name); (*).name); (*).birth); (*).birth); (*).address); (*).address); (*).gender); (*).gender); ポインタアロー ポインタアロー 演 算 子 ->name); ->name); ->birth); ->birth); ->address); ->address); ->gender); ->gender); 構 造 体 変 数 そのまま そのまま my_data.name); my_data.name); my_data.birth); my_data.birth); my_data.address); my_data.address); my_data.gender); my_data.gender); Prog-1 2007 Lec 09-5
自 己 参 照 的 構 造 体 構 造 体 内 に 自 分 の 型 のポイ ンタを 置 く 場 合 がある これを 自 己 参 照 的 構 造 体 と 呼 ぶ 以 下 のような 場 合 a.next は b を 指 す b.next は c を 指 す a.next->next は c を 指 す このようにデータが 順 に 繋 がっているデータ 構 造 を 連 結 リストと 呼 び 来 週 の 授 業 で 更 に 詳 しく 説 明 する name[30]; name[30]; birth; birth; address[80]; address[80]; gender; gender; *next; *next; ; ; a,b,c; a,b,c; a.next a.next &b; &b; b.next b.next &c; &c; a next b next c Prog-1 2007 Lec 09-6
構 造 体 のポインタ 演 算 構 造 体 の 場 合 も 通 常 の 配 列 同 様 に ポインタに 対 して 加 算 減 算 インクリメント デクリメント 演 算 を 行 なうことが 出 来 る インクリメント 処 理 によって 増 えるアドレスの 量 は 構 造 体 配 列 の 要 素 1 個 分 の 大 きさである (つまりsizeof(meibo[0]) 型 の 場 合 120バイト) 構 造 体 配 列 meibo -- または -1 meibo[n-1] または 1 meibo[n] meibo[n1] Prog-1 2007 Lec 09-7
構 造 体 の 大 きさ なお 構 造 体 の 大 きさは 必 ずしもメンバーの 大 きさの 合 計 にはな らない 例 えば 型 の 場 合 単 純 な 合 計 は (3080)*sizeof() 2*sizeof() (30 80) 2*4 118 コンピュータ(std1ss1) 上 でsizeof(meibo[0])によって 表 示 させると 120と 表 示 された 以 下 のプログラムで 各 メンバの 先 頭 アドレスの 差 を 表 示 させたところ 文 字 配 列 nameの 後 に2バイトの 穴 があることが 分 かる (このプログラムは 参 考 に 載 せたものなので 意 味 が 理 解 できなくても 差 し 支 えない) name birth address gender 4 4 30 2 80 使 用 されない 領 域 Prog-1 2007 Lec 09-8
構 造 体 のポインタ 演 算 ポインタ 演 算 を 行 うサンプルプログラムを 示 す name[30]; name[30]; birth; s1000001std0ss01 birth; s1000001std0ss01./.a.out./.a.out address[80]; meibo[0] address[80]; meibo[0] effff9f8 effff9f8 effff9f8 effff9f8 gender; meibo[1] gender; meibo[1] effffa70 effffa70 effffa70 effffa70 ; sizeof(meibo) ; sizeof(meibo) 240 240,, sizeof(meibo[1]) sizeof(meibo[1]) 120 120 s1000001std0ss02 s1000001std0ss02 *, *, meibo[2] meibo[2] " " 要 素 0 初 期 化 データ", データ", " " 要 素 1 初 期 化 データ", データ", ; ; &meibo[0]; &meibo[0]; meibo; meibo; でも でも 良 い rf("meibo[0] rf("meibo[0] % % %\n",, %\n",, &meibo[0]); &meibo[0]); ; ; rf("meibo[1] rf("meibo[1] % % %\n",, %\n",, &meibo[1]); &meibo[1]); rf("sizeof(meibo) rf("sizeof(meibo) %d %d,, sizeof(meibo[1]) sizeof(meibo[1]) %d\n", %d\n", sizeof(meibo), sizeof(meibo), sizeof(meibo[1])); sizeof(meibo[1])); Prog-1 2007 Lec 09-9
構 造 体 のポインタ 演 算 構 造 体 配 列 メンバの 出 力 を 行 うサンプルプログラムを 示 す 4 種 類 の 方 法 での 出 力 は 同 じ 結 果 となる name[30]; name[30]; birth; birth; address[80]; address[80]; gender; gender; ; ; i; i; *, *, *q, *q, meibo[2] meibo[2] " " 要 素 0 初 期 化 データ", データ", " " 要 素 1 初 期 化 データ", データ", ; ; meibo; meibo; for(i for(i 0; 0; i i 2; 2; i) i) (*( (*( i)).name); i)).name); (*( (*( i)).birth); i)).birth); (*( (*( i)).address); i)).address); (*( (*( i)).gender); i)).gender); Prog-1 2007 Lec 09-10 for(i for(i 0; 0; i i 2; 2; i) i) ( ( i)->name); i)->name); ( ( i)->birth); i)->birth); ( ( i)->address); i)->address); ( ( i)->gender); i)->gender); for(q for(q ; ; q q 2; 2; q) q) (*q).name); (*q).name); (*q).birth); (*q).birth); (*q).address); (*q).address); (*q).gender); (*q).gender); for(q for(q ; ; q q 2; 2; q) q) q->name); q->name); q->birth); q->birth); q->address); q->address); q->gender); q->gender);
関 数 への 構 造 体 のアドレス 渡 し x; x; x x 座 標 y; y; y y 座 標 ; ; swa( swa( *, *, *); *); data1 data1 1.0,2.0, 1.0,2.0, data2 data2 3.0,4.5; 3.0,4.5; swa(&data1,&data2); swa(&data1,&data2); rf("data1(%3.1f,%3.1f) rf("data1(%3.1f,%3.1f) data2(%3.1f,%3.1f)\n", data2(%3.1f,%3.1f)\n", data1.x,data1.y,data2.x,data2.y); data1.x,data1.y,data2.x,data2.y); swa( swa( *a, *a, *b) *b) tm; tm; tm tm *a; *a; *a *a *b; *b; *b *b tm; tm; rf("a(%3.1f,%3.1f) rf("a(%3.1f,%3.1f) b(%3.1f,%3.1f)\n",a->x,a->y,b->x,b->y); b(%3.1f,%3.1f)\n",a->x,a->y,b->x,b->y); Prog-1 2007 Lec 09-11
構 造 体 の 入 れ 子 構 造 構 造 体 宣 言 の 中 に 構 造 体 の 定 義 があるような 構 造 体 の 構 造 を 入 れ 子 と 呼 んでいる これは 既 にある 構 造 体 を 含 んで 更 に 別 のデータのまとまりを 作 り 上 げるときに 有 効 である 例 えば 次 頁 の 例 は 平 面 の 点 構 造 体 を2つ 使 用 してx 軸 y 軸 に 平 行 な 辺 を 持 つ 長 方 形 の 構 造 体 を 宣 言 している この 例 の 場 合 はただの 構 造 体 配 列 でも 実 現 可 能 だが 構 造 体 の 入 れ 子 の 方 が 応 用 範 囲 が 広 い 入 れ 子 の 構 造 体 のメンバーへのアクセスは 以 下 のように 書 く 外 側 の 構 造 体 名. 内 側 の 構 造 体.メンバ 名 Prog-1 2007 Lec 09-12
構 造 体 の 入 れ 子 構 造 先 週 (lec8-19) 平 面 上 の2 点 を 対 角 点 とする 長 方 形 を 構 造 体 配 列 を 使 っ て 考 えたが ここでは 平 面 上 の 点 構 造 体 二 点 をメンバーとして 持 つ 構 造 体 として 考 える math.h> math.h> x; x; x x 座 標 y; y; y y 座 標 ; ; rect rect 1; 1; 2; 2; ; ; s1000001std0ss01 s1000001std0ss01./a.out./a.out The The area area of of the the rectangle rectangle is is 12.000000 12.000000 s1000001std0ss01 s1000001std0ss01 rect rect rect1 rect1 1.0,5.0,4.0,1.0; 1.0,5.0,4.0,1.0; area; area; 構 造 体 メンバーは 構 造 体 名 1. 構 造 体 名 2.メンバー のようにアクセスする Prog-1 2007 Lec 09-13 area area ()fabs((rect1.1.x ()fabs((rect1.1.x - - rect1.2.x) rect1.2.x) * * (rect1.1.y (rect1.1.y - - rect1.2.y)); rect1.2.y)); rf("the rf("the area area of of the the rectangle rectangle is is %f\n",area); %f\n",area);
構 造 体 の 特 徴 構 造 体 を 使 う 利 点 は データの 取 り 扱 いが 明 確 になり 可 読 性 が 向 上 する 扱 う 変 数 の 個 数 が 少 なくなり プ ログラムの 簡 略 化 を 図 ることが 出 来 る データをまとめて 扱 うことができる これをデータのカプセル 化 と 呼 ぶ x 座 標 y 座 標 を 持 つ 平 面 上 の 点 を 点 として 一 括 して 取 り 扱 える 継 承 するデータは 階 層 的 に 構 造 体 を 定 義 すると 非 常 に 有 効 的 となる 点 の 集 まりとして 四 角 形 など の 図 形 を 考 えることが 出 来 る 構 造 体 を 使 う 注 意 点 メンバーをアクセスするときに 名 前 はながくなりやすい 代 入 などの 操 作 を 平 気 に 使 って しまい 余 計 に 計 算 時 間 がかか る 場 合 もある ドット アロー などを 使 うときに 間 違 いやすい Prog-1 2007 Lec 09-14
良 くあるプログラミングミス(1) 構 造 体 を 使 用 した 場 合 の 良 くあるプログラムの 間 違 いを 挙 げてみた なお この 節 の 例 は 全 て 構 造 体 タグを 使 用 するので 構 造 体 タグの 定 義 は 省 略 した またも 省 略 してある 1. ポインタと * を 使 って 構 造 体 のメンバーをアクセスする 時 には 必 ずカッコが 必 要 構 造 体 ポインタがある 時 (*).x *.x 2. 構 造 体 と 構 造 体 ポインタをはっきり 区 別 する 構 造 体 変 数 oと 構 造 体 ポイ ンタがある 時 (*).x *.x o.x (*o).x ->x o->x Prog-1 2007 Lec 09-15
良 くあるプログラミングミス(2) 3. アロー 演 算 子 (->)の 間 に 空 白 を 入 れない(- >のように) *, data1 1.0,2.0; &data1; rf("x %f, y %f\n", - >x, ->y); コンパイラのエラーメッセージ arse error before `>' 4. 大 きな 構 造 体 を 引 数 にする 場 合 は アドレス 渡 しの 方 が 速 い 場 合 がある 値 渡 しの 場 合 は 大 きな 構 造 体 を 関 数 間 でコピーするのに 時 間 がかかるため ただし 速 い からといって どんな 時 でもアドレス 渡 しをするのはいけない 見 易 さ 理 解 し 易 さも 考 えて どちらを 使 用 すべきかを 考 える 必 要 がある 次 頁 に 挙 げたプログラムは 値 渡 しとアドレス 渡 しの 速 さを 比 較 するために 掲 載 した 極 端 な 例 である(Cの 実 験 室 上 級 ラボ 編 林 著 より) 自 分 でも 試 して 体 感 してみる と 良 いだろう Prog-1 2007 Lec 09-16
良 くあるプログラミングミス(3) time.h> time.h> #define #define LOOP LOOP 200000 200000 時 間 に 関 する 関 数 群 の 定 義 詳 しくはman clock などを 参 照 のこと 5000 5000 文 字 の 文 字 配 列 がメンバーの がメンバーの 構 造 体 a[5000]; a[5000]; ; ; fv( fv( ); ); fr( fr( *); *); i; i; data; data; time_t time_t start,end; start,end; 時 間 計 測 用 の 変 数 double double keika; keika; start start clock(); clock(); for(i for(i 0; 0; i i LOOP; LOOP; i) i) fv(data); fv(data); 関 数 呼 び 出 し し end end clock(); clock(); keika keika (end-start)/(double)clocks_per_sec; (end-start)/(double)clocks_per_sec; rf(" rf(" value value %f %f \n",keika); \n",keika); 経 過 時 間 の 表 示 start start clock(); clock(); for(i for(i 0; 0; i i LOOP; LOOP; i) i) fr(&data); fr(&data); 関 数 呼 び 出 し し end end clock(); clock(); keika keika (end-start)/(double)clocks_per_sec; (end-start)/(double)clocks_per_sec; rf(" rf(" address address %f %f \n",keika); \n",keika); 経 過 時 間 の 表 示 Prog-1 2007 Lec 09-17 値 渡 しの しの 関 数 fv( fv( t1) t1) t1.a[0] t1.a[0] 'A'; 'A'; アドレス アドレス 渡 しの しの 関 数 fr( fr( *t2) *t2) t2->a[0] t2->a[0] 'A'; 'A';
実 行 結 果 std1ss40(blade100 std1ss40(blade100 model500 model500 500MHz 500MHz Solaris Solaris 8) 8) value value 5.820000 5.820000 address address 0.010000 0.010000 std3ss20(blade std3ss20(blade 150 150 model550 model550 550MHz 550MHz Solaris Solaris 8) 8) value value 4.720000 4.720000 address address 0.010000 0.010000 std5ss1(blade std5ss1(blade 150 150 model650 model650 650MHz 650MHz Solaris Solaris 8) 8) value value 4.400000 4.400000 address address 0.010000 0.010000 Prog-1 2007 Lec 09-18