納得C言語
[第18回]演習問題Ⅴ
これまで学んだきたことをフルに使って総合演習を行いましょう。
次の工程を経て、成績表を作ってみよう
1 成績表の構造体を作る
(1)学籍番号、氏名、学年、クラス、点数をメンバに持つ構造体を作ります。
なお、点数も構造体とし、英・国・数・理・社の点数をメンバに持ちます。
構造体のイメージにすると下のような図になります。
ここに以下のメンバの点数を初期化で格納し、表示させるプログラムを書いてください。
結果 こんな感じになればOKです
演習問題Ⅴ 第1問のヒント
1 成績表の構造体を作る
この問題は第15回の演習問題(1)と同じようなプログラムです。
やったことを思い出しながら書いてみよう。
もし忘れてしまったら第15回の演習問題(1)を確認しなおそう。
演習問題Ⅴ 第1問の解答
1 成績表の構造体を作る
解答プログラム
#include <stdio.h> //構造体型宣言科目の点数 struct point{ int ei; //英語 int koku; //国語 int suu; //数学 int ri; //理科 int sya; //社会 }; //構造体型宣言生徒の情報 struct student{ int s_no; //学籍番号 char name[256]; //名前 int s_year; //学年 char s_class[256]; //クラス struct point s_point; //科目の点数(構造体) }; int main(){ int i=1; //構造体の宣言と初期化 struct student school[] = { {1, "伊藤博文", 3, "A", {88, 66, 84, 67, 74}}, {2, "墨田清隆", 3, "A", {88, 52, 74, 69, 60}}, {3, "山縣有朋", 3, "B", {90, 51, 80, 56, 83}}, {4, "松方正義", 3, "B", {53, 81, 64, 87, 69}}, {5, "大隈重信", 3, "C", {67, 94, 89, 66, 54}}, {6, "桂太郎", 3, "C", {95, 88, 59, 99, 77}}, {7, "西園寺公望", 2, "A", {87, 98, 94, 86, 83}}, {8, "山本権兵衛", 2, "A", {86, 64, 61, 60, 86}}, {9, "寺内正毅", 2, "A", {87, 81, 75, 73, 95}}, {10, "原敬", 2, "B", {97, 93, 73, 96, 81}}, {11, "高橋是清", 2, "B", {57, 91, 80, 68, 95}}, {12, "加藤友三郎", 2, "C", {61, 97, 75, 73, 88}}, {13, "清浦奎吾", 2, "C", {88, 83, 85, 63, 81}}, {14, "加藤高明", 2, "C", {52, 69, 62, 69, 50}}, {15, "若槻禮次郎", 2, "C", {61, 78, 56, 75, 55}}, {16, "田中義一", 1, "A", {78, 91, 70, 98, 54}}, {17, "濱口雄幸", 1, "B", {97, 91, 77, 63, 89}}, {18, "犬飼毅", 1, "B", {66, 52, 88, 61, 95}}, {19, "斎藤寛", 1, "C", {79, 95, 66, 59, 100}}, {20, "岡田啓介", 1, "C", {83, 50, 99, 89, 94}}, {0, " ",0, " ", {0, 0, 0, 0, 0}} }; //構造体へのポインタを宣言 struct student *p; //ポインタへ先頭アドレスを代入 p=school; printf(" | 点数\n"); printf("学籍番号 氏名 学年クラス | 英語 国語 数学 理科 社会\n"); printf("-------------------------------------------------------------------\n"); //学籍番号が0になるまで続ける for(i=1;((p+i)->s_no)!=0;i++){ //printfで1行で書こうとすると横に長くなってしまい、わかりにくくなります。 //なので、このように複数行に分けるとわかりやすいです。 printf("%7d %10s %d %s | %d %d %d %d %d \n", (p+i)->s_no, (p+i)->name, (p+i)->s_year, (p+i)->s_class, (p+i)->s_point.ei, (p+i)->s_point.koku, (p+i)->s_point.suu, (p+i)->s_point.ri, (p+i)->s_point.sya ); } return 0; }
解説
まず、点数を格納する構造体の型を宣言します。
次に、生徒情報を格納する構造体の型を宣言します。このとき、点数の構造体の型宣言を先に行わないとエラーとなってしまいますね。
それは、生徒情報の構造体の型宣言で宣言されていない型があるとどんな型なのかわからないからです。
次に、構造体の宣言と初期化を行います。ここは構造体の回でもやりましたね。
あとは表示させるだけです。
演習問題Ⅴ 第2問
これまで学んだきたことをフルに使って総合演習を行いましょう。
2 機能別に振り分ける
(1)のプログラムに表示機能と入力機能とに分けてメニューから番号を振り分けましょう。
以下の仕様通りに作ってみよう。
・メニュー選択で0を選択するとプログラムが終了する。
・入力機能は1回の選択で複数のデータを入力でき、学生番号に0を入力したら入力を終了させメニュー選択に戻る。
データ入力時
データ表示時
演習問題Ⅴ 第2問のヒント
2 機能別に振り分ける
1.表示機能と入力機能はそれぞれ自作関数に、メニューはmain関数に分けてみよう
(以後の問題で新しい機能を追加する時は自作関数を作っていきます)。
2.構造体の宣言はmain関数で行い、各々の自作関数には構造体のポインタを参照渡しで自作関数に渡します。
演習問題Ⅴ 第2問の解答
2 機能別に振り分ける
解答プログラム
#include <stdio.h> //構造体の型宣言 struct point{ int ei; int koku; int suu; int ri; int sya; }; struct student{ int s_no; char name[256]; int s_year; char s_class[256]; struct point s_point; }; //プロトタイプ宣言の初期化 void show(struct student *p); void input(struct student *p); int main(){ int sw=1; //switch用変数 //構造体の宣言 //ユーザより入力するので大きめの配列を確保する struct student school[256]; //構造体ポインタを宣言し先頭アドレスを代入 struct student *p; p=school; printf("************成績表(プロトタイプG)************\n"); //0が入力されたら終了 while(sw!=0){ printf("何がしたいわけ!?\n"); printf("1:成績表示\n2:データ入力\n0:終了\n>>"); scanf("%d",&sw); //swの値で行動を変化 switch(sw){ //成績表表示 case 1: show(p); break; //データ入力 case 2: input(p); break; //終了 case 0: printf("入力を終了します\n"); break; // それ以外 default: printf("指定数以外の入力です\n"); break; } } return 0; } /******************************************/ /* void show() */ /* 構造体に格納された成績表を表示する */ /* 引数1:構造体へのポインタ */ /* 戻り値:なし */ /******************************************/ void show(struct student *p){ int i = 0; printf(" | 点数\n"); printf("学籍番号 氏名 学年クラス | 英語 国語 数学 理科 社会\n"); printf("-------------------------------------------------------------------\n"); //forで回すところは(1)と同じですね for(i=0;((p+i)->s_no)!=0;i++){ printf("%7d %10s %d %s | %d %d %d %d %d \n", (p+i)->s_no,(p+i)->name, (p+i)->s_year,(p+i)->s_class, (p+i)->s_point.ei, (p+i)->s_point.koku, (p+i)->s_point.suu, (p+i)->s_point.ri, (p+i)->s_point.sya ); } } /*****************************************/ /* void input() */ /* 構造体へ生徒のデータを格納 */ /* 引数1:構造体へのポインタ */ /* 戻り値:なし */ /*****************************************/ void input(struct student *p){ int i = 0; //ポインタカウント用 // 無限ループを作る while(1){ printf("%d人目の入力\n",i+1); printf("学籍番号="); scanf("%d",&(p+i)->s_no); //学籍番号に0が入力されたら終了 if(((p+i)->s_no)==0){ printf("入力を終了します\n"); break; } printf("氏名="); scanf("%s",(p+i)->name); printf("学年="); scanf("%d",&(p+i)->s_year); printf("クラス="); scanf("%d",(p+i)->s_class); printf("点数\n\n"); printf("英語:"); scanf("%d",&(p+i)->s_point.ei); printf("国語:"); scanf("%d",&(p+i)->s_point.koku); printf("数学:"); scanf("%d",&(p+i)->s_point.suu); printf("理科:"); scanf("%d",&(p+i)->s_point.ri); printf("社会:"); scanf("%d",&(p+i)->s_point.sya); //カウントを1足す i++; } }
解説
表示部分とデータ入力部分を関数に分けて行います。
関数へは構造体のポインタを渡すことでメモリの消費を抑えることができます。
メニューで分けるにはswitchで振り分けましたね。
show関数は(1)の表示部分を関数にしただけですね。
input関数は0が入力されるまでscanfで読み込み構造体に書き込みます。
ここで&をつけ忘れやすいので注意してください。
構造体の場所はポインタで表していますが、メンバ変数はポインタではないので&をつけています。
また、whileで無限ループを作る場合、iは手動で1足していく必要があります。
これを忘れると同じ場所に上書きしてしまうので注意です。
演習問題Ⅴ 第3問
これまで学んだきたことをフルに使って総合演習を行いましょう。
3 データ保存機能の追加
(2)のプログラムに入力したデータを保存する機能を付けましょう。
結果
演習問題Ⅴ 第3問のヒント
3 データ保存機能の追加
1.入力した構造体のデータを保存する時はファイル入出力のfopen( ), fprintf( ), fclose( )を使います
2.ファイルを開く処理(fopen( ))のところは必ずエラー処理も一緒に記述しましょう。
3.何も値が入っていない構造体は整数型の値は0、文字列の値は何も入っていないのを利用してファイルへの書き込みの終了条件にしてください。
演習問題Ⅴ 第3問の解答
3 データ保存機能の追加
解答プログラム
#include <stdio.h> #include <stdlib.h> //exit()で使用 //構造体の型宣言 struct point{ int ei; int koku; int suu; int ri; int sya; }; struct student{ int s_no; char name[256]; int s_year; char s_class[256]; struct point s_point; }; //プロトタイプ宣言の初期化 void show(struct student *p); void input(struct student *p); void file_out(struct student *p); int main(){ int sw=1; //switch用変数 //構造体の宣言 //ユーザより入力するので大きめの配列を確保する struct student school[256]; //構造体ポインタを宣言し先頭アドレスを代入 struct student *p; p=school; printf("************成績表(プロトタイプG)************\n"); //0が入力されたら終了 while(sw!=0){ printf("何がしたいわけ!?\n"); printf("1:成績表示\n2:データ入力\n3:ファイルに書き出し\n0:終了\n>>"); scanf("%d",&sw); //swの値で行動を変化 switch(sw){ //成績表表示 case 1: show(p); break; //データ入力 case 2: input(p); break; //データをファイルへ出力 case 3: file_out(p); break; // 終了 case 0: printf("入力を終了します\n"); break; // それ以外 default: printf("指定数以外の入力です\n"); break; } } return 0; } : 省略 : /******************************/ /* void file_out() */ /* ファイルへ保存 */ /* 引数1:構造体へのポインタ */ /* 戻り値:なし */ /******************************/ void file_out(struct student *p){ //ファイル構造体へのポインタを取得 FILE *fp; int i = 0; //書き込みモードでオープン // 今回は自作関数内で開くので //失敗した場合は強制終了 if(!(fp=fopen("成績表.txt","w"))){ printf(" ファイルオープンエラー!!\n"); exit(1); } //学籍番号がになるまで繰り返す while(((p+i)->s_no)!=0){ //fprintf(ファイルポインタ, 格納する型, 格納するデータ fprintf(fp,"%d %s %d %s %d %d %d %d %d\n", (p+i)->s_no, (p+i)->name, (p+i)->s_year, (p+i)->s_class, (p+i)->s_point.ei, (p+i)->s_point.koku, (p+i)->s_point.suu, (p+i)->s_point.ri, (p+i)->s_point.sya ); i++; } //ファイルクローズ fclose(fp); printf("ファイル保存完了\n\n"); }
解説
(3)以降は(2)と同じなので、すでに作ってある自作関数を省略します。
ファイル構造体へのポインタを宣言し、書込みモードで開きます。
失敗した場合、exit(1)で強制終了させてしまいます。
次に、whileで学籍番号((p+i)->s_no)が0になるまでfprintfでファイルに格納していきます。
(2)と同様にここでもiを1足すのを忘れないようにしましょう。
演習問題Ⅴ 第4問
これまで学んだきたことをフルに使って総合演習を行いましょう。
4 データ読込機能の追加
(2)で保存したデータを読み込む機能を付けましょう。
結果
演習問題Ⅴ 第4問のヒント
4 データ読込機能の追加
1.入力した構造体のデータを読み込む時はファイル入出力のfopen( ), fscanf( ), fclose( )を使います。
2.何も値が入っていない構造体は整数型の値は0、文字列の値は何も入っていないのを利用してファイルへの書き込みの終了条件にしてください。ただし、判定する場所に気をつけてください。
3.(2)の機能と合わせるために読み込んだ後の構造体に格納されている生徒数をカウントする必要があります。カウントの方法は各自が最適だと思った方法を使いましょう。
演習問題Ⅴ 第4問の解答
4 データ読込機能の追加
解答プログラム
#include <stdio.h> #include <stdlib.h> //exit()で使用 //構造体の型宣言 struct point{ int ei; int koku; int suu; int ri; int sya; }; struct student{ int s_no; char name[256]; int s_year; char s_class[256]; struct point s_point; }; //プロトタイプ宣言の初期化 void show(struct student *p); void input(struct student *p); void file_out(struct student *p); void file_in(struct student *p); int main(){ int sw=1; //switch用変数 // 構造体の宣言 //ユーザより入力するので大きめの配列を確保する struct student school[256]; //構造体ポインタを宣言し先頭アドレスを代入 struct student *p; p=school; printf("************成績表(プロトタイプG)************\n"); //0が入力されたら終了 while(sw!=0){ printf("何がしたいわけ!?\n"); printf("1:成績表示\n2:データ入力\n3:ファイルに書き出し\n4:ファイルの読み込み\n0:終了 \n>>"); scanf("%d",&sw); //swの値で行動を変化 switch(sw){ //成績表表示 case 1: show(p); break; //データ入力 case 2: input(p); break; //データをファイルへ出力 case 3: file_out(p); break; // データをファイルから入力 case 4: file_in(p); break; // 終了 case 0: printf("入力を終了します\n"); break; // それ以外 default: printf("指定数以外の入力です\n"); break; } } return 0; } : 省略 : /******************************/ /* void file_in() */ /* ファイルからデータ読出 */ /* 引数1:構造体へのポインタ */ /* 戻り値:なし */ /******************************/ void file_in(struct student *p){ //ファイル構造体へのポインタ FILE *fp; char str[256]; int i=0; //読み込みモードでファイルを開く //開けなかったらエラーを表示し終了 if(!(fp=fopen("成績表.txt","r"))){ printf(" ファイルオープンエラー!!\n"); exit(1); } //fscanfで1行ずつ読み込んで構造体に格納していく //ファイルの終わり(EOF)に達したらwhileから抜ける while((fscanf(fp,"%d %s %d %s %d %d %d %d %d", &(p+i)->s_no, (p+i)->name, &(p+i)->s_year, (p+i)->s_class, &(p+i)->s_point.ei, &(p+i)->s_point.koku, &(p+i)->s_point.suu, &(p+i)->s_point.ri, &(p+i)->s_point.sya))!=EOF){ i++; } //ファイルを閉じる fclose(fp); printf("ファイル保存完了\n\n"); }
解説
こちらも(3)同様その他の自作関数を省略します。
ファイルからの読み込み時も同様にファイル構造体へのポインタを宣言し、今度は読み込みモードでファイルを開きます。
whileで1行ずつ読み込んで、それを構造体に格納していきます。
そしてファイルの終端に達したらwhileから抜けます。
fscanfはこのように一度にいくつかのデータを読み出すことができます。
scanfも同様で今までは1つのデータしか入力してきませんでしたが、いくつかのデータを読み込むこともできます。
最後にファイルを閉じるのを忘れないでくださいね。
演習問題Ⅴ 第5問
これまで学んだきたことをフルに使って総合演習を行いましょう。
5 平均点表示機能
それぞれの学年の総合得点の平均値を表示する機能を付けましょう。
結果
演習問題Ⅴ 第5問のヒント
5 平均点表示機能
学年ごとの平均を求めるには、全ての学年を抜き出す必要があります。
抜き出した学年を基に平均を出していきます。
抜き出す方法は各自出来る方法を用いるといいでしょう。
さらにどの学年に何人いるかというのも出しておく必要があります。
似たようなことを第15回の演習問題(3)で行っています。参考にしてみるといいでしょう。
演習問題Ⅴ 第5問の解答
5 平均値表示機能
解答プログラム
#include <stdio.h> #include <stdlib.h> //exit()で使用 //構造体の型宣言 struct point{ int ei; int koku; int suu; int ri; int sya; }; struct student{ int s_no; char name[256]; int s_year; char s_class[256]; struct point s_point; }; //プロトタイプ宣言の初期化 void show(struct student *p); void input(struct student *p); void file_out(struct student *p); void file_in(struct student *p); void avg_point(struct student *p); int main(){ int sw=1; //switch用変数 //構造体の宣言 //ユーザより入力するので大きめの配列を確保する struct student school[256]; //構造体ポインタを宣言し先頭アドレスを代入 struct student *p; p=school; printf("************成績表(プロトタイプG)************\n"); //0が入力されたら終了 while(sw!=0){ printf("何がしたいわけ!?\n"); printf("1:成績表示\n2:データ入力\n3:ファイルに書き出し\n4:ファイルの読み込み\n5:各学年ごとの平均点表示\n0:終了\n>>"); scanf("%d",&sw); //swの値で行動を変化 switch(sw){ //成績表表示 case 1: show(p); break; //データ入力 case 2: input(p); break; //データをファイルへ出力 case 3: file_out(p); break; // データをファイルから入力 case 4: file_in(p); break; // 学年ごとの平均値を求める case 5: avg_point(p); break; // 終了 case 0: printf("入力を終了します\n"); break; // それ以外 default: printf("指定数以外の入力です\n"); break; } } return 0; } : 省略 : /******************************/ /* void avg_point() */ /* 学年ごとの平均値の算出 */ /* 引数1:構造体へのポインタ */ /* 戻り値:なし */ /******************************/ void avg_point(struct student *p){ int i = 0; //ポインタカウント用 int sum = 0; //合計点 int j = 0; //人数カウント用 int k = 0; //平均値 int a = 0; //学年格納用 //aに先頭の学年を格納する a = p->s_year; printf("各学年ごとの平均\n"); //学籍番号が0になるまで続ける while(((p+i)->s_year)!=0){ //学籍番号がaと同じ間続ける while(((p+i)->s_year)==a){ //sumにsum自身と科目の合計を代入 sum = sum + (p+i)->s_point.ei + (p+i)->s_point.koku + (p+i)->s_point.ri + (p+i)->s_point.suu + (p+i)->s_point.sya; //ポインタカウントを進める i++; //人数をカウントする j++; } //平均値算出 k = sum/j; //平均点表示 printf("学年=%d 総合得点の平均=%d\n", a, k); //各変数の初期化 sum = 0; j = 0; a = (p+i)->s_year; } }
解説
こちらも問題になっている自作関数以外の関数は省略します。
平均値の算出方法は第15回の演習問題でやりましたね。違うところは学年と役職で、学年はint型、役職はchar型(文字列)ぐらいでしょうか。また、今回問われているのは「総合得点の平均値」なので平均値を算出するときはj(人数)だけとなります。
k = sum/(j*5)
と書いてしまうかもしれませんが、これでは1科目の平均値となるので違ってしまいますね。
あとは表示させて、学籍番号が0になったら終了となります。
あとがき
ここまでやり遂げた皆さん、お疲れ様でした。
今回で「納得C言語」の連載は終了となります。
連載で学習してきたC言語の知識はC言語全体のほんの一部に過ぎません。
この先には今よりさらに高い壁が立ちはだかっております。
ここまで学習してきた皆さんなら、次のステップに進んでも問題ないでしょう。
あとはアナタの努力次第!!!
最後に...
今まで、「納得C言語」を学習して頂き本当にありがとうございました。
またどこかでお会いしましょう。
コメントの投稿
トラックバックURL
http://www.isl.ne.jp/cgi-bin/mt/mt-tb.cgi/1085