応用理工学情報処理 9回目

本日の課題の要約

(1)構造体、およびそのメンバーにアクセスするためのドット演算子(.)とアロー演算子(->)
(2)typedef宣言:既存の型に対して同じように使用できる別名を与える。
(3)ファイル入出力:fopen, fcloseによるファイルのオープン・クローズ
(4)テキストファイル入出力(fprintf, fscanf)
(5)バイナリファイル入出力(fread, fwrite)

===実習課題1:これまでの復習(制限時間:5分)===

以下のプログラムの関数asteriskにコードを書き加えて、プログラムを実行した結果が以下のようになるようにせよ。
<実行結果>
口座番号:****************
<プログラム>

#include  <stdio.h>
void asterisk(char *account)
{
    // ここに書き加える。
} 
int main(void)
{
    char account[] = "1234567890123456";
    asterisk(account);
    printf("口座番号:%s\n", account);
    return (0);
}

===実習課題2:構造体 (制限時間:5分)===

以下のプログラムが内積の結果を表示するように関数sproduct内部にコードを書き加えよ。

#include  <stdio.h>
struct vector {
    double x; /* X成分 */
    double y; /* Y成分 */
    double z; /* Z成分 */
};
double sproduct( struct vector a, struct vector b) 
{ 
    /* ここにコードを書き加える */
} 
int main(void)
{
    struct vector a, b;
    printf("点Aの座標:\n");
	printf("x:");	  scanf("%lf", &a.x);
	printf("y:");	  scanf("%lf", &a.y);
	printf("z:");	  scanf("%lf", &a.z);
    printf("点Bの座標:\n");
	printf("x:");	  scanf("%lf", &b.x);
	printf("y:");	  scanf("%lf", &b.y);
	printf("z:");	  scanf("%lf", &b.z);
    printf("内積は%.2fです。\n", sproduct(a, b));
    return (0);
}

===実習課題3:ファイルからの入力 (制限時間:10分)===

以下のプログラムはx=0からπまでのsin(x)の値をxの間隔π/50でファイルに出力するものである。出力されたファイルから値を読み取って、以下のようにその値を出力するプログラムを作成せよ。
<作成するプログラムの実行結果>
0.00 0.06 0.13 0.19 0.25 0.31 0.37 0.43 0.48 0.54 0.59 0.64 0.68 0.73 0.77 0.81 0.84 0.88 0.90 0.93 0.95 0.97 0.98 0.99 1.00 1.00 1.00 0.99 0.98 0.97 0.95 0.93 0.90 0.88 0.84 0.81 0.77 0.73 0.68 0.64 0.59 0.54 0.48 0.43 0.37 0.31 0.25 0.19 0.13 0.06 0.00

<ファイルへsin(x)を出力するプログラム>

// sin.txtファイルへ出力
#include <stdio.h>
#include <math.h>
#define N 50
#define PI 3.141592
int main(void)
{
    FILE *fp; 
    int i;
    double x = 0.0;

    fp = fopen("sin.txt", "w");
    for (i=0; i<=N; i++)
        fprintf(fp, "%.2f\n", sin(i*PI/N));
    fclose(fp);
    printf("ファイル作成終了。\n");
    return 0;
} // 出力プログラム終了

===実習課題4:画像ファイルの色 (制限時間:5分)===

演習問題9Cの問題で与えてあるプログラムを実行したときの出力画像の色を白黒から青に変更せよ。

 

(a) 構造体 

(1)構造体(structure):ひとまとまりのデータを集めたデータ構造
- 構造体とは、関連性のあるデータをひとつの型としてまとめて扱う場合に便利なものです。

(2)構造体の宣言 最後のセミコロンを忘れずに!構造体の名前studentをタグ名と呼びます。また、name, height, weightをこの構造体のメンバーと呼びます。
struct student {
    char name[20]; /* 名前 */
    int height; /* 身長 */
    double weight; /* 体重 */
};

(3)構造体変数の作成
struct student shibata;
struct student watanabe;

(4)プログラム中で1回しか変数を宣言しないときはタグ名を省略し以下のようにできる。

struct {
    char name[20];
    int height; 
    double weight; 
} shibata;

(5)構造体で用いる演算子
以下のような構造体を仮定する。

