プログラミングC
第7回 演習問題


A問題(50点)

演習問題1(構造体の基礎を学ぶ)

以下の問題を読み、prog01a.c, prog01.h, prog01b.cの3つのファイルを提出しなさい。
  1. 以下に示す構造体 record を宣言し、 main 関数中で、record 型構造体変数 data を定義する(外部変数にしないこと)。
    そして、標準入力(キーボード)からデータを入力すると、変数 data のメンバに入力値を格納し、 変数 data のメンバの内容を下記実行例のように出力するプログラムを作成せよ。 (提出ファイル名 prog01a.c)。
    ※また、この時点ではヘッダファイルは使用しないこと。
    /* 個人レコード */
    struct record{
    	char familyname[20]; /* 苗字 */
    	char firstname[20];  /* 名前 */
    	int birthday[3];     /* 誕生日(要素0: 西暦, 要素1: 月, 要素2:日) */
    };
    
    struct record data; /* この1行は main関数に書いて外部変数にはしないこと */
    
    実行例
    % ./a.out
    データを入力して下さい
    苗字 -> Smith
    名前 -> Olivia
    生まれた年(西暦) -> 1988
    生まれた月 -> 2
    生まれた日 -> 24
    
    苗字: Smith
    名前: Olivia
    生年月日: 1988/2/24
    
  2. 上述の構造体 record 宣言部分を typedef を利用して、 以下のように書き直したものを prog01.h として保存する。
    そして、prog01a.c を、 prog01.h を使用した形に書き換えた prog01b.c を作成せよ。 (提出ファイル名 prog01.h, prog01b.c
    /* 個人レコード */
    typedef struct {
        char familyname[20]; /* 苗字 */
        char firstname[20];  /* 名前 */
        int birthday[3];     /* 誕生日(要素0: 西暦, 要素1: 月, 要素2:日) */
    }Record;
    

B問題(50点)

演習問題2(関数の引数・戻り値として構造体を使用する方法を学ぶ)

以下の問題を読み、prog02a.c, prog02b.c, prog02c.cの3つのファイルを提出しなさい。
  1. 演習問題1-2(prog01b.c)を 以下の3つの関数に分割したプログラムを作成せよ(提出ファイル名 prog02a.c)。 前問で作成したヘッダファイル prog01.h はそのまま使用すること。
    • Record input(void) : 標準入力(キーボード)から構造体への入力
    • void output(Record) : 構造体内容の標準出力への出力(ディスプレイへの表示)
    • int main() : 構造体変数 data を定義する。 inputoutput をそれぞれ1回ずつよぶ
  2. 上記プログラムを更に修正し(main関数の修正、および関数int getAge(Record) の追加)、 2人分のデータに対応するようにする。
    構造体変数 data を要素数2の構造体配列に変更し、 前問で作成したprog02a.cの関数 inputoutputを使用して、 2人分のデータの標準入力からの入力と標準出力への出力を行う (inputoutput 関数には手を加えないこと)。
    さらに、1個の構造体要素を引数として、基準日の時点での満年齢を求める関数 int getAge(Record) を作成し、この関数を mainからよび、それぞれの人の年齢も表示するようにせよ。
    ただし、基準日は2022年10月1日とし、マクロで与えるものとする。(提出ファイル名 prog02b.c

    年齢を求める考え方:
    例えば、2002年生まれの人は、2022年には満20歳か19歳のどちらかである。
    このことから、基準日が誕生日より前であれば、(西暦年の差-1) 歳、基準日が誕生日以降であれば、(西暦年の差) 歳として考えることができる。
    よって,西暦年の差を計算し、あとは基準日と誕生日の月日を比較して、必要なら1歳調整すれば良いことがわかる。

    実行例
    % ./a.out
    --------データ入力--------
    1人目の データを入力して下さい
    苗字 -> Johnson
    名前 -> Liam
    生まれた年(西暦) -> 1995
    生まれた月 -> 5
    生まれた日 -> 4
    
    2人目の データを入力して下さい
    苗字 -> Williams
    名前 -> Emma
    生まれた年(西暦) -> 1995
    生まれた月 -> 10
    生まれた日 -> 2
    
    --------データ出力--------
    年齢基準日: 2022/10/1
    1人目のデータ:
    苗字: Johnson
    名前: Liam
    生年月日: 1995/5/4 年齢: 27
    
    2人目のデータ:
    苗字: Williams
    名前: Emma
    生年月日: 1995/10/2 年齢: 26
    
    
  3. 前問で作成したプログラム prog02b.c をさらに変更し、 以下に示すファイルmember.txtをリダイレクションで構造体配列に読み込み、 実行例のように入力されたデータと基準日時点の満年齢を計算し表示しなさい。 (提出ファイル名 prog02c.c)
    (端末上で,コマンド cp /home/course/prog1/public_html/2022/ex/ex07/member.txt .で、 カレントディレクトリにファイルmember.txtをコピーすることができる)
    Brown      Noah       1999  3   31
    Jones      Ava        1998  4   1
    Garcia     Charlotte  1998  7   4
    Miller     Sophia     1999  10  2
    Davis      Elijah     1998  5   5
    Rodriguez  William    2000  12  1
    Martinez   Amelia     2000  2   4
    Hernandez  Isabella   2000  9   30
    Lopez      Lucas      1998  10  22
    Gonzalez   Henry      1996  1   24
    

    条件およびプログラム作成のヒント

    • 構造体配列の最大サイズは #define N 20 で与える。
    • input()の入力をうながす printf を削除する。
    • 入力の終了は EOF で検出するが、 入力終了を input()関数からmain()側に伝える必要がある。
      その手段として、input()関数で入力終了時には 戻り値構造体のメンバ変数birthdayの最初の要素に -1 を入れるものとする。
      main側では戻り値のbirthdayの最初の要素の値を調べ、-1 なら入力処理を止めればよい
    • getAge()output()からよぶように変更する。また,以下のプログラム実行例を参考に適宜修正する。

    実行例
    % ./a.out < member.txt 
    --------データ出力--------
    年齢基準日: 2022/10/1
     1人目のデータ: 氏名: Brown      Noah      , 生年月日: 1999/ 3/31, 年齢: 23
     2人目のデータ: 氏名: Jones      Ava       , 生年月日: 1998/ 4/ 1, 年齢: 24
     3人目のデータ: 氏名: Garcia     Charlotte , 生年月日: 1998/ 7/ 4, 年齢: 24
     4人目のデータ: 氏名: Miller     Sophia    , 生年月日: 1999/10/ 2, 年齢: 22
     5人目のデータ: 氏名: Davis      Elijah    , 生年月日: 1998/ 5/ 5, 年齢: 24
     6人目のデータ: 氏名: Rodriguez  William   , 生年月日: 2000/12/ 1, 年齢: 21
     7人目のデータ: 氏名: Martinez   Amelia    , 生年月日: 2000/ 2/ 4, 年齢: 22
     8人目のデータ: 氏名: Hernandez  Isabella  , 生年月日: 2000/ 9/30, 年齢: 22
     9人目のデータ: 氏名: Lopez      Lucas     , 生年月日: 1998/10/22, 年齢: 23
    10人目のデータ: 氏名: Gonzalez   Henry     , 生年月日: 1996/ 1/24, 年齢: 26
    

演習問題3 (構造体と関数の応用を学ぶ)

前準備として、データ一行につき「学籍番号、名前、4つの得点」が書かれている成績ファイル score.txtを自分のディレクトリにコピーしておく。 (端末上で、コマンド cp /home/course/prog1/public_html/2022/ex/ex07/score.txt .で、 カレントディレクトリにファイルscore.txtをコピーすることができる)

79105001  Alexander  87   70   71   92
79105002  Mia        67   77   75   92
79105003  Mason      37   60   71   52
 (中略)
79105014  Ella       81   45   46   75
79105015  Sebastian  29   37   40   20
79105016  Elizabeth  95   97   90   95

以下の説明文を読み、題意を満たすプログラムを作成せよ。(提出ファイル名 prog03.c

  1. 上記のデータをファイルから構造体配列に読み込む。配列の大きさは20とする。
  2. 科目数を表すマクロ SUBJECT_NUM を使用して、 データを格納する構造体の宣言は typedef によって以下のように行う。 (ヘッダファイルとして独立させてはいけない)
  3. #define SUBJECT_NUM 4 /* 科目数 */
    
    typedef struct{
    	char id[10];            /* 学籍番号 */
    	char name[10];          /* 名前 */
    	int score[SUBJECT_NUM]; /* 各点数 */
    	int sum;                /* 合計点 */
    	double ave;             /* 平均 */
    	char grade;             /* 判定 */
    }Record;
    
  4. コマンドラインオプションを使ってデータファイル名を入力する (コマンドラインオプションのプログラムへの渡し方については講義第6回参照)。 ファイル名が無い場合はエラーメッセージを出力して終了する。
  5. 読み込みと同時に4教科の試験の合計点を構造体の要素(メンバー)sum に、 そして、平均点を要素 ave に代入する。 さらに、平均点に対して会津大学の判定基準でA~D,およびFの判定を行い要素grade に代入する。
  6. すべての人のすべてのデータ(4教科の試験の得点、合計点、平均、判定)を表示する。平均点は小数第一位の出力とする。
  7. その後、検索したい名前か番号を標準入力から入力し、リニアサーチを行う。 文字列の一致の判定にはstrcmp関数を使ってよい (最初にstring.hをインクルードしておくこと)。
    見つかった場合は、該当の人のすべてのデータ(4教科の得点、合計点、平均、判定)を表示する。 入力されたのが名前か番号かを区別するのは、最初の文字が英字か数字かを基準に判定すればよい。
  8. 判定基準A~D, Fの判定は、構造体を引数に取る関数として実装すること。
  9. データの表示は、構造体を引数にとる関数として実装すること。 すべての人のすべてのデータを表示するときも、検索結果を表示するときも、この同じ関数を用いること。
  10. 実行例:
    % ./a.out score.txt
    79105001   Alexander   87   70   71   92  320   80.0  A
    79105002   Mia         67   77   75   92  311   77.8  B
    79105003   Mason       37   60   71   52  220   55.0  C
    79105004   Evelyn      79   61   82   70  292   73.0  B
    79105005   Michael     77   60   75   55  267   66.8  B
    79105006   Harper      88   66   75   45  274   68.5  B
    79105007   Ethan       43   79   64   18  204   51.0  C
    79105008   Camila      45   46   75   98  264   66.0  B
    79105009   Jacob       99   96   95   99  389   97.2  A
    79105010   Abigail     40   25   37   27  129   32.2  F
    79105011   Jackson     70   95   96   68  329   82.2  A
    79105012   Luna        34   58   52   45  189   47.2  D
    79105013   Levi        99   69   77   60  305   76.2  B
    79105014   Ella        81   45   46   75  247   61.8  C
    79105015   Sebastian   29   37   40   20  126   31.5  F
    79105016   Elizabeth   95   97   90   95  377   94.2  A
    Input a student name/ID: Ethan
    79105007   Ethan       43   79   64   18  204   51.0  C
    Input a student name/ID: Rosemary
    This student does not exist!
    Input a student name/ID: 79105001
    79105001   Alexander   87   70   71   92  320   80.0  A
    Input a student name/ID: Ctrl-D
    % ./a.out
    Error!  Usage: ./a.out datafilename
    

Extra問題

演習問題4(二次元座標情報を格納する構造体を利用したプログラム)

以下の説明文を読み、題意を満たすプログラムを作成せよ。(提出ファイル名 prog04.c)

  1. 大きさ256×256ドットの画像(PBM形式)を作成し、標準出力へ出力する。 なお、実行時にはパイプにて display コマンドを使って表示する。
  2. 画像は左上を原点とし、右に x 軸、下に y 軸をとる。 初期状態では画像全体が白い画像とする。
  3. コマンドライン引数から円の中心座標 x, y の組を1つ以上入力し、 中心 (x, y)、半径 RD (マクロで64と定義する)の円の内部を白黒反転する。 入力された座標の組の数だけ白黒反転を繰り返す。
  4. 二次元平面の点については x, y 座標をメンバにもつ構造体を定義して使用する。
  5. なお、黒丸が例1のように画像からはみ出す場合も、うまく表示できるようにすること。
  6. 必要に応じて、さらに関数を作成して良い。

pbm画像の作成については、これまでの演習問題やプログラミング入門などを参考にせよ。

必要なら数学関数を使用しても良い。使用方法は前期プログラミング入門の 演習問題等を参照せよ。 その場合、コンパイラのオプションに -lm を与えること。 (コンパイル例: gcc prog04.c -lm

コマンドラインの引数はすべて文字列として得られるが、これを数値(int型) に変換するには、 <stdlib.h>の中の atoi関数 を使用する。 例えば、文字列変数 str に文字列 “123” が入っているとき、 atoi(str) の戻り値は int型の123という値になる。

#include <stdio.h>
#include <stdlib.h>
/* 数学関数を使いたい場合は以下のコメントを外す */
/* #include <math.h> */

/* マクロ定義 */
#define N 256 /* 画像サイズ */
#define RD 64 /* 円盤の半径 */
#define BLACK '1'
#define WHITE '0'

/* 構造体宣言 */
typedef struct{
	int x;
	int y;
}XYdata;

/* 関数のプロトタイプ宣言 */

/* 画像の各ドットを表す外部変数の定義 */
char data[N][N];

int main(int argc, char *argv[]){
	XYdata cent;
	int ncent, i;
	
	/* コマンドラインから円の中心座標を何組か入力される */
	
	/* 入力された中心座標が何組あるか計算 */
	ncent = ???;
	
	/* 画像初期化関数(すべて白くする)をよぶ */
	
	/* 座標の組の数だけ繰り返す */
	
		/* 円の内部を白黒反転する */
		circle(cent);
	
	/* 表示 */
	imgout();
	
	return 0;
}

/* すべての点を白に初期化する関数 */
void init(void){

}

/* 引数で指示された座標の点一つを白黒反転する関数 */
void rev(XYdata a){

}

/* 引数で指示された座標を中心に,半径 RD 以内の点を白黒反転する関数 */
void circle(XYdata c){

}

/* Plain PBM形式で画像データを出力する関数 */
void imgout(void){

}
実行例1: 座標(0, 0)と座標(255, 255)を中心とする円を描く。
% ./a.out 0 0 255 255 | display - &
prog04-3.png

実行例2: 座標(128, 90)と座標(88, 160)と座標(168, 160)を中心とする円を描く。 二つの円が重なる所は白く、三つの円が重なる所は黒くなる。
% ./a.out 128 90 88 160 168 160 | display - &
prog04-4.png

演習問題5(配列をメンバに含む構造体の配列を利用したプログラム)

ファイル bus-table-u.txtは、 ある年の会津地区から新宿方面行きの高速バスの時刻情報を格納している。 このファイルの 1 行目には 2 行目以降に入っているデータの説明(停留所名など)が書かれており、 2 行目以降に各バス便の号数、運行会社、喜多方?新宿の停留所の時刻が入っている。 時刻は時・分を合わせた3-4桁の整数(11時50分なら1150)で表示されており、 バスが停車しない停留所には値 -999 が入っている。

このとき,以下の説明文を読み、題意を満たすプログラムを作成せよ。(提出ファイル名 prog05.c)

  1. 時刻データファイルは、コマンドラインで指定されたものを読み込む
  2. データファイルの 1 行目にある、停留所名などを格納する構造体を宣言しておき、 それを使って 1 行目のデータを読み出す。
  3. バス一便ごとのデータを格納する構造体を宣言しておき、 その構造体の配列を使ってファイルから 2 行目以降のデータもすべて読み込む。 なおデータのうち時刻については、4 桁表記のデータを 時・分それぞれ別のメンバに分けてから構造体に格納する。
  4. 標準入力から停留所名と時間帯(6 時台から 12 時台など。分単位は無視)を入力し、 その停留所に指定した時間帯に停車するバスの時刻情報を表示する(通過する停留所の情報は省略する)。 表示は関数を作成してよび出すことで行う。(時間帯の判定まではmain側で行うとよい)
プログラム例(最初の部分のみ)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define NMAX 20     /* 1日の最大バス本数 */
#define NSTOP 6     /* バス停留所数 */
#define UNDEF -999  /* バスが停車しない時を表すダミー値 */

typedef struct{
	char colstr1[8];        /* カラム1(番号) */
	char colstr2[8];        /* カラム2(会社名) */
	char bsname[NSTOP][12]; /* バスの停留所名 */
}Tableinfo;

typedef struct{
	int number;      /* バス番号 */
	char company[8]; /* 会社名 */
	int hour[NSTOP]; /* 時間 */
	int min[NSTOP];  /* 分 */
}Businfo;

void printInfo(Businfo, Tableinfo); /* 結果表示用関数 */
...
実行例
% ./a.out bus-table-u.txt
Input the name of bus stop.
Wakamatsu
Hours from/to?
7 9

Number: 4 Company: Aizubus
Kitakata      6:50
Wakamatsu     7:30
Oji          11:16
Ikebukuro    11:26
Shinjuku     11:50

Number: 6 Company: JRbus
Wakamatsu     8:30
Inawashiro    9:00
Oji          12:40
Ikebukuro    12:50
Shinjuku     13:14

Number: 8 Company: Aizubus
Wakamatsu     9:30
Oji          13:16
Ikebukuro    13:26
Shinjuku     13:50