みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

関数への構造体の配列の渡し方<c言語初心者>

こんにちは、関数への構造体の配列の渡し方で理解できない点があるため、質問させていただきます。
以下がスクリプトになります。3人の名前と年齢をinput関数で入力し、それらのデータをoutput関数で出力するのが目的です。

#include <stdio.h>

typedef struct{
char name[64];
int age;
}property;

void input(property *data[]);
void output(property *data[]);

int main(void){
property data[3];
printf("Input data of three people.\n");
input(&data);
output(&data);
return 0;
}

void input(property *data[]){
int i;
for(i=0;i<3;i++){
printf("%d banme\n",i+1);
printf("name:");
scanf("%s",&data[i]->name);
printf(" age:");
scanf("%3d",&data[i]->age);
}
return;
}

void output(property *data[]){
int i;
for(i=0;i<3;i++){
printf("%d banme\n",i+1);
printf("name:%s\n",data[i]->name);
printf("age :%3d\n",data[i]->age);
}
return;
}


コンパイル時のエラーメッセージは以下のようになりました。(ファイル名はstructure5.c)
structure5.c: In function ‘main’:
structure5.c:14:2: warning: passing argument 1 of ‘input’ from incompatible pointer type [enabled by default]
input(&data);
^
structure5.c:8:6: note: expected ‘struct property **’ but argument is of type ‘struct property (*)[3]’
void input(property *data[]);
^
structure5.c:15:2: warning: passing argument 1 of ‘output’ from incompatible pointer type [enabled by default]
output(&data);
^
structure5.c:9:6: note: expected ‘struct property **’ but argument is of type ‘struct property (*)[3]’
void output(property *data[]);
^
structure5.c: In function ‘input’:
structure5.c:24:3: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[64]’ [-Wformat=]
scanf("%s",&data[i]->name);
^

構造体の配列をinput関数やoutput関数に渡すときにエラーが発生しているようなのですが、自分で調べても解決できなかったため、質問させて頂きます。
皆様のお知恵を貸してください。なおプログラミング言語自体初心者のため、できる限りわかりやすいお言葉でご教授願います。よろしくお願い致します。

投稿日時 - 2014-04-14 15:42:56

QNo.8554246

すぐに回答ほしいです

質問者が選んだベストアンサー