struct point {
    double x;
    double y;
    double z;
};
struct point start;
struct point *pointer;

ドット演算子(.演算子)
start.x = 1.0;
start.y = 1.0;
start.z = 1.0;
printf(“(%f, %f, %f)”, start.x, start.y, start.z);

アロー演算子(->演算子)
pointer = &start;
printf(“(%f, %f, %f)”,(*pointer).x, pointer->y, pointer->z);

同じ型の構造体変数は代入可能

struct point {
    double x;
    double y;
    double z;
};
struct point start, end;
start.x = 1.0;
start.y = 1.0;
start.z = 1.0;
end = start; /* 可能 */

ポインターをメンバーとして含む場合はコピーされた参照先が同じとなるので、参照先を書き換えるとコピー元のデータも変化するので注意が必要。

(6)構造体の初期化
struct point end = {1.0, 2.0, 3.0};

(7)typedef宣言
typedef宣言:既存の型に対して型名として用いることのできる同義語を与える。構造体に関してはtypedefで別名を与えることが良く行われる。

typedef unsigned int UINT;
UINT num; /* unsigned int num;と等しい */

typedef struct {double x, y, z;} point;
point start;
/* struct {double x, y, z;} start;と等しい */

「1」構造体のメンバ(.演算子の例、テキストp.312 List12-2から)

/*
	学生を表す構造体で表した佐中君
*/

#include  <stdio.h>
#include  <string.h>

#define NAME_LEN	64

struct student {
	char   name[NAME_LEN];	/* 名前 */
	int    height;		/* 身長 */
	double  weight;		/* 体重 */
	long   schols;		/* 奨学金 */
};

int main(void)
{
	struct student  sanaka;
/*  struct student sanaka = {"Sanaka", 175, 60.5, 70000}; */

	strcpy(sanaka.name, "Sanaka");	/* 名前 */
	sanaka.height = 175;			/* 身長 */
	sanaka.weight = 60.5;			/* 体重 */
	sanaka.schols = 70000;			/* 奨学金 */

	printf("氏 名 = %s\n",  sanaka.name);
	printf("身 長 = %d\n",  sanaka.height);
	printf("体 重 = %f\n",  sanaka.weight);
	printf("奨学金 = %ld\n", sanaka.schols);

	return (0);
}
 

「2」構造体のメンバ(->演算子の例、テキストp.314 List12-4から)

/*
	超能力をもったひろ子さん
*/

#include  <stdio.h>

#define NAME_LEN	64

struct student {
	char   name[NAME_LEN];	/* 名前 */
	int    height;		/* 身長 */
	double  weight;		/* 体重 */
	long   schols;		/* 奨学金 */
};

/*--- 身長を伸ばし体重を減らしてくれるひろ子さん ---*/
void hiroko(struct student *std)
{
	if (std->height < 180) std->height = 180;
	if (std->weight >  80) std->weight =  80;
}

int main(void)
{
	struct student  sanaka = {"Sanaka", 175, 60.5, 70000};

	hiroko(&sanaka);

	printf("氏 名 = %s\n",  sanaka.name);
	printf("身 長 = %d\n",  sanaka.height);
	printf("体 重 = %f\n",  sanaka.weight);
	printf("奨学金 = %ld\n", sanaka.schols);

	return (0);
}

「3」C言語内で定義された構造体には、処理系の違いを吸収するという役割や、複雑な構造をユーザーから隠してブラックボックスとして扱えるようにするといった役割があります。ここでは、日付と時間を表す構造体tmを使ったプログラムを作ってみましょう。

現在の暦時刻を取得するライブラリ関数として、time.hで宣言されているtimeという関数があります。
#include <time.h>
time_t time( time_t *timer);

ここで用いられているtime_t型は、ここで用いている処理系ではtypedef unsigned long time_t;と定義されています。また、この関数はある基準時刻からの経過秒数を返しますが、この値から直接現在の日時を計算することは困難です。そのため、この値から現在の日時を求める関数として、localtimeという関数が用意されています。

暦時刻を年・月・日・時・分・秒へ変換 する関数
#include <time.h>
struct tm *localtime(const time_t *timer);

この関数は、関数timeで返されたtime_t型の値を引数として、現在の日時を表すtm構造体へのポインタを返します。

