ソースコードエラーについて、アドバイスを下さい。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
あーてぃ

ソースコードエラーについて、アドバイスを下さい。

#1

投稿記事 by あーてぃ » 10年前

始めての投稿となります。
テンプレをお借りさせて頂きますが、宜しくお願いいたします。

[1] 質問文

[1.1] 自分が今行いたい事は何か
とあるテキストの練習問題で
「40人の生徒の平均タイムを表示させよ」
といった問題があったので、練習のため不器用ながら作ってみました。
しかし、わからないところが2つほどあり、自力では解決方法が分からないのでどなたか教えていただきたいです。



[1.2] どのように取り組んだか(プログラムコードがある場合記載)

コード:

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

#define Nom 40

double Average(double);

int main(void){
	int i=0;
	double stu[Nom],Ave;


	printf("%d人分のタイムを入力して下さい",Nom);
	for(i=0;i<Nom;i++){
		printf("%2d人目",i+1);
		scanf("%f",&stu[i]);
	}
	
	Ave = Average(stu[Nom]);

	printf("%d人の平均タイムは%5.2lfです",Nom,Ave);

	return 0;
}


double Average(double stu[]){
	int i=0;
	int sum=0;

	for(i=0;i<Nom;i++){
		sum += stu[i];
	}
	
	return sum/Nom;
}


[1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)

1・コンパイル時に「パラメーターの型 1 が関数宣言と一致しません。」と表示されるが、直し方が分からない。
2・コンパイルは出来て40人分のタイムを入力することはできるが、「40人の平均タイムは-53687091.00です」と表示がおかしい。

[1.4] 今何がわからないのか、知りたいのか

勉強中のためソースコードの作りに甘い部分があると思いますが、上記の2点に関してどなたか教えて頂けるとありがたいです。


[2] 環境  
 [2.1] OS : Windows7
 [2.2] コンパイラ名 : Visual Studio コマンドプロンプト

[3] その他
学校でC言語について学んではいたものの、イマイチ身に付けることが出来なかったため、独学で勉強をしようと思いました。
入門書を3冊ほど読み、関数、ポインタ、構造体あたりをウロウロとしています。

BODY

Re: ソースコードエラーについて、アドバイスを下さい。

#2

投稿記事 by BODY » 10年前

うろ覚えなんで間違ってるかもしれませんが
19行目のNUMの部分はいらないような…

あと29行目のintの部分はdoubleにするべき…?

アバター
milfeulle
記事: 47
登録日時: 10年前
住所: マリーランド
連絡を取る:

Re: ソースコードエラーについて、アドバイスを下さい。

#3

投稿記事 by milfeulle » 10年前

> 1・コンパイル時に「パラメーターの型 1 が関数宣言と一致しません。」と表示されるが、直し方が分からない。
C言語では宣言された関数の引数と、実際に実行するときに渡す引数が一致していないとコンパイルエラーを吐きます。(それは当然ですよね! むしろ間違いを見つけてくれて喜ぶべきです。)

例えば、

コード:

void func(int x) {
	printf("%d\n", x);
}
という関数があったとします。ここにint*型のデータを渡したらだめですよね。

コード:

int* p;
func(p); // エラー
これがご質問のエラーのことです。コンパイラのはき出すエラーの行数を確認して、どこでエラーが起きているのか確認しましょう。

コード:

Ave = Average(stu[Nom]); // ここでしょう
あーてぃさんは重大な間違いを犯しています。それはstu[Nom]が何を意味しているのか分かっていない点です。stu[Nom]は配列ではありません。Numは今数字の40ですから、stu[40]と書いても同じです。(本当に文字通り同じです。コンパイル時にNumという識別子が強制的に40に置き換えられます。)これは配列stuの41個目のデータを指しますよね? double型のデータを1つ渡していることと等価です。