もちろん
void input(property *data);
にして
int main(void){
 property data[3];

 input(data);
が最も普通 (まあ大きさも渡すと思うけど) です>#7.

配列の各要素は連続して配置されますから,
&data[0] = data,
&data[1] = &data[0]+1 = data+1,
&data[2] = &data[0]+2 = data+2
が (意味のある限りにおいて) 常に成り立ちます. つまり「ポインタの配列」を使う必要性はほとんどありません. しいて言えば「複数の配列のデータを扱うとき」には意味はあるけど, そのような状況があまり想像できない.

あと
void input(property (*data)[3]);
とすると「大きさ 3 の配列」は渡せるけどその他の大きさの配列は渡せなくなります. ただ, 例えば
property data[4][3];
とかいう 2次元配列を
input(data);
のように渡す (ここで &data としちゃうとさらに面倒です) と上のような形が発生します. この 2次元配列との絡みで「3」が省略できない.

投稿日時 - 2014-04-17 10:36:24

お礼

配列はそもそもポインタのようなものだから、わざわざ配列をポインタにする意味がないとわかったのは、今回の大きな収穫でした。


>void input(property (*data)[3]);
>とすると「大きさ 3 の配列」は渡せるけどその他の大きさの配列は渡せなくなります. ただ, 例えば
>property data[4][3];
>とかいう 2次元配列を
>input(data);
>のように渡す (ここで &data としちゃうとさらに面倒です) と上のような形が発生します. この 2次元
>配列との絡みで「3」が省略できない.

すみません、ちょっと難しいです。
今の私には少しレベルが高いようなので、もう少し勉強して、わからなかったらまた質問しに参ります。

御回答ありがとうございました。

投稿日時 - 2014-04-18 00:38:38

ANo.8

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(8)

ANo.7

Wr5

>でもそうすると、配列の要素全てのアドレスをinput関数に渡したいとなると、Wr5さんのおっしゃるように、あたらしくpdata[3]のようなアドレスを代入する配列を新しくつくらないといけないということになりますね。

今回の場合だったら…
void input(property *data);
にして
int main(void){
 property data[3];

 input(data);
でも可能かと。
ただし、input()では配列の要素がいくつあるのか(そもそも配列なのか)は知る方法がありませんけど。

#6に描かれている内容からすると…
void input(property (*data)[3]);
で3個だと…やれるのですかね。
こういう書き方はやったことないので不明ですが。
呼び出し側が3つ以外の配列の場合に面倒になりそうですな…。
# そんな訳で、そういうパターンだと引数1つ追加して個数も渡すようにすることが多いです。

投稿日時 - 2014-04-16 15:05:03

お礼

仰る通りにやってみたら、コンパイル通りました。

void input(property *data);------(1)

void input(property data[]);

void input(property data[3]);

って等価なんですね。
(上記のどの表記にせよ、input関数に渡すのは配列の先頭のアドレスに変わりはない)
でも、(1)の表記はポインタを意識してしまうので紛らわしいですね。(アドレスを渡すのだからポインタっぽくていいと言えばいいのですが…。)

御回答ありがとうございました。

投稿日時 - 2014-04-18 00:32:03

ANo.6

型はなんでも同じなんで int で書いてみる.

int a; → a は int
int a[3]; → a は int×3 の配列
int *a; → a は int へのポインタ
int *a[3]; → a は (int へのポインタ)×3 の配列
int (*a)[3]; → a は (int×3 の配列) へのポインタ
int a(); → a は引数不明の int を返す関数
int *a(); → a は引数不明の (int へのポインタ) を返す関数
int (*a)(); → a は (引数不明の int を返す関数) へのポインタ
int (*a[3])(); → a は ((引数不明の int を返す関数) へのポインタ)×3 の配列

投稿日時 - 2014-04-16 00:04:12

お礼

御回答ありがとうございます。

int (*a)[3]; → a は (int×3 の配列) へのポインタ

の解釈はわかりましたが、

int *a[3]; → a は (int へのポインタ)×3 の配列

の説明がイマイチ理解できていないため、もう少し考えてわからなかったら、別件で新しく質問させて頂きます。

投稿日時 - 2014-04-18 00:11:05

ANo.5

「配列を関数に渡す」とき, 配列名に & を付けるのはたいてい間違いだと思っていいです. 質問文では「input関数やoutput関数に渡すとき」だけを意識していますが, 実は scanf でも警告が出ています.

ちなみに今の例でど~しても
input(&data);
と渡したければ
void input(property (*data)[3]);
というプロトタイプが必要です.

投稿日時 - 2014-04-15 02:16:14

お礼

仰る通り、

void input(property (*data)[3])

と記述すると見事にコンパイルが通りました。

*data[3]と書くと、"data[3]という要素のポインタ"となり、

(*data)[3]と書くと、"要素数3つのdataのポインタ"となるのかな、と私なりに解釈しました。

しかし、

void input(property (*data)[])

のように、要素数を書かないと通りませんでした。不思議です。
しばらく、このことについて調べたり、考えてみようかと思います。

御回答ありがとうございました。

投稿日時 - 2014-04-15 11:43:37

ANo.4

Wr5

>ミイラ取りがミイラになってますよ>#1.

コピペで適当に直しただけでしたから…。
って言い訳も微妙に見苦しいな。

投稿日時 - 2014-04-14 22:11:14

ANo.3

おっととと.

ミイラ取りがミイラになってますよ>#1.

# & が余計

投稿日時 - 2014-04-14 18:18:22

ANo.2

余談.

構造体じゃなくって int の配列だったらどう渡す?

投稿日時 - 2014-04-14 16:51:12

お礼

intの配列の場合を考えようと、試しに以下のようなプログラムを作りました。

#include <stdio.h>

int getsum(int *data[]);

int main(void){
int total, array[5]={13,83,19,25,49};
total=getsum(&array);
printf("%d\n",total);
return 0;
}

int getsum(int *data[]){
int i,sum;
for(i=0;i<5;i++){
sum+=*data[i];
}
return sum;
}

すると、同じようにコンパイルエラーが出ました。
いろいろ調べてみると、(Wr5様への御礼にも書きましたが、)配列を渡す、というのは、配列の先頭のアドレスを渡す、ということで、上記のプログラムでgetsum(&array)として渡すのは間違いで、&記号はいらないわけですね。そして受け取り側も配列の型で受け取ると。
私は無理に配列の要素全てのポインタを渡そうとしていましたが、関数への配列の受け渡しはそもそもアドレスのやりとりですから、ポインタを使う意味はないですね。一応疑問は解決致しました。
ヒントといいますか、的確な道を示して下さり勉強になりました。
もし上述の私の理解に間違いがございましたら、よろしければご指摘頂ければ幸いです。
御回答ありがとうございました。

投稿日時 - 2014-04-14 22:35:51

ANo.1

Wr5

>void input(property *data[]);

「property型へのポインタ」の配列であって、
「property型の配列の先頭要素へのポインタ」ではない…ですよね?

単純に
void input(property *data);
にするべきなのではありませんか?
# 実際には、配列のサイズも渡した方が汎用性(?)は上がりますが。

void input(property *data[]);
でやりたいのならば……

int main(void){
 property data[3];
 property *pdata[3];

 pdata[0] = &data[0];
 pdata[1] = &data[1];
 pdata[2] = &data[2];

 input(&pdata);

かと。

投稿日時 - 2014-04-14 16:10:44

お礼

Wr5様とTacosan様の御回答から、

#include <stdio.h>

typedef struct{
char name[64];
int age;
}property;

void input(property *data[]);
void output(property *data[]);

int main(void){
property data[3];
property *pdata[3];
pdata[0]=&data[0];
pdata[1]=&data[1];
pdata[2]=&data[2];
printf("Input data of three people.\n");
input(pdata);
output(pdata);
return 0;
}

void input(property *data[]){
int i;
for(i=0;i<3;i++){
printf("%d banme\n",i+1);
printf("name:");
scanf("%s",data[i]->name);
printf(" age:");
scanf("%3d",&data[i]->age);
}
return;
}

void output(property *data[]){
int i;
for(i=0;i<3;i++){
printf("%d banme\n",i+1);
printf("name:%s\n",data[i]->name);
printf("age :%3d\n",data[i]->age);
}
return;
}

と書いてみたら、無事コンパイルできました。
こうしてみると、input関数(引数:ポインタ)に渡すときに構造体の配列を
input(&data)として渡すことはできないのですね。
もしかして、配列は先頭のアドレスを渡すから、&は必要ないということですか。なるほど。
でもそうすると、配列の要素全てのアドレスをinput関数に渡したいとなると、Wr5さんのおっしゃるように、あたらしくpdata[3]のようなアドレスを代入する配列を新しくつくらないといけないということになりますね。
もし間違っていましたらご指摘下されば幸いです。
御回答ありがとうございました!

投稿日時 - 2014-04-14 22:12:17

あなたにオススメの質問