struct tm {
	int tm_sec; //秒
	int tm_min; //分
	int tm_hour; //時
	int tm_mday; //日
	int tm_mon; // 1を足した数字が月
	int tm_year; // 1900を足した数が西暦
	int tm_wday; // 0: 日曜、1: 月曜、...6: 土曜
	int tm_yday; //1月1日からの通算日
	int tm_isdst; //季節時間フラグ
}

関数localtimeからの返り値はポインタなので、それぞれのメンバーの値を知るにはアロー演算子( -> )を用います。

/*
	今日の日付を表示する(テキストp.340, List13C-1参照)
*/

#include  <time.h>
#include  <stdio.h>

void put_date(void)
{
	time_t		current;
	struct tm  *local;
	char  wday_name[][3] = { "日", "月", "火", "水", "木", "金", "土" };

	time(&current);						/* 現在の時刻を取得 */
	local = localtime(&current);		/* 地方時の構造体に変換 */
	printf("%4d年%02d月%02d日(%s)", local->tm_year + 1900		/* 年 */
								  ,	local->tm_mon + 1			/* 月 */
								  , local->tm_mday				/* 日 */
								  , wday_name[local->tm_wday] ); /* 曜日 */
}

int main(void)
{
	printf("今日は");
	put_date();
	printf("です。\n");

	return (0);
}

 

(b) ファイル処理

「4」テキストファイルの入出力

ファイルには、テキストファイルとバイナリファイルがあります。この違いは、たとえば数字の1を保存する場合に対応するASCIIコード(16進数で表すと0x31)として保存するか、int型のビットパターン(int型が32ビットなら、00000000 00000000 00000000 00000001)として保存するかの違いです。ここではまず、テキストファイルについて学びます。

C言語でファイルを取り扱うときには、stdio.hで宣言されているFILE構造体を用います。ちょっとstdio.hファイルを覗いてその構造体の内部を見てみましょう。

typedef struct
{
        unsigned char  *curp;       /* Current active pointer     */
        unsigned char  *buffer;     /* Data transfer buffer       */
        int             level;      	/* fill/empty level of buffer */
        int             bsize;      	/* Buffer size                */
        unsigned short  istemp;     /* Temporary file indicator   */
        unsigned short  flags;      /* File status flags          */
        wchar_t         hold;       /* Ungetc char if no buffer   */
        char            fd;         	/* File descriptor            */
        unsigned char   token;      /* Used for validity checking */
}       FILE;                       /* This is the FILE object    */

このそれぞれのメンバーがどういう意味を持っているかは、ここでは理解する必要がありません。必要なことは、ファイルを作成したりする関数はFILE構造体へのポインタを返します。ファイルを操作する関数はこのFILE構造体へのポインタを引数として受け取ることにより、どのファイルに対して操作するのかを判断します。C言語ではファイルへのアクセスという複雑な処理にともなう情報をFILE構造体の中に閉じ込めることによってユーザーがその内部をブラックボックスとして扱うことを可能にしています。

以下、簡単なテキストファイルへの出力プログラムと入力プログラムを見てみましょう。テキストファイルは、Windowsに付属しているメモ帳などのテキストエディタで読めるファイルです。これら2つのプログラムは、前者で出力したファイルを後者で読み込むようになっています。4つの新しい関数が出てきます。

FILE *fopen( const char *(オープンするファイル名), const char *(オープンするモード) ); //オープンするモードは出力なら "w"、入力なら "r"となる。

int fclose(FILE *fp); // fopenの返り値であるfpで示されるファイルを閉じる。

残りの2つの関数、fprintf fscanf は、最初にFILE構造体へのポインタを引数として取る以外はこれまで用いてきた関数 printf 及び scanf と同じです。

/*  ファイルへ出力   */
#include <stdio.h>

int main(void)
{
    FILE *fp; 
    int i;
    double x = 0.0;

    fp = fopen("test1.txt", "w");
    fprintf(fp, "ファイルテスト\n");
    for (i=0; i<=10; i++)
        fprintf(fp, "%.2f\n", x++);
    fclose(fp);
    printf("ファイル作成終了。\n");
    return 0;
} /* 出力プログラム終了 */
/* ファイルから入力  */
#include <stdio.h>
int main(void)
{
    FILE *fp;
    char string[50];
    int i;
    double x[11];

    fp = fopen("test1.txt", "r");
    fscanf(fp, "%s", string);
    printf("%s\n", string);
    for (i=0; i<=10; i++)
        fscanf(fp, "%lf", &x[i]);
    for (i=0; i<=10; i++)
        printf("%.2f\n", x[i]);
    fclose(fp);
    return 0;
}
/* 入力プログラム終了 */

