情報処理4レポート課題(1)の解答例

更新:2007年9月23日


以下の問題について,
  1. 次のプログラムは,関数substractを用いて ベクトルの差と ベクトルの差を計算します. コメント部分に何かを足してプログラムを完成して下さい. ただし既にあるプログラム部分を変更してはいけません.
    vector_subtract0.c
    #include<stdio.h>
    
    /***関数subtractの宣言***/
    
    int main(void){
    	double a[7], b[7], c[5], d[5];
    	int i;
    	double a_sub_b[7], c_sub_d[5];
    
    	a[0]=0.1; a[1]=0.2; a[2]=0.3; a[3]=0.4; a[4]=0.5; a[5]=0.6; a[6]=0.7;
    	b[0]=1.1; b[1]=2.2; b[2]=3.3; b[3]=4.4; b[4]=5.5; b[5]=6.6; b[6]=7.7;
    
    	c[0]=0.5; c[1]=0.6; c[2]=0.7; c[3]=0.8; c[4]=0.9;
    	d[0]=11.0; d[1]=22.1; d[2]=33.2; d[3]=44.3; d[4]=55.4;
    
    	printf("a:\n");
    	for(i=0;i<7;i++){
    		printf("a[%d]=%f\n", i, a[i]);
    	}
    
    	printf("b:\n");
    	for(i=0;i<7;i++){
    		printf("b[%d]=%f\n", i, b[i]);
    	}
    
    	/***関数subtractの呼出***/
    
    	printf("a_sub_b:\n");
    	for(i=0;i<7;i++){
    		printf("a_sub_b[%d]=%f\n", i, a_sub_b[i]);
    	}
    
    	printf("c:\n");
    	for(i=0;i<5;i++){
    		printf("c[%d]=%f\n", i, c[i]);
    	}
    
    	printf("d:\n");
    	for(i=0;i<5;i++){
    		printf("d[%d]=%f\n", i, d[i]);
    	}
    
    	/***関数subtractの呼出***/
    
    	printf("c_sub_d:\n");
    	for(i=0;i<5;i++){
    		printf("c_sub_d[%d]=%f\n", i, c_sub_d[i]);
    	}
    
    	return 0;
    }
    
    /***関数subtractの定義***/
    

    この問題は、配列を処理する関数を自作するものです。 int型やdouble型などの単一の値を処理する関数を自作する技術に加えて、 配列を関数で処理する際に配列の性質を理解しておく必要があります。

    関数を自作する場合には、その関数を宣言・定義した上で、 呼び出すことになります。

    宣言するためには、必要となる(1つ以上の)引数の型と返却値の型を決めなければなりません。

    引数は次の通りです

    また、返却値の型はvoidです。 よって関数subtractの宣言は次の通りです。
    void subtract(double *lhs, double *rhs, int size, double *result);
    
    この関数subtractを呼び出すときには、実引数a,b,7,a_sub_bを指定したり、 c,d,5,c_sub_dを指定したりします。

    実引数aは配列aの名前でプログラム内では配列の先頭要素のアドレスを表すので、 aだけではその配列の要素数が分かりません。 そこで、配列名とは別に配列の要素数(仮引数sizeに実引数7や5)を指定する必要があるのです。

    結果はを表す配列となるのですが、C言語では配列を返却することができません。 そこで、結果を表す配列a_sub_bやc_sub_dも関数subtractの実引数にして(仮引数をdouble *resultにして)、 関数subtract内で計算結果をa_sub_bやc_sub_dに対応する仮引数resultの各要素に代入します。 結果の処理をこのように引数を用いることにしたので、 本来結果を処理するために使われる返却値は不要となるので返却値の型をvoidにします。

    定義するためには宣言と同じように始めて実際の処理を書きます。 実引数a,bやc,dに対応する仮引数lhs,rhsの各要素の差を 実引数7や5に対応する仮引数size個分計算して、 実引数a_sub_bやc_sub_dに対応する仮引数resultの各要素に代入します。
    void subtract(double *lhs, double *rhs, int size, double *result){
    	int i;
    	for(i=0;i<size;i++){
    		result[i]=lhs[i]-rhs[i];
    	}
    	return;
    }
    
    計算結果を返却しないので、値を指定しないreturn文になっています。

    vector_subtract.c
    #include<stdio.h>
    
    void subtract(double *lhs, double *rhs, int size, double *result);
    
    int main(void){
    	double a[7], b[7], c[5], d[5];
    	int i;
    	double a_sub_b[7], c_sub_d[5];
    
    	a[0]=0.1; a[1]=0.2; a[2]=0.3; a[3]=0.4; a[4]=0.5; a[5]=0.6; a[6]=0.7;
    	b[0]=1.1; b[1]=2.2; b[2]=3.3; b[3]=4.4; b[4]=5.5; b[5]=6.6; b[6]=7.7;
    
    	c[0]=0.5; c[1]=0.6; c[2]=0.7; c[3]=0.8; c[4]=0.9;
    	d[0]=11.0; d[1]=22.1; d[2]=33.2; d[3]=44.3; d[4]=55.4;
    
    	printf("a:\n");
    	for(i=0;i<7;i++){
    		printf("a[%d]=%f\n", i, a[i]);
    	}
    
    	printf("b:\n");
    	for(i=0;i<7;i++){
    		printf("b[%d]=%f\n", i, b[i]);
    	}
    
    	subtract(a, b, 7, a_sub_b);
    
    	printf("a_sub_b:\n");
    	for(i=0;i<7;i++){
    		printf("a_sub_b[%d]=%f\n", i, a_sub_b[i]);
    	}
    
    	printf("c:\n");
    	for(i=0;i<5;i++){
    		printf("c[%d]=%f\n", i, c[i]);
    	}
    
    	printf("d:\n");
    	for(i=0;i<5;i++){
    		printf("d[%d]=%f\n", i, d[i]);
    	}
    
    	subtract(c, d, 5, c_sub_d);
    
    	printf("c_sub_d:\n");
    	for(i=0;i<5;i++){
    		printf("c_sub_d[%d]=%f\n", i, c_sub_d[i]);
    	}
    
    	return 0;
    }
    
    void subtract(double *lhs, double *rhs, int size, double *result){
    	int i;
    	for(i=0;i<size;i++){
    		result[i]=lhs[i]-rhs[i];
    	}
    	return;
    }
    
  2. 次のプログラムは文字列aの先頭文字からヌル文字の直前までの文字数を 画面表示します. コメント部分に何かを足してプログラムを完成して下さい. ただし既にあるプログラム部分を変更してはいけません.
    strlength0.c
    #include<stdio.h>
    
    int main(void){
    	char *a="bcdef";
    	char *p=a;
    	while(1){
    		if(/***ここに何か書く***/){
    			break;
    		}
    		/***ここに何か書く***/
    	}
    	printf("%d\n", p-a);
    	return 0;
    }
    

    この問題は文字列操作の内で文字列を書き換えない類のものです。 文字列には可視文字以外に最後にヌル文字(もしくは、終端文字)があるという知識と 基本的には配列と同じように扱うことの知識が必要とされます。

    基本方針は、char型へのポインタを用意して、処理対象文字列の先頭要素を指すようにし、 指した要素の文字がヌル文字でない限りポインタをインクリメントして次の文字を指すようにします。 ヌル文字を指したときのポインタと処理対象文字列の先頭要素のアドレスの差が目的の文字数になります。

    雛型プログラムではp-aを画面表示しているので、aでなくpをインクリメントします。

    strlength.c
    #include<stdio.h>
    
    int main(void){
    	char *a="bcdef";
    	char *p=a;
    	while(1){
    		if(*p=='\0'){
    			break;
    		}
    		p++;
    	}
    	printf("%d\n", p-a);
    	return 0;
    }
    
  3. 次のプログラムは,文字列aを保持するための必要十分な記憶領域を確保して変数pに対応づけ, 確保された記憶領域に文字列aをコピーします. コメント部分に何かを足してプログラムを完成して下さい. ただし既にあるプログラム部分を変更してはいけません.
    dstrcpy0.c
    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    
    int main(void){
    	char *a="bcdef";
    	char *p;
    	int n=strlen(a), i;
    	/***ここに何か書く***/
    	if(/***ここに何か書く){
    		printf("out of memory\n");
    		/***ここに何か書く***/
    	}
    	for(i=0;i<n;i++){
    		/***ここに何か書く***/
    	}
    	/***ここに何か書く***/
    	printf("%s\n", p);
    	free(p);
    	return 0;
    }
    

    この問題は、malloc関数を用いて必要な大きさ分の領域を確保することと、 文字列操作の内で文字列を書き換える類のものです。

    malloc関数の使い方については、丸覚えするのが良いでしょう。 nには文字列aのヌル文字を含まない文字数が格納されていることを踏まえて、 malloc関数を用いてchar型(n+1)個分の領域を確保します。 nより1つ多いのはヌル文字のためです。 エラー処理も行って下さい。

    文字列のコピーについては基本的には配列と同じです。 文字数が分からない場合にはコピー元のヌル文字を確認しながらコピーすることになりますが、 今は文字数が分かっているので文字数n分コピーして、 最後にヌル文字を代入します。

    dstrcpy.c
    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    
    int main(void){
    	char *a="bcdef";
    	char *p;
    	int n=strlen(a), i;
    	p=malloc((n+1)*sizeof(char));
    	if(p==NULL){
    		printf("out of memory\n");
    		exit(1);
    	}
    	for(i=0;i<n;i++){
    		p[i]=a[i];
    	}
    	p[i]='\0';
    	printf("%s\n", p);
    	free(p);
    	return 0;
    }
    

    ヌル文字の代入を忘れてもたまたまうまくコピー先pが表示される場合がありますが、 たまたまです。 続けて、より文字数の少ない文字列を上書きすると、ヌル文字の代入をしておかないと 前の文字列の残骸も表示されてしまいます。
    dstrcpy_lostnull.c(誤り)
    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    
    int main(void){
    	char *a="bcdef";
    	char *p;
    	char *b="ghi";
    	int n=strlen(a), i;
    	p=malloc((n+1)*sizeof(char));
    	if(p==NULL){
    		printf("out of memory\n");
    		exit(1);
    	}
    	for(i=0;i<n;i++){
    		p[i]=a[i];
    	}
    	/***敢えてヌル文字代入を忘れてみる。***/
    	printf("%s\n", p);
    	/***たまたまうまく表示されるかも知れない***/
    
    	/***続けて、より文字数の少ないbをコピーする***/
    	/***nに文字列bの文字数を代入***/
    	n=strlen(b);
    	/***n文字分bをpにコピー***/
    	for(i=0;i<n;i++){
    		p[i]=b[i];
    	}
    	/***コピー先pを表示してみる***/
    	printf("%s\n", p);
    	free(p);
    	return 0;
    }
    

