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日」なら,2
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