第10回:構造体 (12/1)


第9回演習:解答例


今週は,構造体を学びます.

以前学んだ配列は,同じ型のデータを多数まとめて扱う手段であった.
では,異なる型をまとめて扱う方法はないでしょうか.
たとえば,レポート提出状況をチェックするえんま帳を例にとると,

学年 番号 氏名 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 というキーワードは,「構造体」という意味である.

構造体の中身の変数(ここではgakunenkumiなど)の要素一つ一つのことを,構造体のメンバと呼ぶ.

これで,新しい変数の「型」であるseiseki型が定義できた.


構造体の使い方

(1)まず,定義した構造体型の変数を使うための方法. 比較のために,intfloat型の変数宣言も書いておく.


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関数.  */
}
 


構造体を関数へ渡す (1)値参照

以上は,構造体の書式と基本的な使い方でした.いよいよここからが本題.

まず,構造体を関数に渡してみよう.
関数に変数を渡すには,値参照とアドレス参照とがあるが,まずは値参照を使ってみよう.


#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 の値」 を取り出したいので,同様に括弧が必要です.(無いとコンパイルエラー.)


構造体を関数へ渡す (2)アドレス参照

配列,構造体へのポインタが理解できたら,関数に構造体変数をアドレス参照で渡してみましょう.

アドレス参照:配列を渡す例


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システムを使って提出すること.
授業終了時に送るのは出席の確認用であり,完成した課題は提出用の回に送ること.
(提出期限を厳守し,提出用の回に提出しないと採点を行わない)