プログラミング論 構造体 http://www.ns.kogakuin.ac.jp/~ct13140/Prog/
概要 構造体 複数の変数を組合わせて,ひとまとめにしたもの 簡単 重要 自己参照型, リスト 重要, 難しい
新しい構造体の宣言 例 struct 構造体名{ データ型 メンバ名1; データ型 メンバ名2; : データ型 メンバ名3; }; struct seiseki{ char name[20]; int math; int engl; }; これで「struct seiseki型」 という新しい型が作成(宣言)された. mathやenglは構造体のメンバ.
構造体型の変数の宣言 struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane, yasu; : } yasuという1個の変数の中に, char name[20], int math, int englの 3個が含まれている. int x, y; なら,intが型, xやyが変数名. 「struct seiseki」が型. sane や yasu が変数名.
メンバ変数へのアクセス (1) ドット(dot)演算子 . (2) アロー(arrow)演算子 -> 構造体変数.メンバ変数 構造体変数aの中の,メンバ変数xにアクセス a.x 構造体へのポインタpが指す構造体の中の,メンバ変数yにアクセス (*p).y (2) アロー(arrow)演算子 -> 構造体変数へのポインタ->メンバ変数 構造体へのポインタpが指す構造体の中の,メンバ変数zにアクセス p->z
メンバ変数へのアクセス (1) ドット演算子 (非ポインタ) struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane, yasu; sane.math = 80; sane.engl = 70; strcpy( sane.name, "saneyasu"); sane.name[0] = 'S'; printf("name=%s, math=%d\n", sane.name, sane.math); } (1) ドット演算子 . 構造体変数 . メンバ変数 実行結果 name=Saneyasu, math=80
メンバ変数へのアクセス (1) ドット演算子 (ポインタ) struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane, yasu; struct seiseki *p; p = &yasu; // アドレス1028~1031に1000が格納される yasu.math = 50; // アドレス1020~1023に50が格納される (*p).engl = 60; printf("math=%d, engl=%d\n", (*p).math, yasu.engl); } (1) ドット演算子 . 構造体変数 . メンバ変数 1000 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 char name[20] int math int engl struct seiseki *p yasu 実行結果 math=50, engl=60
メンバ変数へのアクセス (1) ドット演算子 (ポインタ) struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane, yasu; printf("%p %p\n", &sane, &(sane.name[0])); printf("%p %p\n", &(sane.math), &(sane.engl)); } (1) ドット演算子 . 構造体変数 . メンバ変数 ca18 ca2b ca2c ca2d ca2e caef ca30 ca31 ca32 ca33 char name[20] int math int engl sane 実行結果 0xbfb2ca18 0xbfb2ca18 0xbfb2ca2c 0xbfb2ca30
メンバ変数へのアクセス (1) ドット演算子 (ポインタ) struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane, yasu; struct seiseki *p; p = &yasu; (*p).math = 70; // OK *p.engl = 80; // NG! *(p.engl)=80 の意味になってしまう! } (1) ドット演算子 . 構造体変数 . メンバ変数 演算子の 優先順位に注意!
メンバ変数へのアクセス (2) アロー演算子 (ポインタ) struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane, yasu; struct seiseki *p; p = &sane; strcpy(p->name, "saneyasu"); p->math=10; p->engl=20; p->name[0] = 'S'; printf("name=%s, math=%d\n", p->name, p->math); } (2) アロー演算子 -> 構造体変数へのポインタ -> メンバ変数 p->xyz と (*p).xyz は同義 実行結果 name=Saneyasu, math=10
メンバ変数へのアクセス (ポインタ) ドット演算子 と アロー演算子 struct seiseki sane, yasu; struct seiseki *p; p = &sane; (*p).math = 70; p->engl = 80; ポインタの場合, (*p).math = 70; p->math = 70; のどちらを用いても問題ない. 通常 -> を用いる. アロー演算子は, シンタックスシュガー
構造体の宣言 (1)一般的(?)な構造体宣言と変数宣言 struct hoge{ int x; char y; }; void main(){ struct hoge z; }
構造体の宣言 (2)構造体宣言と変数宣言を別々に struct hoge{ int x; char y; }; struct hoge a, b; void main(){ } struct hogeが型名で, aとbが変数名. aとbはグローバル変数. (3)構造体宣言と変数宣言を同時に struct hoge{ int x; char y; } a, b; void main(){ } struct hogeが型名で, aとbが変数名. aとbはグローバル変数.
構造体の宣言 (3)再掲載 struct hoge{ int x; char y; } a, b; void main(){ } struct hogeが型名で, aとbが変数名. (4)名無し構造体 struct { int x; char y; } a, b; void main(){ } ・その構造体を二度と使わない ・変数を同時に宣言している 場合は,構造体名を省略可能
構造体の宣言 (5)typedefの使用 struct hoge{ int x; char y; }; typedef struct hoge sthoge; void main(){ sthoge a, b; } struct hoge型に, sthoge型という別名を与えた. struct hogeとsthogeが型名, aとbが変数名.
構造体の宣言 (6)構造体宣言とtypedef同時使用 typedef struct hoge{ int x; char y; } sthoge; void main(){ sthoge a, b; } struct hogeとsthogeが型名, aとbが変数名. (7)構造体宣言とtypedef同時使用 typedef struct{ int x; char y; } sthoge; void main(){ sthoge a, b; } 名無し構造体にsthogeの別名を付与. sthogeが型名, aとbが変数名.
構造体の初期化 (1)変数宣言と初期化を同時に struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki sane={"SaneYama",10,20}; struct seiseki yasu={"Yasu",5}; printf("%s %d %d\n",sane.name,sane.math,sane.engl); printf("%s %d %d\n",yasu.name,yasu.math,yasu.engl); } nameに"SaneYama"が, mathに10が, englに20が入る. {"Yasu",5,0} と同じ意味. 省略すると, 0になる. 実行結果 SaneYama 10 20 Yasu 5 0
構造体の初期化 (2)構造体宣言と変数宣言と初期化を同時に struct seiseki{ char name[20]; int math; int engl; } sane={"SaneYama",10,20}; void main(){ printf("%s %d %d\n",sane.name,sane.math,sane.engl); } 実行結果 SaneYama 10 20
構造体の初期化 (3)配列 struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki stu[10] = { {"Taro",10,20}, {"Jiro",30,40}, {"Saburo",50,60} printf("%s %d\n", stu[1].name, stu[1].math); } 実行結果 Jiro 30
構造体の初期化 (4)配列 struct seiseki{ char name[20]; int math; int engl; }; void main(){ struct seiseki stu[10] = { "Taro",10,20, "Jiro",30,40, "Saburo",50,60 printf("%s %d\n", stu[1].name, stu[1].math); } データの対応が正しければ, 内側の括弧{}は 省略可能. 実行結果 Jiro 30
構造体の演算 構造体に対して,以下の演算が可能 代入 (構造体同士の丸ごとコピー) 構造体のアドレス取得 構造体のメンバを参照(紹介済み) 構造体同士の比較はできない if( stu[0] < stu[1] ) はできない メンバ変数の比較は可能
構造体の演算 (1) 構造体同士のコピー struct seiseki{ char name[20]; int math; int engl; }; struct seiseki sane, yasu; strcpy( sane.name, "Saneyasu"); sane.math=30; sane.engl=40; yasu = sane; printf("%s %d\n", yasu.name, yasu.engl); この代入で, saneの中身 (name,math,engl) のすべてが, yasuにコピーされる. name,math,englを 格納可能である入れ物が 2個作成される. 実行結果 Saneyasu 40
構造体の演算 (2) アドレス獲得 & struct seiseki{ char name[20]; int math; int engl; }; &演算子で,構造体の先頭アドレスを取得可能 (以下,再掲載) struct seiseki sane; printf("%p %p\n", &sane, &(sane.name[0])); printf("%p %p\n", &(sane.math), &(sane.engl)); ca18 ca2b ca2c ca2d ca2e caef ca30 ca31 ca32 ca33 実行結果 0xbfb2ca18 0xbfb2ca18 0xbfb2ca2c 0xbfb2ca30 char name[20] int math int engl struct seiseki sane
構造体の演算 (2) アドレス獲得 & struct seiseki{ char name[20]; int math; int engl; }; struct seiseki sane, stu[5]; struct seiseki *p, *q; p = &sane; // アドレスを取得し,pに代入 q = stu; // stu[0]のアドレスをqに代入 Printf("sizeof(struct seiseki)=%d", sizeof(struct seiseki)); printf("&sane=%p\n", p); printf("stu =%p\n", q); printf("stu+2=%p\n", q+2); 実行結果 sizeof(struct seiseki)=28 &sane=0x7fffa1018570 stu =0x7fffa10184e0 stu+2=0x7fffa1018518 4e0 4f3 4f4 f48 4fb 4fc stu と stu+2の 差が 28*2=56 char name[20] int math int engl stu[0] stu[1]
構造体とポインタ 実行結果 p++ : pに格納されているアドレスが1増える. stu[0] 10, stu[1] 30 struct seiseki stu[2]={ {"sane", 10, 20},{"yasu",30,40} }; struct seiseki *p; p = stu; // pにstu[0]のアドレスを入れる printf("stu[0] %d, stu[1] %d\n",stu[0].math,stu[1].math); p->math++; // stu[0]のmathを1増やす. 10から11に変わる. p++; // pを1増やす. pがstu[0]からstu[1]のアドレスになる. p->math++; // stu[1]のmathを1増やす. 30から31に変わる. struct seiseki{ char name[20]; int math; int engl; }; 実行結果 stu[0] 10, stu[1] 30 stu[0] 11, stu[1] 30 stu[0] 11, stu[1] 31 p++ : pに格納されているアドレスが1増える. p->math++ : 「pに格納されているアドレス」に 存在する構造体の中のmathが1増える.
関数引数と構造体 (値渡し) aの中身は 変わっていない 実行結果 hoge: 10 20 main: 10 20 struct seiseki{ char name[20]; int math; int engl; }; void hoge(struct seiseki a){ printf("hoge: %d %d\n", a.math, a.engl); a.math = 90; a.engl = 95; } void main(){ struct seiseki a = {"sane",10,20}; hoge(a); printf("main: %d %d\n", a.math, a.engl); aの中身は 変わっていない 実行結果 hoge: 10 20 main: 10 20
関数引数と構造体 (アドレス渡し) aの中身は 変わっている 実行結果 hoge: 10 20 main: 90 95 struct seiseki{ char name[20]; int math; int engl; }; void hoge(struct seiseki *a){ printf("hoge: %d %d\n", a->math, a->engl); a->math = 90; a->engl = 95; } void main(){ struct seiseki a = {"sane",10,20}; hoge(&a); printf("main: %d %d\n", a.math, a.engl); aの中身は 変わっている 実行結果 hoge: 10 20 main: 90 95
struct score{ int math; int engl; }; void hoge( struct score struct score{ int math; int engl; }; void hoge( struct score *s){ // s[0]の中のmathを100にする } void main(){ struct score s[100]; s[0].math=…; s[0].engl=…; s[1].math=…; s[1].engl=…; : hoge( s ); 練習0
struct score{ int math; int engl; }; void fuga( struct score struct score{ int math; int engl; }; void fuga( struct score *s){ // s[10]の中のmathを100にする } void main(){ struct score s[100]; s[0].math=…; s[0].engl=…; s[1].math=…; s[1].engl=…; : hoge( s ); 練習1
struct score{ int math; int engl; }; void piyo( struct score struct score{ int math; int engl; }; void piyo( struct score *s){ //すべて(s[0]~s[99])の中のmathを100に } void main(){ struct score s[100]; s[0].math=…; s[0].engl=…; s[1].math=…; s[1].engl=…; : hoge( s ); 練習2
練習0 正解 (*s).math = 100; または s->math = 100; 以下はNG *s.math = 100; s.math = 100; 正解 0, 1, 2 練習1 正解 (*(s+10)).math = 100; または (s+10)->math = 100; 以下はNG (*s+10).math = 100; *(s+10).math = 100; *s+10.math = 100; 練習2 正解 int i; for(i=0; i<100; i++){ (s+i)->math = 100; }
入れ子構造体 (1) 構造体の中に構造体 構造体の中に,構造体 struct score{ int math; int engl; }; struct student{ struct score sc; char name[20]; void main(){ struct student sane; strcpy( sane.name, "Saneyasu"); sane.sc.math = 30; sane.sc.engl = 40; printf("%s %d\n", sane.name, sane.sc.math); } 構造体の中に,構造体 struct student型の変数saneの中に, struct score型の変数sがある. それは sane.sc である. struct score型の sane.sc の中に, int型の変数mathがある. それは sane.sc.math である. これで,math,engl,name のすべての箱ができる. 実行結果 Saneyasu 30
入れ子構造体 (2a) 構造体の中に構造体へのポインタ 構造体の中に, 構造体へのポインタ void main(){ struct student sane; : } struct score{ int math; int engl; }; struct student{ struct score *p; char name[20]; これで アドレスを入れる箱p と, charを20個入れるname ができる. intを入れるmath や intを入れるengl は作成されない. 100 101 102 103 104 105 123 p name[20] sane
入れ子構造体 (2a) 構造体の中に構造体へのポインタ 構造体の中に, 構造体へのポインタ void main(){ struct student sane; struct score sc={10,20}; sane.p = ≻ strcpy( sane.name, "Sane"); printf("%d\n", sane.p->math); printf("%d\n", sane.p->engl); printf("%s\n", sane.name); } struct score{ int math; int engl; }; struct student{ struct score *p; char name[20]; saneはアドレスでないので sane. とする 100 101 102 103 104 105 123 124 125 126 127 128 129 130 131 実行結果 10 20 Sane 124 'S' 'a' 10 20 p name[20] math engl sane sc
入れ子構造体 (2b) 構造体の中に構造体へのポインタ void main(){ struct score sc={10,20}; struct student sane; struct student *q; sane.p = ≻ q = &sane; strcpy( q->name, "Sane"); printf("%d\n", q->p->math); printf("%d\n", q->p->engl); printf("%s\n", q->name); } 構造体の中に, 構造体へのポインタ struct score{ int math; int engl; }; struct student{ struct score *p; char name[20]; q はアドレスなので q-> とする 100 101 102 103 104 105 123 124 125 126 127 128 129 130 131 実行結果 10 20 Sane 124 'S' 'a' 10 20 p name[20] math engl sane sc
自己参照型構造体 (1) 800 804 808 812 816 820 824 828 10 808 20 816 30 824 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d; a.data = 10; a.next = &b; b.data = 20; b.next = &c; c.data = 30; c.next = &d; d.data = 40; d.next = NULL; data next data next data next data next a b c d printf("%d ", a.data); printf("%d ", a.next->data); printf("%d ", a.next->next->data); printf("%d\n", a.next->next->next->data); } 実行結果 10 20 30 40
自己参照型構造体 (1) 800 804 808 812 816 820 824 828 10 808 20 816 30 824 40 data next data next data next data next a b c d a b c d data 10 data 20 data 30 data 40 next next next next X リンクリスト
自己参照型構造体 (2) これで p に 800(aのアドレス)が 代入される. 実行結果 10 20 30 40 800 804 808 812 816 820 824 828 10 808 20 816 30 824 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; a.next = &b; b.data = 20; b.next = &c; c.data = 30; c.next = &d; d.data = 40; d.next = NULL; data next data next data next data next a b c d これで p に 800(aのアドレス)が 代入される. p = &a; for(;;){ printf("%d ", p->data); p = p->next; if( p == NULL ){ break; } } 実行結果 10 20 30 40
自己参照型構造体 (2) これで, 「pがNULLでない間は 繰り返す」の意味になる. NULLと0はほぼ同じ. 実行結果 800 804 808 812 816 820 824 828 10 808 20 816 30 824 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; a.next = &b; b.data = 20; b.next = &c; c.data = 30; c.next = &d; d.data = 40; d.next = NULL; data next data next data next data next a b c d これで, 「pがNULLでない間は 繰り返す」の意味になる. NULLと0はほぼ同じ. p = &a; for(; p; ){ printf("%d ", p->data); p = p->next; } 実行結果 10 20 30 40
自己参照型構造体 (2) もっと短くすることもできる. 実行結果 10 20 30 40 800 804 808 812 816 820 824 828 10 808 20 816 30 824 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; a.next = &b; b.data = 20; b.next = &c; c.data = 30; c.next = &d; d.data = 40; d.next = NULL; data next data next data next data next a b c d もっと短くすることもできる. for(p=&a; p; ){ printf("%d ", p->data); p = p->next; } 実行結果 10 20 30 40
自己参照型構造体 (2) もっと短くすることもできる. 実行結果 10 20 30 40 800 804 808 812 816 820 824 828 10 808 20 816 30 824 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; a.next = &b; b.data = 20; b.next = &c; c.data = 30; c.next = &d; d.data = 40; d.next = NULL; data next data next data next data next a b c d もっと短くすることもできる. for(p=&a; p; p = p->next){ printf("%d ", p->data); } 実行結果 10 20 30 40
自己参照型構造体 (3) リストからbを削除 a.next = a.next->next; a b c d data 10 data 20 data 30 data 40 next next next next X リストからbを削除 a.next = a.next->next; a b c d data 10 data 20 data 30 data 40 next next next next X
自己参照型構造体 (3) 800 804 808 812 816 820 824 828 10 816 20 816 30 824 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; a.next = &b; b.data = 20; b.next = &c; c.data = 30; c.next = &d; d.data = 40; d.next = NULL; data next data next data next data next a b c d a.next = a.next->next; p = &a; for(;;){ printf("%d ", p->data); p = p->next; if( p == NULL ){ break; } } 実行結果 10 30 40
自己参照型構造体 (4) bとcの順を交換 旧:a→b→c→d 新:a→c→b→d a b c d data 10 data 20 data 30 data 40 next next next next X bとcの順を交換 旧:a→b→c→d 新:a→c→b→d a b c d data 10 data 20 data 30 data 40 next next next next X
自己参照型構造体 (4) 800 804 808 812 816 820 824 828 10 816 20 824 30 808 40 #include <stdio.h> struct dp{ int data; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; : d.next = NULL; p = &a; struct dp *x, *y, *z; x = p->next; y = p->next->next; z = p->next->next->next; data next data next data next data next a b c d x->next = z; y->next = x; p->next = y; for(;;){ printf("%d ", p->data); p = p->next; if( p == NULL ){ break; } } 実行結果 10 30 20 40
自己参照型構造体 (5) 双方向リスト #include <stdio.h> struct dp{ int data; struct dp *prev; struct dp *next; }; void main(){ struct dp a, b, c, d, *p; a.data = 10; a.prev = NULL; a.next = &b; b.data = 20; b.prev = &a; b.next = &c; c.data = 30; c.prev = &b; c.next = &d; d.data = 40; d.prev = &c; d.next = NULL; p = &a; : } 800 804 808 812 816 820 824 828 832 836 840 844 10 812 20 800 824 30 812 828 40 824 data prev next data prev next data prev next data prev next a b c d
自己参照型構造体 (5) a b c d data 10 data 20 data 30 data 40 prev X prev prev next next next next X
自己参照型構造体 (練習) 双方向リスト #include <stdio.h> struct dp{ int data; struct dp *prev; struct dp *next; }; void main(){ struct dp *p, *q; struct dp x; : p = …; q = p-> next; /* p の次が q である. p と q の間に x を挿入するには? */ ??? : }
自己参照型構造体 (練習) cとdの間にxを挿入交換 旧:… b ←→ c ←→ d 新:… b ←→ c ←→ x ←→ d a b c data 10 data 20 data 30 data 40 prev X prev prev prev next next next next X cとdの間にxを挿入交換 旧:… b ←→ c ←→ d 新:… b ←→ c ←→ x ←→ d x data 100 prev (2) (1) next a b c d data 10 data 20 data 30 (3) data 40 (4) prev X prev prev prev next next next next X
自己参照型構造体 (正解) 双方向リスト #include <stdio.h> struct dp{ int data; struct dp *prev; struct dp *next; }; void main(){ struct dp *p, *q; struct dp x; : p = …; q = p-> next; p->next = &x; /* (1) */ q->prev = &x; /* (2) */ x.prev = p; /* (3) */ /* x.prev = &p; は NG */ x.next = q; /* (4) */ /* x.next = &q; は NG */ : }