「5」バイナリファイルの入出力

テキストファイルは全てのデータが文字として保存されているファイルですが、バイナリファイルは整数値や実数値のビットパターンをそのまま保存したファイルです。バイナリファイルでは、テキストファイルと比較して同じデータをかなりコンパクトに保存することができますが、テキストファイルのようにその内容をテキストエディターで読むことは出来ません。

以下の4つの関数を用いれば、基本的な書き込み及び読み込みを行うことができます。fopenはテキストファイルの場合とモードが違うことに注意してください。

FILE *fopen( const char *(オープンするファイル名), const char *(オープンするモード) ); //オープンするモードは出力なら "wb"、入力なら "rb"となる。

int fclose(FILE *fp); // fopenの返り値であるfpで示されるファイルを閉じる。

fwrite(書き込むデータの格納先のアドレス(ポインタ)、要素1個の大きさ、要素の個数、fopenで返されたファイル構造体へのポインタ) 返り値:書き込んだ要素数

fread(読み込むデータの格納先のアドレス(ポインタ)、要素1個の大きさ、要素の個数、fopenで返されたファイル構造体へのポインタ) 返り値:読み込んだ要素数

 

以下に1個の整数値と要素数10のdouble型配列のバイナリファイルへの書き込み、及び読み出しプログラムを示します。バイナリファイルを読み出す場合には、事前にどのような型の情報が保存されているかを知っている必要があります。
/* バイナリファイルの入出力 */

/*  ファイルへ出力   */
#include <stdio.h>

int main(void)
{
	FILE *fp; 
	int a = 100, i;
	double array[10];
	for (i=0; i<10; i++){
		array[i] = (double)i;
	}
	
	fp = fopen("test1.dat", "wb");
	fwrite(&a, sizeof(int), 1, fp);
	fwrite(array, sizeof(double), 10, fp);
	fclose(fp);
	printf("バイナリファイル作成終了。\n");
	return 0;
} /* 出力プログラム終了 */

/* ファイルから入力  */
#include <stdio.h>

int main(void)
{
	FILE *fp; 
	int a, i;
	double array[10];
	
	fp = fopen("test1.dat", "rb");
	fread(&a, sizeof(int), 1, fp);
	fread(array, sizeof(double), 10, fp);
	fclose(fp);
	printf("ファイル読み込み終了。\n");
	printf("a = %d\n", a);
	for (i=0; i<10; i++){
		printf("array[%d] = %f\n", i, array[i]);
	}
	return 0;
} /* 入力プログラム終了 */

====== 演習問題 9A (p9a.c) ==============

double型の3つのメンバx, y, zを持つvectorというタグ名を持つ構造体を定義する。このvector構造体を2つ引数として取り、その2つのベクトル積(外積)を返り値として持つ関数vproductとスカラー積(内積)を返り値として持つ関数sproductを以下のように作成する。

struct vector {
    double x; /* X成分 */
    double y; /* Y成分 */
    double z; /* Z成分 */
};

struct vector vproduct( struct vector v1, struct vector v2) { /* */}
double sproduct( struct vector v1, struct vector v2) { /* */}

原点O、及びキーボードから読み込んだ3点A, B, Cの座標(ax, ay, az), (bx, by, bz), (cx, cy, cz)を頂点とする平行6面体の体積を計算するプログラムをこの関数を用いて作成せよ。 体積は、読み込んだ3点の座標をそれぞれ3つのvector構造体a, b, cに設定して、sproduct( vproduct(a, b), c)の絶対値として計算できる。 座標の読み込みは、例えばA点なら

	printf("点Aの座標:\n");
	printf("x:");	  scanf("%lf", &a.x);
	printf("y:");	  scanf("%lf", &a.y);
	printf("z:");	  scanf("%lf", &a.z);
