今週は,構造体を学びます.
以前学んだ配列は,同じ型のデータを多数まとめて扱う手段であった.
では,異なる型をまとめて扱う方法はないでしょうか.
たとえば,レポート提出状況をチェックするえんま帳を例にとると,
学年 | 組 | 番号 | 氏名 | 1回目点数 | 2回目点数 | 平均点 | 判定 |
2 | 1 | 1 | アベ | 55 | 70 | 62.5 | C |
2 | 1 | 2 | イトウ | 100 | 90 | 95.0 | A |
データは,学年,組,番号,点数が「整数型」,氏名が「文字列型」,平均点が「実数型」,判定が「文字型」となっており,ひとつの変数(配列)では取り扱うことができなさそうです.
今日の演習で学ぶ「構造体」は, 異なる型のデータをまとめて一つの変数として扱うための手法である.
構造体は,新しい変数の「型」を自分で作り出す役割をする.
構造体を利用するには,まず既存の変数を組み合わせて,新しい「型」の構造体を定義する.
#include <stdio.h>
struct seiseki {
int gakunen;
int kumi;
int bangou;
char simei[25];
int ten_1;
int ten_2;
float heikin;
char hantei;
};
void main()
{
....
}
構造体の定義部分(赤色の箇所)記述は,関数の内側にも外側にも記述することができる.
関数の中で宣言すれば,その関数内のみで有効になる.(ローカル変数やグローバル変数と同様.)
以降,何度も登場する struct
というキーワードは,「構造体」という意味である.
構造体の中身の変数(ここではgakunen
やkumi
など)の要素一つ一つのことを,構造体のメンバと呼ぶ.
これで,新しい変数の「型」であるseiseki
型が定義できた.
(1)まず,定義した構造体型の変数を使うための方法.
比較のために,int
やfloat
型の変数宣言も書いておく.
int i; /* 整数型の変数,i */
float r; /* 実数型の変数, r */
struct seiseki list2009; /* 新しく定義した seiseki 型の変数,list2009 */
上で定義したseiseki
型が構造体であることを示すために,struct
キーワードを必ず記する.
これで,成績データを格納する変数 list2009
が,使えるようになった.
(2)次に,構造体のメンバに値を代入する方法と,値を参照する方法をマスターしよう.
(2-a)代入する方法.
基本的には,構造体変数名.メンバ変数名 のように,構造体変数の名前と,メンバの変数の名前を .(ピリオド,ドット)で結ぶ.
変数の名前がlist2009
で,メンバ名がgakunen, kumi, ...
なので,
list2009.gakunen = 2;
list2009.kumi = 7;
list2009.bangou = 1;
strcpy(list2009.simei, "メイジ"); /* 文字列の代入.下の注を参照! */
list2009.ten_1 = 90;
list2009.ten_2 = 80;
また,変数の定義と同時に初期化するときは,配列の場合と同様に
struct seiseki list2009 = {2, 7, 1, "メイジ", 90, 80};
と,書くこともできる.
(2-b)次に,構造体変数の各メンバの参照方法.これは単純に
printf("番号 = %d 氏名 = %s ¥n", list2009.bangou, list2009.simei);
とすることで,値を取り出すことができる.
文字列は,文字型の「配列」なので,
char name[25];
name = "meiji"
これは,エラーになる.では,どうやって文字列に代入するかというと,
char name[20];
name[0] = 'm';
name[1] = 'e';
....
が,正しい手順であるが,文字数が増えると大変である.
そこで,C言語では文字列の代入をする関数が用意されている.
#include <stdio.h>
#include <string.h> /* 文字列操作関数用のヘッダファイル */
void main()
{
char name[20];
strcpy(name, "meiji"); /* string copy関数. */
}
以上は,構造体の書式と基本的な使い方でした.いよいよここからが本題.
まず,構造体を関数に渡してみよう.
関数に変数を渡すには,値参照とアドレス参照とがあるが,まずは値参照を使ってみよう.
#include <stdio.h>
struct seiseki {
int gakunen;
int kumi;
int bangou;
char simei[25];
int ten_1;
int ten_2;
float heikin;
char hantei;
};
/* 構造体のメンバをすべて表示する関数 */
void disp_all(struct seiseki list)
{
printf("学年=%d¥n", list.gakunen);
printf(" 組=%d¥n", list.kumi);
printf("番号=%d¥n", list.bangou);
printf("氏名=%s¥n", list.simei);
printf("点数1=%d¥n", list.ten_1);
printf("点数2=%d¥n", list.ten_2);
}
void main()
{
struct seiseki list2009 = {2, 7, 1, "メイジ", 90, 80};
disp_all(list2009); /* 関数に構造体を渡す */
}
構造体であることを示す struct
を記述すること以外は,通常の値参照による関数呼び出しと同じです.
成績リストの例では,構造体1つに1人分の名前・成績データなどを格納できた.
これを用いて,全員分の名前や点数を一つの変数に保存するために,構造体を配列にすればよさそうである.
構造体変数も,通常の変数同様,配列を宣言することができる.
struct seiseki list2009all[100];
この例では,struct seiseki
型の変数を100人分,配列名を list2009all
にする,という意味である.
構造体の配列の各要素の,メンバへの代入は,例えば配列の先頭の構造体へは
list2009all[0].gakunen = 2;
list2009all[0].kumi = 7;
list2009all[0].bangou = 1;
strcpy(list2009all[0].simei, "メイジ"); /* 文字列の代入. */
list2009all[0].ten_1 = 90;
list2009all[0].ten_2 = 80;
とすることができる.配列の添え字用 [ ] と,構造体用のピリオド「.」の順序に注意!
普通の配列同様,添え字を変化させることにより,構造体の配列すべてのメンバを操作することができる.
配列の宣言と同時に,初期化する場合に限り,
struct seiseki list2009all[] = {
{2,1,1, "イクタ",10,20},
{2,1,2, "スルガダイ",5,10},
};
という記述が可能である.
通常の変数と同様,構造体変数へのポインタも宣言できる.
struct seiseki list2009;
struct seiseki *p; /* ポインタ変数 */
p = &list2009; /* list2009 のアドレスを p に代入. */
printf("%s ¥n", (*p).simei); /* 演算子の優先順位より,要括弧 */
最終行,printf
の行では,
「ポインタ p
の指す構造体の値 *p
」の
「メンバ変数 simei
」
を取り出したいので,演算子 *
と演算子 .
の優先順位から,*p
全体に括弧が必要です.(括弧が無いとコンパイルエラー.)
配列の場合も同様です.
struct seiseki list2009all[] = {
{2,1,1, "イクタ",10,20},
{2,1,2, "スルガダイ",5,10},
};
struct seiseki *p;
p = list2009; /* 配列 list2009all の先頭アドレスを p に代入.p=&list2009all[0] と同じ意味. */
printf("%s ¥n", (*p).simei); /* 配列先頭のsimei,「イクタ」と表示される. */
printf("%s ¥n", (*(p+1)).simei); /* 配列2番目のsimei,「スルガダイ」と表示される. */
この場合も,2つ目の printf
の行では,
「配列の先頭アドレス p
に 1 を足したアドレス p+1
(=配列の先頭の次のアドレス)」の
「構造体変数の値 *(p+1)
」の
「メンバ変数 simei
の値」
を取り出したいので,同様に括弧が必要です.(無いとコンパイルエラー.)
配列,構造体へのポインタが理解できたら,関数に構造体変数をアドレス参照で渡してみましょう.
アドレス参照:配列を渡す例
void disp_name_all(struct seiseki *p, int n) /* n は,配列の要素数 */
{
int i;
for(i=0; i<n; i++) {
printf("番号 %d の氏名は,%s ¥n", (*(p+i)).bangou, (*(p+i)).simei);
/* printf("番号 %d の氏名は,%s ¥n", p[i].bangou, p[i].simei); */ /* これも可 */
}
}
void main()
{
struct seiseki list2009all[] = {
{2,1,1, "イクタ",10,20,},
{2,1,2, "スルガダイ",5,10},
};
disp_name_all(list2009all, 2);
}
これが充分に理解できれば,ここまでの関数,配列,ポインタ,構造体の箇所は完璧です.
よくわからないところがある場合は,関連する演習をよく復習しておきましょう.
(1)次に示す生徒の情報を構造体として作成し,内容を関数(値参照)を用いて表示しなさい.
番号 | 氏名 | 点数 | 成績 |
1 | 石山 | 82 | A |
(2)次に示す複数の生徒の情報を構造体として作成し,内容を関数(アドレス参照)を用いて表示しなさい.
番号 | 氏名 | 点数 | 成績 |
1 | 石山 | 82 | A |
2 | 松村 | 48 | F |
3 | 中島 | 98 | S |
(3)次に示す複数の生徒の情報を構造体として作成し,平均値と成績を求めるプログラムを作成せよ.
*main文に表に示す初期条件を入れること*
番号 | 氏名 | 第1回 | 第2回 | 第3回 | 第4回 | 平均 | 成績 |
1 | 石山 | 88 | 41 | 100 | 90 | 0.0 | ? |
2 | 大場 | 78 | 65 | 70 | 62 | 0.0 | ? |
3 | 河合 | 89 | 92 | 88 | 76 | 0.0 | ? |
4 | 松村 | 30 | 48 | 69 | 22 | 0.0 | ? |
5 | 中島 | 98 | 98 | 96 | 92 | 0.0 | ? |
成績判定には,以下の表を利用しなさい.
点数 | 成績 |
90点以上 | S |
80点以上90点未満 | A |
70点以上80点未満 | B |
60点以上70点未満 | C |
60点未満 | F |
main文は以下を参考にすること.
void main()
{
struct seiseki seito[5] = {{1,"石山",88,41,100,90,0.0,'?'},
{2,"大場",78,65,70,62,0.0,'?'},
{3,"河合",89,92,88,76,0.0,'?'},
{4,"松村",30,48,69,22,0.0,'?'},
{5,"中島",98,98,96,92,0.0,'?'}};
disp_all(seito,5); /* 生徒の情報を画面に表示する関数 */
ave(seito,5); /* 平均を求める関数 */
seiseki(seito,5); /* 成績を出す関数 */
disp_all(seito,5); /* 生徒の情報を画面に表示する関数 */
}
(4)(3)で表示されている生徒の情報をファイル(seiseki.csv)に保存するプログラムを作成せよ.
授業終了時までのプログラムと完成した提出用プログラムをoh-meijiシステムを使って提出すること.
授業終了時に送るのは出席の確認用であり,完成した課題は提出用の回に送ること.
(提出期限を厳守し,提出用の回に提出しないと採点を行わない)