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