構造体

プログラミング論
構造体
http://www.ns.kogakuin.ac.jp/~ct13140/Prog/
S-1
概要
• 構造体
– 複数の変数を組合わせて,ひとまとめにしたもの
– 簡単
– 重要
• 自己参照型, リスト
– 重要, 難しい
S-2
新しい構造体の宣言
struct 構造体名{
データ型 メンバ名1;
データ型 メンバ名2;
:
データ型 メンバ名3;
};
例
struct
char
int
int
};
seiseki{
name[20];
math;
engl;
これで「struct seiseki型」
という新しい型が作成(宣言)された.
mathやenglは構造体のメンバ.
S-3
構造体型の変数の宣言
struct seiseki{
yasuという1個の変数の中に,
char name[20];
char name[20],
int math;
int math, int englの
int engl;
3個が含まれている.
};
void main(){
struct seiseki sane, yasu;
:
int x, y;
}
なら,intが型,
「struct seiseki」が型.
xやyが変数名.
sane や yasu が変数名.
S-4
メンバ変数へのアクセス
• (1) ドット(dot)演算子
.
– 構造体変数.メンバ変数
• 構造体変数aの中の,メンバ変数xにアクセス a.x
• 構造体へのポインタpが指す構造体の中の,メンバ変
数yにアクセス (*p).y
• (2) アロー(arrow)演算子
->
– 構造体変数へのポインタ->メンバ変数
• 構造体へのポインタpが指す構造体の中の,メンバ変
数zにアクセス p->z
S-5
メンバ変数へのアクセス
(1) ドット演算子 (非ポインタ)
(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);
}
実行結果
name=Saneyasu, math=80
S-6
メンバ変数へのアクセス
(1) ドット演算子 (ポインタ)
(1) ドット演算子 .
struct seiseki{
構造体変数 . メンバ変数
char name[20];
1000
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
int math;
int engl;
struct seiseki *p
char name[20]
int math
int engl
};
yasu
void main(){
struct seiseki sane, yasu;
struct seiseki *p;
p = &yasu;
// アドレス1000~1027に1000が格納される
yasu.math = 50; // アドレス1020~1023に50が格納される
(*p).engl = 60;
printf("math=%d, engl=%d\n", (*p).math, yasu.engl);
}
実行結果
math=50, engl=60
S-7
メンバ変数へのアクセス
(1) ドット演算子 (ポインタ)
(1) ドット演算子 .
struct seiseki{
構造体変数 . メンバ変数
char name[20];
ca18
ca2b ca2c ca2d ca2e caef ca30 ca31 ca32 ca33
int math;
int engl;
};
char name[20]
int math
int engl
sane
void main(){
struct seiseki sane, yasu;
printf("%p %p\n", &sane, &(sane.name[0]));
printf("%p %p\n", &(sane.math), &(sane.engl));
}
実行結果
0xbfb2ca18 0xbfb2ca18
0xbfb2ca2c 0xbfb2ca30
S-8
メンバ変数へのアクセス
(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
メンバ変数へのアクセス
(2) アロー演算子 (ポインタ)
(2) アロー演算子 ->
struct seiseki{
構造体変数へのポインタ -> メンバ変数
char name[20];
p->xyz と (*p).xyz は同義
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;
name=Saneyasu, math=10
p->name[0] = 'S';
printf("name=%s, math=%d\n", p->name, p->math);
}
S-10
メンバ変数へのアクセス (ポインタ)
ドット演算子 と アロー演算子
struct seiseki sane, yasu;
struct seiseki *p;
p = &sane;
ポインタの場合,
(*p).math = 70;
(*p).math = 70;
p->engl = 80;
p->math = 70;
のどちらを用いても問題ない.
通常 -> を用いる.
アロー演算子は,
シンタックスシュガー
S-11
構造体の宣言
(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(){
(3)構造体宣言と変数宣言を同時に
struct hoge{
int x;
char y;
} a, b;
void main(){
}
}
struct hogeが型名で,
aとbが変数名.
aとbはグローバル変数.
struct hogeが型名で,
aとbが変数名.
aとbはグローバル変数.
S-13
構造体の宣言
(3)再掲載
struct hoge{
int x;
char y;
} a, b;
void main(){
(4)名無し構造体
struct {
int x;
char y;
} a, b;
void main(){
}
}
・その構造体を二度と使わない
・変数を同時に宣言している
場合は,構造体名を省略可能
struct hogeが型名で,
aとbが変数名.
S-14
構造体の宣言
(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が変数名.
(7)構造体宣言とtypedef同時使用
typedef struct{
int x;
char y;
} sthoge;
void main(){
sthoge a, b;
}
名無し構造体にsthogeの別名を付与.
sthogeが型名,
aとbが変数名.
S-16
構造体の初期化
nameに"SaneYama"が,
• (1)変数宣言と初期化を同時に
mathに10が,
struct seiseki{
englに20が入る.
char name[20];
{"Yasu",5,0}
int math;
と同じ意味.
int engl;
省略すると, 0になる.
};
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);
}
実行結果
SaneYama 10 20
Yasu 5 0
S-17
構造体の初期化
• (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
S-18
構造体の初期化
• (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}
Jiro 30
};
printf("%s %d\n", stu[1].name, stu[1].math);
}
S-19
構造体の初期化
• (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
Jiro 30
};
printf("%s %d\n", stu[1].name, stu[1].math);
}
S-20
構造体の演算
• 構造体に対して,以下の演算が可能
(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;
saneの中身
(name,math,engl)
yasu = sane;
のすべてが,
yasuにコピーされる.
struct
char
int
int
};
seiseki{
name[20];
math;
engl;
name,math,englを
格納可能である入れ物が
2個作成される.
printf("%s %d\n", yasu.name, yasu.engl);
実行結果
Saneyasu 40
S-22
構造体の演算 (2)
アドレス獲得 &
struct
char
int
int
};
&演算子で,構造体の先頭アドレスを取得可能
(以下,再掲載)
struct seiseki sane;
printf("%p %p\n", &sane, &(sane.name[0]));
printf("%p %p\n", &(sane.math), &(sane.engl));
ca18
seiseki{
name[20];
math;
engl;
ca2b ca2c ca2d ca2e caef ca30 ca31 ca32 ca33
char name[20]
int math
int engl
実行結果
0xbfb2ca18 0xbfb2ca18
0xbfb2ca2c 0xbfb2ca30
struct seiseki sane
S-23
構造体の演算 (2)
アドレス獲得 &
struct
char
int
int
};
seiseki{
name[20];
math;
engl;
struct seiseki sane, stu[5];
struct seiseki *p, *q;
p = &sane; // アドレスを取得し,pに代入
q = stu;
// stu[0]のアドレスをqに代入
printf("&sane=%p\n", p);
実行結果
printf("stu =%p\n", q);
sizeof(struct seiseki)=28
&sane=0x7fffa1018570
printf("stu+2=%p\n", q+2);
stu =0x7fffa10184e0
stu と stu+2の
stu+2=0x7fffa1018518
差が 28*2=56
4e0
4f3
char name[20]
4f4
f48
int math
stu[0]
4fb
4fc
int engl
stu[1]
S-24
構造体とポインタ
struct seiseki stu[2]={
struct seiseki{
char name[20];
{"sane", 10, 20},{"yasu",30,40}
int math;
};
int engl;
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);
p++ : pに格納されているアドレスが1増える.
p->math++ : 「pに格納されているアドレス」に
存在する構造体の中のmathが1増える.
実行結果
stu[0] 10, stu[1] 30
stu[0] 11, stu[1] 30
stu[0] 11, stu[1] 31
S-25
関数引数と構造体 (値渡し)
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の中身は
a.engl = 95;
変わっていない
}
void main(){
実行結果
struct seiseki a = {"sane",10,20};
hoge: 10 20
hoge(a);
main: 10 20
printf("main: %d %d\n", a.math, a.engl);
}
S-26
関数引数と構造体 (アドレス渡し)
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の中身は
a->engl = 95;
変わっている
}
void main(){
実行結果
struct seiseki a = {"sane",10,20};
hoge: 10 20
hoge(&a);
main: 90 95
printf("main: %d %d\n", a.math, a.engl);
}
S-27
入れ子構造体 (1)
構造体の中に構造体
struct score{
構造体の中に,構造体
int math;
struct student型の変数saneの中に,
int engl;
struct score型の変数sがある.
};
それは sane.sc である.
struct student{
struct score型の sane.sc の中に,
struct score sc;
int型の変数mathがある.
char name[20];
それは sane.sc.math である.
};
void main(){
これで,math,engl,name
struct student sane;
のすべての箱ができる.
strcpy( sane.name, "Saneyasu");
sane.sc.math = 30;
sane.sc.engl = 40;
実行結果
printf("%s %d\n", sane.name, sane.sc.math); Saneyasu 30
}
S-28
入れ子構造体 (2a)
構造体の中に構造体へのポインタ
構造体の中に,
構造体へのポインタ
void main(){
struct student sane;
:
}
struct score{
int math;
int engl;
};
struct student{
struct score *p;
char name[20];
};
100
'S'
119 120
'a'
name[20]
sane
121
122
これで
アドレスを入れる箱p と,
charを20個入れるname ができる.
intを入れるmath や
intを入れるengl は作成されない.
123 124
125
126
127 128
129
130
124
10
20
p
math
engl
sc
131
実行結果
10
20
Sane
S-29
入れ子構造体 (2a)
構造体の中に構造体へのポインタ
構造体の中に,
構造体へのポインタ
void main(){
struct student sane;
struct score sc={10,20};
sane.p = &sc;
strcpy( sane.name, "Sane");
printf("%d\n", sane.p->math);
printf("%d\n", sane.p->engl);
printf("%s\n", sane.name);
}
saneはアドレスでないので sane. とする
struct score{
int math;
int engl;
};
struct student{
struct score *p;
char name[20];
};
100
'S'
119 120
'a'
name[20]
sane
121
122
123 124
125
126
127 128
129
130
124
10
20
p
math
engl
sc
131
実行結果
10
20
Sane
S-30
入れ子構造体 (2b)
構造体の中に構造体へのポインタ
void main(){
struct score sc={10,20};
struct student sane;
struct student *q;
sane.p = &sc;
q はアドレスなので
q-> とする
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];
};
100
'S'
119 120
'a'
name[20]
sane
121
122
123 124
125
126
127 128
129
130
124
10
20
p
math
engl
sc
131
実行結果
10
20
Sane
S-31
自己参照型構造体 (1)
800
804
808
812
816
820
824
828
10
808
20
816
30
824
40
0
#include <stdio.h>
data next data next data next data next
struct dp{
a
b
c
d
int data;
struct dp *next;
};
void main(){
struct dp a, b, c, d;
a.data = 10;
a.next = &b;
b.data = 20;
printf("%d ", a.data);
b.next = &c;
printf("%d ", a.next->data);
c.data = 30;
printf("%d ", a.next->next->data);
c.next = &d;
printf("%d\n", a.next->next->next->data);
d.data = 40;
}
実行結果
d.next = NULL;
10 20 30 40
S-32
自己参照型構造体 (1)
800
804
808
812
816
820
824
828
10
808
20
816
30
824
40
0
data
next
data
next
data
next
data
next
a
a
data
next
b
b
10
data
next
c
d
c
20
data
next
d
30
data
40
next
• リンクリスト
S-33
自己参照型構造体 (2)
800
804
808
812
816
820
824
828
10
808
20
816
30
824
40
0
#include <stdio.h>
data next data next data next data next
struct dp{
a
b
c
d
int data;
struct dp *next;
};
これで p に
void main(){
800(aのアドレス)が
struct dp a, b, c, d, *p;
代入される.
a.data = 10;
p = &a;
a.next = &b;
for(;;){
b.data = 20;
printf("%d\n", p->data);
b.next = &c;
p = p->next;
c.data = 30;
if( p == NULL ){ break; }
c.next = &d;
}
d.data = 40;
実行結果
}
d.next = NULL;
10 20 30 40
S-34
自己参照型構造体 (4)
a
data
b
10
next
data
c
20
next
data
d
30
next
data
40
next
リストからbを削除
a.next = a.next->next;
a
data
next
b
10
data
next
c
20
data
next
d
30
data
40
next
S-35
自己参照型構造体 (4)
800
804
808
812
816
820
824
828
10
816
#include <stdio.h>
data next
struct dp{
a
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;
20
816
30
824
40
0
data
next
data
next
data
next
b
c
d
a.next = a.next->next;
p = &a;
for(;;){
printf("%d\n", p->data);
p = p->next;
if( p == NULL ){ break; }
}
}
実行結果
10 30 40
S-36
自己参照型構造体 (5)
800
804
808
812
816
820
824
828
10
808
#include <stdio.h>
data next
struct dp{
a
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;
20
816
30
824
40
0
data
next
data
next
data
next
b
c
d
双方向リスト
c.prev =
c.next =
d.data =
d.next =
p = &a;
:
&b;
&d;
40;
NULL;
}
S-37