cp-13. 構造体
(C プログラミング入門)
URL: https://www.kkaneko.jp/pro/adp/index.html
1
金子邦彦
C言語における構造体の定義方法、構造体を用いた配列・ポ
インタ・リストの作成と操作
【学習内容の構成】
1. 構造体の基本:複数のデータをグループ化して新しい型
を定義する方法
2. 構造体の配列:住所録のような複数件のデータを構造体
配列で管理
3. 構造体と関数:ポインタ渡しによる構造体データの受け
渡しとアロー演算子(->)によるメンバアクセス
4. 構造体のリスト:ポインタを用いた動的なデータ構造の
実現と動的メモリ管理(new/malloc)
• 前提:C言語の基本データ型、配列、ポインタ、関数の知
識
• 意義:実用的なデータ管理プログラムの設計能力の獲得
2
内容
例題1.住所録
例題2.構造体と関数
構造体とは.構造体を扱う関数.
例題3.構造体のリスト
• 構造体とポインタの組み合わせ
3
目標
• 自分で「構造体」を定義する
• 自分で定義した構造体について,配列やポインタ
を作成する
4
データ型
• 基本データ型
char 文字(1文字)
int 整数
double 浮動小数
など
・その他のデータ型
配列→ データの並び(文字列も,文字の並び)
ポインタ → メモリアドレス
構造体 → いくつかのデータを「グループ化」したもの
など
5
データの集まり
住所
氏名
支店名
口座番号
残高
銀行口座
氏名
年齢
住所
住所録
実数部
虚数部
複素数
6
構造体とは?
• いくつかのデータを,グループ化して,新しい
型の名前を付けたもの
• 基本データ型(整数,浮動小数)などの組み合
わせ
7
例題1.住所録
• 住所録を表示するプログラムを作る
• 住所録データを扱うために,構造体の配列を使う
• 住所録は,名前(サイズ20の文字の配列),年齢,
住所(サイズ40の文字の配列)から構成する
8
#include <stdio.h>
#pragma warning(disable:4996)
struct Person {
char name[20];
int age;
char address[40];
};
int main()
{
struct Person a[] = {{"Ken", 20, "NewYork"},
{"Bill", 32, "HongKong"},
{"Mike", 35, "Paris" }};
int i;
for ( i = 0; i < 3; i ++ ) {
printf( "name=%s, age=%d, address=%s¥n", a[i].name, a[i].age, a[i].address );
}
return 0;
}
構造体 Person の型宣言
構造体の配列 a
の宣言と初期化
構造体のメンバの
読み出し
9
住所録
実行結果の例
name=Ken, age=20, address=NewYork
name=Bill, age=32, address=HongKong
name=Mike, age=35, address=Paris
10
プログラムとデータ
メモリ
a[0]
a[i].name
a[i].age
a[i].address
構造体の配列から
の値の読み出し
Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
a[1]
a[2]
name age address
11
構造体の型宣言
• 構造体は,いくつかのデータをグループ化したも
の.
• 構造体には,名前がある
• それぞれのデータ(メンバという)は,名前と型
(データの種類のこと)がある.
struct Person {
char name[20];
int age;
char address[40];
};
名前
メンバ
12
構造体メンバの読み書き
• 配列の中身を読み書きするときには,構造体
のメンバを書く
例) a[i].name
これがメンバ
a[0] Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
a[1]
a[2]
name age address
13
構造体の使い方
#include <stdio.h>
#pragma warning(disable:4996)
struct Person {
char name[20];
int age;
char address[40];
};
int main()
{
struct Person a[] = {{"Ken", 20, "NewYork"},
{"Bill", 32, "HongKong"},
{"Mike", 35, "Paris" }};
int i;
for ( i = 0; i < 3; i ++ ) {
printf( "name=%s, age=%d, address=%s¥n", a[i].name, a[i].age, a[i].address );
}
return 0;
}
②ここで Person を使って、「構造体の配列 a」 を宣言
(変数の宣言)
①ここで「構造体 Person 」を宣言
(構造体の宣言)
③ここで a を使う
14
例題2.構造体と関数
• 3人分の住所録を読み込んで,構造体の配列に格
納した後に,表示するプログラムを作る
• 住所録データを扱うために,構造体の配列を使う
• 住所録は,例題1と同じく,名前(サイズ20の文字
の配列),年齢,住所(サイズ40の文字の配列)か
ら構成する
• ここでは,練習のため,1人分の住所録を読み込む関
数,1人分の住所録を表示する関数を作る.これら関
数への引数として,構造体のポインタを渡すこと.
15
#include <stdio.h>
struct Person {
char name[20];
int age;
char address[40];
};
void read_person( struct Person* a )
{
printf("name=");
scanf("%s", a->name );
printf("age=");
scanf("%d", &a->age );
printf("address=");
scanf("%s", a->address );
return;
}
void print_person( struct Person* a )
{
printf( "name=%s, age=%d, address=%s¥n", a->name, a->age, a->address );
return;
}
構造体 Person の型宣言
構造体の
ポインタ渡しに関係する
16
int main()
{
struct Person a[3];
int i;
for ( i = 0; i < 3; i ++ ) {
read_person( &a[i] );
}
for ( i = 0; i < 3; i ++ ) {
print_person( &a[i] );
}
return 0;
}
構造体の
ポインタ渡しに関係する
17
構造体と関数
実行結果の例
name=Ken
age=20
address=NewYork
name=Bill
age=32
address=HongKong
name=Mike
age=35
address=Paris
name=Ken, age=20, address=NewYork
name=Bill, age=32, address=HongKong
name=Mike, age=35, address=Paris
18
関数呼び出しの流れ
read_person関数
void read_person( struct Person* a )
main 関数
int main()
read_person( &a[i] );
関数呼び出し
return;
戻り
print_person( &a[i] );
関数呼び出し
return;
戻り
print_person関数
void print_person( struct Person* a )
19
void read_person( struct Person* a )
{
printf("name=");
scanf("%s", a->name );
printf("age=");
scanf("%d", &a->age );
printf("address=");
scanf("%s", a->address );
return;
}
void print_person( struct Person* a )
{
printf( "name=%s, age=%d, address=%s¥n", a->name, a->age, a->address );
return;
}
int main()
{
struct Person a[3];
int i;
for ( i = 0; i < 3; i ++ ) {
read_person( &a[i] );
}
for ( i = 0; i < 3; i ++ ) {
print_person( &a[i] );
}
return 0;
}
プログラム実行順
①
②
⑩
⑪
③
④
read_person関数
main関数
⑫
関数呼び出し
戻り
main 関数の先頭行
がプログラムの始まり
main 関数内の return
がプログラムの終わり
print_person関数
戻り
関数呼び出し
⑤
⑥
⑦
⑧
⑨
⑬
⑭
20
データの流れ
型 仮引数
① メモリアドレスを,
read_person 関数に渡す
②メモリアドレスを受け取って,
「a」という名前で使う
③main 関数には,
何も返さない
read_person関数
void read_person( struct Person* a )
main 関数
int main()
read_person( &a[i] );
関数呼び出し
return;
戻り
print_person( &a[i] );
関数呼び出し
return;
戻り
print_person関数
void print_person( struct Person* a )
⑥main 関数には,
何も返さない
型 仮引数
⑤メモリアドレスを受け取って,
「a」という名前で使う
④ メモリアドレスを,
print_person 関数に渡す
21
read_person関数呼び出しでのデータの流れ
main 関数内で宣言された a
a
メモリアドレス
main 関数内で宣言された a
と,仮引数で宣言された a は
別のもの
型
仮引数
① メモリアドレスを,
read_person 関数に渡す
②メモリアドレスを受け取って,
「a」という名前で使う
read_person関数
void read_person( struct Person* a )
main 関数
int main()
read_person( &a[i] );
関数呼び出し
return;
戻り
③main 関数には,
何も返さない
a[0]
Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
a[1]
a[2]
name age address
a[0]
Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
a[1]
a[2]
name age address
22
関数への構造体の受け渡し
• 呼び出し側
• 「&」を付けて,メモリアドレスを,関数に渡す
例) read_person( &a[i] );
print_person( &a[i] );
• 関数側
• メモリアドレスを受け取ることを宣言しておく
例) void read_person( struct Person* a )
void print_person( struct Person* a )
「a[i] のメモリアドレス」という意味
「メモリアドレスを受け取って,a
として使う」という意味
23
配列とポインタ
プログラム例: read_person( &a[i] );
a[0] Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
a[1]
a[2]
name age address
i = 0 ならここ
i = 1 ならここ
i = 2 ならここ
24
演算子 -> の意味
メモリアドレスから,構造体メンバにアクセス
void read_person( struct Person* a )
{
printf("name=");
scanf("%s", a->name );
printf("age=");
scanf("%d", &a->age );
printf("address=");
scanf("%s", a->address );
return;
}
例)
ここでは,「a」に入っているのはメモリアドレス
25
scanf で a->age にだけ & をつける理
由
• 文字列
• a->name, a->address は,「文字の配列の先頭メモリアド
レス」という意味 (&は付けない)
• 整数,浮動小数
• &a->age は,「整数データのメモリアドレス」という意味
(&を忘れると,うまく動かない)
scanf("%s", a->name );
scanf("%d", &a->age );
scanf("%s", a->address );
26
課題1.住所録の条件検索
• 3人分の住所録を読み込んで,構造体の配列に格納し
た後に,「20歳以上」のデータだけを選んで表示す
るプログラムを作りなさい
• ここでは,「20歳以上のデータだけを表示する機能」を
持った関数(main 関数とは別の関数)を作ること
• 住所録データを扱うために,構造体の配列を使うこと
• 住所録は,例題1と同じく,名前(サイズ20の文字の配
列),年齢,住所(サイズ40の文字の配列)から構成す
る
• 確かに,20歳以上のデータだけが表示されることを確認
すること
27
課題2.日付
• 日付を扱う構造体を設計し、それを使ったプロ
グラムを作成しなさい
• 日付データを扱うために,構造体を使うこと
• 日付は,年(整数データ),月(整数データ),日
(整数データ)から構成する
• 日付を読み込んで,1か月分のカレンダーを表示する
ようなプログラムであること.
例)日付が「2001年12月21日」なら,20
01年12月の1か月分のカレンダーを表示する
28
例題3.構造体のリスト
• 住所録の読み込みと表示を行うプログラムを
作る
• 住所録データを扱うために,構造体のリストを使
う
• 住所録は,名前(サイズ20の文字の配列),年
齢,住所(サイズ40の文字の配列)から構成す
る
• メニュー機能を作るために switch 文を使う
29
#include <stdio.h>
#include <string.h>
#include <malloc.h>
struct Person {
char name[20];
int age;
char address[40];
};
struct PersonNode {
struct Person person;
struct PersonNode* next;
};
struct PersonList {
struct PersonNode* top;
};
構造体 Person の型宣言
構造体 PersonNode の型宣言
構造体 PersonList の型宣言
30
void insert_head( struct PersonList* a, char* name, int age, char* address )
{
struct PersonNode* x = new struct PersonNode();
strcpy(x->person.name, name);
x->person.age = age;
strcpy(x->person.address, address);
x->next = a->top;
a->top = x;
return;
}
void input_data( struct PersonList* a )
{
char name[20];
int age;
char address[40];
printf("name=");
scanf("%s", name );
printf("age=");
scanf("%d", &age );
printf("address=");
scanf("%s", address );
insert_head( a, name, age, address );
return;
}
構造体へのポインタ x
の宣言と初期化
構造体のメンバの
読み出し
31
void print_person( struct Person* a )
{
printf( "name=%s, age=%d, address=%s¥n", a->name, a->age, a->address );
return;
}
void print_data( struct PersonList* a )
{
PersonNode* current;
if ( a->top == NULL ) {
return;
}
current = a->top;
do {
print_person( &(current->person) );
current = current->next;
} while( current != NULL );
}
構造体へのポインタ current
の宣言
構造体のメンバの
読み出し
構造体のメンバの
読み出し
32
void menu()
{
struct PersonList* a = new PersonList();
a->top = NULL;
int command;
while(1) {
printf("menu¥n");
printf("--------¥n");
printf("1. input¥n");
printf("2. print¥n");
printf("9. exit¥n");
scanf("%d", &command );
if ( command == 9 ) {
return;
}
switch( command ) {
case 1:
input_data( a );
break;
case 2:
print_data( a );
break;
default:
printf("invalid command¥n");
}
}
return;
}
構造体へのポインタ a
の宣言と初期化
switch 文については後述
33
int main()
{
menu();
return 0;
}
34
実行結果の例
menu
--------
1. input
2. print
9. exit
1
name=Ken
age=20
address=NewYork
menu
--------
1. input
2. print
9. exit
1
name=Bill
age=32
address=HongKong
menu
--------
1. input
2. print
9. exit
2
name=Bill, age=32, address=HongKong
name=Ken, age=20, address=NewYork
35
リスト
• 「何か」を順に並べたもの
• 順序に意味がある
ポインタを使ったリストの実現例
ポインタ
部分
データ
部分
1つの構造体
データ データ データ データ
NULL
NULLで,リストの
末端であることを表す
36
リストの例
struct Person {
char name[20];
int age;
char address[40];
};
struct PersonNode {
struct Person person;
struct PersonNode* next;
};
struct PersonList {
struct PersonNode* top;
};
Bill 32 HongKong Ken 20 NewYork
ポインタ
ポインタ
NULL
構造体
struct PersonList
構造体
struct Person
構造体
struct Person
構造体
struct PersonNode
構造体
struct PersonNode
37
リストの辿り
current = a->top;
do {
print_person( &(current->person) );
current = current->next;
} while( current != NULL );
Bill 32 HongKong Ken 20 NewYork
ポインタ
ポインタ
NULL
ループ1回目は,cuurent は,
ここへのポインタ
ループ2回目は,cuurent は,
ここへのポインタ
( current->next が NULL なので,
ループが終了する)
38
動的メモリ管理
• プログラムの実行中に、必要に応じて「メモ
リを確保」、「メモリを解放」すること
• new, delete を使用
39
リストと動的メモリ管理
「新しいノード」を作るには,new あるいは malloc を
使う.下記は new を使った例
void insert_head( struct PersonList* a, char* name, int age, char* address )
{
struct PersonNode* x = new struct PersonNode();
strcpy(x->person.name, name);
x->person.age = age;
strcpy(x->person.address, address);
x->next = a->top;
a->top = x;
return;
}
40
挿入
void insert_head( struct PersonList* a, char* name, int age, char* address )
{
struct PersonNode* x = new struct PersonNode();
strcpy(x->person.name, name);
x->person.age = age;
strcpy(x->person.address, address);
x->next = a->top;
a->top = x;
return;
}
①
②
③
④
⑤
⑥
Bill 32 HongKong
Ken 20 NewYork
ポインタ
ポインタ
NULL
⑤の時点
Bill 32 HongKong
Ken 20 NewYork
ポインタ
ポインタ
NULL
⑥の時点
41
動的メモリ管理のメリット
• 必要な分だけのメモリを、好きなときに得られ
る
1. 「リスト」は,必要に応じて,大きくなったり小さ
くなったりする
2. 配列は,あらかじめサイズが決まっていて,サイズ
を超えるデータは入らない
42
課題3.住所録
• 例題3のプログラムについて,住所録の表
示を関数の再帰呼び出しによって行うよう
に書き換えなさい.
43
課題3のヒント
表示関数
関数呼び出し
表示関数
表示関数
return;
関数呼び出し
return;
要素の表示
要素の表示
要素の表示
1番目の要素の表示
2番目の要素の表示
末尾の要素の表示
44