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