とすればよい。また、絶対値の計算は数学関数の fabs関数を用いて良い。以下の出力例に示すとおりに入力と出力が表示されるプログラムを作成すること。
=====  出力例 =====
3点A, B, Cの座標を入力してください。 点Aの座標: x:1 y:0 z:0 点Bの座標: x:0 y:1 z:0 点Cの座標: x:0 y:0 z:1 平行6面体の体積は、1.00です。 ====================

====== 演習問題 9B (p9b.c) ==============

y = sin(20*x) + sin(19*x)の値をx = 0からx=10までN=100としてxの間隔10/Nで合計101点求め、以下の出力例の通りにCSVファイルに出力するプログラムを作成せよ。 CSVファイルはデータをコンマで区切ったテキストファイルで、拡張子をcsvとしておくとExcelで直接開くことができる。この保存されたcsvファイルをExcelで開き、Excel上で下図のようなグラフを作成せよ(データ領域を選択し、メニューから挿入、散布図を選択する。)。なお、課題の提出はプログラムのソースコードのみで良い。
=====出力例==========
x, y
0.000000, 0.000000
0.100000, 1.855598
...(途中省略)...
9.900000, -0.464903
10.000000, 0.124502
===================


====== 演習問題 9C (p9c.c) ==============

以下に示すプログラムは、符号なし文字型(unsigned char , 0~255までの値を表現できる)の512×512の配列data[i][j]をグレースケールでWindowsビットマップ形式の画像としてディスクに保存して表示するプログラムである。このプログラムの関数interference(…)はここでは縦方向に濃度が変化する画像を計算しているが、この関数本体の処理を書き換えて以下のような振動する2点から広がった波の強度分布を画像として保存して表示するプログラムを作成せよ。書き換える部分は関数interferenceの内部のみである。

2次元配列の添え字(i, j)を2次元座標(X,Y)と考え、位置A(I1, J1)とB(I2, J2)に同位相で振動する物体があり、その波が周囲に波長λで伝わっているとする。このとき、配列上のある点P(I, J)における振幅は、r1及びr2をAP及びBPの長さとして、(つまりr1 =sqrt( (I-I1)*(I-I1)+(J-J1)*(J-J1)) として)

と書ける。この波の振幅は2cos( )なので、強度分布は次の式に比例する。