あーてぃさんがやりたいことは配列を渡したいのだと思いますが、これではデータを1つしか渡せていませんよね。しかも40個しか用意していない配列の41個目のデータを渡そうとしています。これはコンパイルエラーだけでは済まされません!

ところで

コード:

double Average(double stu[]){
の部分ですが、C言語では関数の引数として宣言した「配列」は「ポインタ」に置換されてしまいます!

コード:

double Average(double* stu){
となってしまいます。誤解のないようにこのように宣言すると分かりやすいと思います。この際stuは配列ではありません。もはやポインタとなってしまっています。

つまり個数の情報はありません。本当に40個あるか確認しようがありません。がこれはいろいろ調べてみて下さい。(より汎用的なプログラムをお書きになりたいならサイズを渡します。40個の配列だけが欲しいならdouble (*stu)[40]と宣言します。)

さてポインタですが、ポインタであっても配列と同じようにstu[1]のようにして値を取得したり設定したりできます。これはポインタstuを0番目の要素として扱って、そこからN個目先のデータを指す表現として用いられます。今回の場合は、40個のデータをAverage関数に渡したいのですよね。

それなら配列の初めのポインタをAverage関数に渡して、Average関数内で40個のデータを処理すればいいですね。

配列stuの初めの要素はstu[0]ですね。これのポインタは&stu[0]ですね。これを渡せばよいと考えられます。応用すると、&stu[10]とすれば最初の10個を無視して11番目から処理されます。(m番目からn番目の平均を求める関数を作ってみましょう!)

ところで&stu[0]ですが、C言語では配列変数stuを適切な位置でポインタに変換してくれます。今回の場合は、Average関数にstuをそのまま渡した場合、stu自体はポインタではなく配列ですが、C言語さんのご厚意により適宜変換して呼び出してくれます!
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー

box
記事: 2002
登録日時: 13年前

Re: ソースコードエラーについて、アドバイスを下さい。

#4

投稿記事 by box » 10年前

あーてぃ さんが書きました:

コード:

double Average(double);
関数プロトタイプ宣言を書くのであれば、関数の実体を定義している
あーてぃ さんが書きました:

コード:

double Average(double stu[]){
この部分をコピペして、{ の代わりに ; を書けばよいのです。
楽をしましょう。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: ソースコードエラーについて、アドバイスを下さい。

#5

投稿記事 by みけCAT » 10年前

皆さんの指摘とは直接関係ないですが、
あーてぃ さんが書きました:

コード:

scanf("%f",&stu[i]);

printf("%d人の平均タイムは%5.2lfです",Nom,Ave);
ここもまずいですね。double型の値をC言語/C++のscanf(系の関数)で読み込む時は%lfを、
printf(系の関数)で書き出すときは%fを用いるべきです。特に前者は間違えると致命的になることが多いです。
(もちろん、それぞれ%lg、%gなどでもかまいません)

注:ここでは「scanf系の関数」とはfscanf、sscanfなどを意図した。「printf系の関数」も同様。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ソースコードエラーについて、アドバイスを下さい。

#6

投稿記事 by ISLe » 10年前

milfeulle さんが書きました:ところで

コード:

double Average(double stu[]){
の部分ですが、C言語では関数の引数として宣言した「配列」は「ポインタ」に置換されてしまいます!

コード:

double Average(double* stu){
となってしまいます。誤解のないようにこのように宣言すると分かりやすいと思います。この際stuは配列ではありません。もはやポインタとなってしまっています。
milfeulle さんが書きました:ところで&stu[0]ですが、C言語では配列変数stuを適切な位置でポインタに変換してくれます。今回の場合は、Average関数にstuをそのまま渡した場合、stu自体はポインタではなく配列ですが、C言語さんのご厚意により適宜変換して呼び出してくれます!
ご厚意に甘えて、配列が欲しいところでは配列として関数原型を宣言したほうが意図を明確にできるというメリットはないでしょうか。
わたしは実装に沿うよりも設計の意図を伝えるほうが重要である気がします。

配列の一部を渡せることに気付けないかもしれませんが、気付けないレベルでそのようなことはしないほうが良いとも言えるのではないかと思います。
milfeulle さんが書きました:つまり個数の情報はありません。本当に40個あるか確認しようがありません。がこれはいろいろ調べてみて下さい。(より汎用的なプログラムをお書きになりたいならサイズを渡します。40個の配列だけが欲しいならdouble (*stu)[40]と宣言します。)
嘘が混じってますよ。

アバター
milfeulle
記事: 47
登録日時: 10年前
住所: マリーランド
連絡を取る:

Re: ソースコードエラーについて、アドバイスを下さい。

#7

投稿記事 by milfeulle » 10年前

※ 以下、本トピックと直接の関係がありません。

フォローありがとうございます。お気に障られたようなので補足しておきます。
ISLe さんが書きました:ご厚意に甘えて、配列が欲しいところでは配列として関数原型を宣言したほうが意図を明確にできるというメリットはないでしょうか。
わたしは実装に沿うよりも設計の意図を伝えるほうが重要である気がします。
私もそう思います。ただ、あーてぃさんはそのようなセマンティクスのレベルで迷われているのではなく、シンタックスのレベルでご質問されています。そのため型をより意識して頂けるように冗長な書き方をご紹介しました。

ただ、C言語で(シンタックス上の)「配列」は渡せないわけですから意識せざるを得ません。敢えて隠す必要はなく、そのことを理解した上でご自身の分かりやすいように書けるようになって頂くのがこの掲示板の趣旨だと思いますが……。
ISLe さんが書きました:嘘が混じってますよ。
申し訳ございませんでした。もしよろしければ後学のためにご教授お願いします。私の理解では関数にシンタックス上の配列を渡すには、配列へのポインタ〔C++では参照の方が便利だと思います〕を渡すしかないと思っておりました。

コード:

void func(int (*ary)[4]) { // *aryは配列
	int i;

	for(i = 0; i < 4; ++i) {
		cout << (*ary)[i] << endl;
	}
}
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー

あーてぃ
記事: 5
登録日時: 10年前

Re: ソースコードエラーについて、アドバイスを下さい。

#8

投稿記事 by あーてぃ » 10年前

こんばんは。あーてぃです。
みなさん、アドバイス有難うございます。
コメントを元に、問題点を一つづつ解決していった結果、どうにかプログラムが動くように成りました。

以下、改善したコードになります。

コード:

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

//タイムを入力する人数
#define Nom 5

double Average(double *stu);

/************************************
*	int main();
*	機能
*		人数分のタイム入力、平均タイムの出力
*	引数
*		無し
*	戻り値
*		int			正常終了時に0を返す
*************************************/

int main(void){
	int i=0;
	double stu[Nom],Ave;


	printf("%d人分のタイムを入力して下さい\n"
		"*****************************\n",Nom);
	for(i=0;i<Nom;i++){
		printf("%2d人目",i+1);
		scanf("%lf",&stu[i]);
	}
	
	Ave = Average(&stu[0]);

	printf("\n%2d人の平均タイムは%10.7fです。\n",Nom,Ave);

	return 0;
}

/************************************
*	double Average(double *stu);
*	機能
*		生徒全員の平均タイムの計算
*	引数
*		double &stu[0]	stu[Nom]配列の最初のアドレス
*	戻り値
*		double sum/Nom	平均タイム
*************************************/

double Average(double *stu){
	int i=0;
	double sum=0;

	for(i=0;i<Nom;i++){
		sum += *(stu+i);
	}
	
	return sum/Nom;
}
 
・改善点
エラーの最も大きな原因は、関数の引数だったようです。
【改善前】

コード:

    Ave = Average(stu[Nom]);
【改善後】

コード:

	Ave = Average(&stu[0]);
関数の引数に、stu配列のNom番目の値のみを渡していたのが最大の原因だったようです。
これに関しては、 milfeulle さんのアドバイスがとても助かりました。ありがとうございます。

また、average関数内の構造も変更をしました。
【改善前】

コード:

double Average(double stu[]){
    int i=0;
    int sum=0;
 
    for(i=0;i<Nom;i++){
        sum += stu[i];
    }
    
    return sum/Nom;
}
【改善後】

コード:

double Average(double *stu){
	int i=0;
	double sum=0;

	for(i=0;i<Nom;i++){
		sum += *(stu+i);
	}
	
	return sum/Nom;
}
引数には配列の先頭のアドレスを渡し、仮引数はポインタとして*stuを用意。
アドレスは型の数だけ増えていくことを使って、for文で一つづつiを増やしていくことでそれぞれの生徒のタイムを参照することが出来ました。

・おわりに
milfeulleさんの他にも、BODYさん、boxさん、みけCAT さん、ISLeさんのアドバイス、大変勉強になりました。
経験者からみると、まだまだ未熟な部分が私には見受けられるかもしれませんが、今後も勉強していくつもりです。
皆様こそ宜しければ、今後もどうぞよろしくお願い致します。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ソースコードエラーについて、アドバイスを下さい。

#9

投稿記事 by ISLe » 10年前

別に気には障っていません。
milfeulle さんが書きました:申し訳ございませんでした。もしよろしければ後学のためにご教授お願いします。私の理解では関数にシンタックス上の配列を渡すには、配列へのポインタ〔C++では参照の方が便利だと思います〕を渡すしかないと思っておりました。

コード:

void func(int (*ary)[4]) { // *aryは配列
	int i;

	for(i = 0; i < 4; ++i) {
		cout << (*ary)[i] << endl;
	}
}
この宣言ではサイズを渡しているのでしょうか。
C言語では警告止まりですし。
より汎用的とおっしゃっていますが、この宣言に変えることで、直後にあなたが提案された応用への選択肢が減りますよね。

配列へのポインタの提案が唐突で説明不足であるのは、誤解が生まれることを懸念する人間の行動として矛盾しているように思います。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ソースコードエラーについて、アドバイスを下さい。

#10

投稿記事 by ISLe » 10年前

milfeulle さんが書きました:
ISLe さんが書きました:ご厚意に甘えて、配列が欲しいところでは配列として関数原型を宣言したほうが意図を明確にできるというメリットはないでしょうか。
わたしは実装に沿うよりも設計の意図を伝えるほうが重要である気がします。
私もそう思います。ただ、あーてぃさんはそのようなセマンティクスのレベルで迷われているのではなく、シンタックスのレベルでご質問されています。そのため型をより意識して頂けるように冗長な書き方をご紹介しました。

ただ、C言語で(シンタックス上の)「配列」は渡せないわけですから意識せざるを得ません。敢えて隠す必要はなく、そのことを理解した上でご自身の分かりやすいように書けるようになって頂くのがこの掲示板の趣旨だと思いますが……。
わたしはシンタックスの話しかしていません。
唐突に無関係と思われるセマンティクスな話を持ち出したのはmilfeulleさんですが。

コード:

    double stu[Nom],Ave;
    double Average(double stu[]);
    Ave = Average(stu);

コード:

    double stu[Nom],Ave;
    double Average(double (*stu)[Nom]);
    Ave = Average(&stu); /* このトピックでは一度も説明されていない記述 */
これらを比較して、わたしには前者のほうが直感的で分かりやすく汎用性が高いと思います。
#扱いには注意が必要ですが。
後者を持ち出す意図が分かりません。

No.8の改善後のAverage関数の中身もシンタックスにこだわるあまり分かりにくくなってしまったのではないでしょうか。

(追記)
質問者さんの質問に対して、配列へのポインタを使って要素数を限定するコードにすべきというのは回答としてあると思います。
配列へのポインタを持ち出すこと自体を否定するものではありません。

閉鎖

“C言語何でも質問掲示板” へ戻る