以下は,頂いた考察・感想の幾つかに対するコメントです.
関数を呼び出すタイミングは、a-bとc-d同時の方が簡潔で分かりやすいと思ったのです がどうでしょう?
うーん,どうでしょう. aの表示,bの表示,a-bの計算,a-bの表示で1セットなので, aの表示,bの表示,a-bの計算,a-bの表示, cの表示,dの表示,c-dの計算,c-dの表示の順の方が分かりやすいと思うのですが.
2つ目の「/***関数subtractの定義***/」の所でなぜcとdのパターンの時に「a_sub_b」 としておいてもc_sub_dがうまくいくのかわからなかった。 関数を2種類定義して2種類呼び出せば問題ないと考えたけれど途中で確認した時に結果 がたまたまうまくいってしまったのでその部分を解説してもらえると嬉しいです。
ええと,今一つご質問の意図が分からず, 提出して頂いたプログラムを見てみましたが, 「2つ目の「/***関数subtractの定義***/」の所でcとdのパターンの時に「a_sub_b」 としている」ようなプログラムでなかったので, 指摘している現象を再現できませんでした. 指摘している現象が起きるプログラムを掲載した上で, あらためて質問して下さい.
呼び出す際に[]を付けなくてよいことにいまいち理解できなかった。
配列を引数とする関数は所詮,配列の先頭要素のアドレスを引数としていること を踏まえると理解できると思います.
関数の宣言であったり、定義であったりでdouble *a_sub_bだけで実行できたが 、なぜ double *c_sub_dを宣言や定義をしなくても、呼び出すだけで実行できるのか不思議であった。
関数の仮引数名と実引数名を混同しているようです. 関数subtractの仮引数名a_sub_bと1回目の呼出時の実引数名a_sub_bは別物であることに注意して下さい.
覚えてない所を思い出そうとするのにはイライラしました。前期のテストは持ち込み可 で理解はしたけど形が覚えられないというのばかりでした。やっぱり、覚えるべき所もあ ったんですかね?
「覚えるべき」=「丸覚えするべき」ならば, 覚えるべき所はありません. 使い込むことによって指に覚えさせて欲しいです.
関数呼出の所で、宣言は、 void subtract(double *x,double *y,double *result,int num); だったのに対し、呼出では、 subtract(a,b,a_sub_b,7); と、配列では無くなっていた事がちょっと疑問だったので、解説して頂けると嬉しいです 。
プログラム中の配列名単独は何を表すのでしたっけ.
課題2での「p-a」の文の意味がわかりません。
p,aの型はいずれもchar型へのポインタです. ポインタ変数に1足すとそのポインタ変数の指す型の大きさ1つ分だけアドレスが増えます. これを踏まえると,p-aは((アドレスp)-(アドレスa))/(char型の大きさ)になります.
それぞれの結果を呼び出すときに両方ともa_sub_dと書くのが気持ち悪いので はじめから計算結果をresult[7]とresult[5]で定義し、...
プログラム中に同一名の異なる配列(や変数や関数)を宣言することはできないのです.
問題3にて最後のヌル文字を入れなくてもpは正しく表示された。print文中%sで表示す るにはヌル文字が無いといけないはずなのに
たまたまです.
いまさらですが問題2で最後p-aをする必要がわからなかった。わざわざpにaをいれなく てもaをカウントすれば文字数のカウントはできるのではないでしょうか??
確かに,指摘されている方法でも解くことができます. ただし,指摘された方法では, カウントした結果を格納するint型変数が必要になります. 今回,指定した方法ではそのようなint型変数が不要な代わりに, pが必要になります. どちらがいいということではありませんが, 今回は題意を満たす方法で作成してもらいました.
n=strlen(a) の意味がわかりません。
strlen関数は文字列aの文字数を返却します.よって,nには文字列aの文字数を格納しています.
問題2で文字コードの数はアルファベット順に1ずつ繰り上がって行くので、p++でnullに なるまでカウントアップして最後に、最後の文字コードの数から最初の文字コードの数を 引いて文字数が表示されると思っていた。しかしアルファベット順に並んでいなくても( 例えばbcdefx)でも、文字数がきちんと表示された。これが謎だった。
文字コードを順に追っているのでなく, 文字列を先頭から順に追っています.
3番のexit(1)やout of memoryの用途が良く分からなくなった。
使用可能メモリ以上の領域を確保しようとしたときが出番です.
通信工学科ではjava等はやらないのですか? 独学で学んでいるのですが限界が・・・
残念ながらカリキュラムにJavaは含まれていません. 今日たまたま研究室に遊びに来たOBが云うには 「今,Javaやってるんですけど,C++やってて良かったです.」だそうです. 個人的には,C++からJavaには難なく移行できるけど, JavaからC++に移行するのは大変だと思います. 有限のカリキュラム上,当学科ではC,C++のみ学修することになっています.
問題文には/***ここに何か書く***/というところ全て使ってプログラミングして つくれと書いてあるが最後の部分はなぜか使わずに答えがでてしまい どうすればいいかわからない。
NULL文字を入れて下さい.
free(p);とあるが、どういう時に領域開放をするのか分からなかった。
pが不要になった時です.
malloc関数と文字列関数string.hのstrcpy(p,a)の両方を使っているが、strcpy(p,a)が無 くても実行ができ、同じ結果になりました。 使わなくてもよいのかと友達と検討しましたが、結局よくわからなかったです。
この問題の趣旨は, コピー先領域をプログラムに確保させることと,コピーすることです. コピーについてfor文を用いて行っているので,strcpyは不要です.
free(p)がよく分からなかった。
pの指す領域を解放しています.

何かありましたら、 まで。