この値に255を掛けた値を関数interference(…)で計算し、この干渉縞を下の画像のようなファイルとして保存するプログラムとせよ。なお、振動源の位置は、A(I1, J1)=(100, 0), 位置B(I2, J2)=(400, 0) とする。また、波長は、WL=25とする。プログラムが正常に動作すれば、画像が表示される。下の例は、振動源の位置を(200, 200)と(300, 300)とした例である。(下記プログラムでは、プログラム中で、「#define PROGRAM "mspaint" /* 使用するビットマップファイルビュアー */」としています。Windowsパソコンならこれで大抵画像が表示されると思いますが、Macなど他のOSのパソコンでは対応する画像表示プログラムに切り替える必要があります。適当なプログラムが存在しない場合には、main関数のreturn文の前のsystem(temp);をコメントアウトして、プログラムを実行して保存された画像ファイルを後から何らかの画像表示プログラムで表示させてください。)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define N 512 /*  = Image width and height */
#define FILENAME "result.bmp" /* 保存するデータファイル名 */
#define PROGRAM "mspaint"   /* 使用するビットマップファイルビュアー */
#define I1 200   /* Source A (X) */
#define J1 200   /* Source A (Y) */
#define I2 300   /* Source B (X) */
#define J2 300   /* Source B (Y) */
#define WL 25.0  /* Wavelength (pixel) */

typedef struct {
	short	bfType; /* 'BM' */
	int		bfSize;
	short	bfRes1;
	short	bfRes2;
	int		bfOffBits;
	int		biSize;
	int		biWidth;
	int		biHeight;
	short	biPlanes;
	short	biBitCount;
	int		biCompression;
	int		biSizeImage;
	int		biXPelsPerMeter;
	int		biYPelsPerMeter;
	int		biClrUsed;
	int		biClrImportant;
	unsigned char	color[256][4];
	unsigned char data[N][N];
} BITMAP;

void init_BMP(BITMAP *bmp);
void save_BMP(char *filename, BITMAP *bmp);
void interference(unsigned char data[][N], int i1, int j1, int i2, int j2, float w);

int main(void)
{
	static BITMAP image;
	char temp[50];
	
	init_BMP(&image);
	interference(image.data, I1, J1, I2, J2, WL);
	save_BMP(FILENAME, &image);
	strcpy(temp, PROGRAM);
	strcat(temp, " ");
	strcat(temp, FILENAME);
	system(temp);
	return 0;
}

void interference(unsigned char data[][N], int i1, int j1, int i2, int j2, float w)
{	/* Source A(i1, j1), Source B(i2, j2), w=wavelength  */
	int i, j;
	
	for (j=0; j<N; j++){
		for (i=0; i<N; i++){
			data[j][i] = (unsigned char)(j % 256);
		}
	}
}

//////////////////////////////////
void init_BMP(BITMAP *bmp)
{
	int i, j;
	bmp->bfType=0x4d42;		/* bfType='BM' */
	bmp->bfSize=14+40+sizeof(bmp->color)+sizeof(bmp->data); /* bfSize */
	bmp->bfRes1=0;
	bmp->bfRes2=0;
	bmp->bfOffBits=14+40+sizeof(bmp->color);
	bmp->biSize=40; /* biSize */
	bmp->biWidth=N; /* Image Width */
	bmp->biHeight=N; /* Image Height */
	bmp->biPlanes=1; /* biPlanes */
	bmp->biBitCount=8; /* Number of bits/pixel */
	bmp->biCompression=0; /* No compression */
	bmp->biSizeImage=sizeof(bmp->data); /* biSizeImage */
	bmp->biXPelsPerMeter=0; /* biXPelsPerMeter */
	bmp->biYPelsPerMeter=0; /* biYPelsPerMeter */
	bmp->biClrUsed=256; /* biClrUsed */
	bmp->biClrImportant=256; /* biClrImportant */
/* Initialize Gray Scale Color Map */
	for(i=0; i<256; i++){
		bmp->color[i][0]=bmp->color[i][1]=bmp->color[i][2]=(unsigned char)i;
		bmp->color[i][3]=0;
	}
}

void save_BMP(char *filename, BITMAP *bmp) /* Write BMP File to Disk */
{
	FILE *fp;
	fp=fopen(filename, "wb"); /* 'wb': Write and Binary Mode */
	fwrite(&(bmp->bfType), sizeof(bmp->bfType), 1, fp);
	fwrite(&(bmp->bfSize), sizeof(bmp->bfSize), 1, fp);
	fwrite(&(bmp->bfRes1), sizeof(bmp->bfRes1), 1, fp);
	fwrite(&(bmp->bfRes2), sizeof(bmp->bfRes2), 1, fp);
	fwrite(&(bmp->bfOffBits), sizeof(bmp->bfOffBits), 1, fp);
	fwrite(&(bmp->biSize), sizeof(bmp->biSize), 1, fp);
	fwrite(&(bmp->biWidth), sizeof(bmp->biWidth), 1, fp);
	fwrite(&(bmp->biHeight), sizeof(bmp->biHeight), 1, fp);
	fwrite(&(bmp->biPlanes), sizeof(bmp->biPlanes), 1, fp);
	fwrite(&(bmp->biBitCount), sizeof(bmp->biBitCount), 1, fp);
	fwrite(&(bmp->biCompression), sizeof(bmp->biCompression), 1, fp);
	fwrite(&(bmp->biSizeImage), sizeof(bmp->biSizeImage), 1, fp);
	fwrite(&(bmp->biXPelsPerMeter), sizeof(bmp->biXPelsPerMeter), 1, fp);
	fwrite(&(bmp->biYPelsPerMeter), sizeof(bmp->biYPelsPerMeter), 1, fp);
	fwrite(&(bmp->biClrUsed), sizeof(bmp->biClrUsed), 1, fp);
	fwrite(&(bmp->biClrImportant), sizeof(bmp->biClrImportant), 1, fp);
	fwrite(&(bmp->color), sizeof(bmp->color), 1, fp);
	fwrite(&(bmp->data[0][0]), sizeof(bmp->data), 1, fp);

	fclose(fp);
	printf("ファイル名%sで保存しました。\n", filename);
}

========