質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

structure

このタグは、プログラム言語におけるデータ型structure(構造体)に関するタグです。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

4回答

11713閲覧

構造体とポインタと配列がごっちゃになって困っています。

teityura

総合スコア84

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

structure

このタグは、プログラム言語におけるデータ型structure(構造体)に関するタグです。

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

1クリップ

投稿2018/03/15 17:52

編集2018/03/16 10:35

下記のプログラムのどこがどうダメなのか教えてください。

1.関数の引数に通常の変数を渡すと値渡しとなり、
呼び出し元の変数に変更を加えることができませんが、
関数の引数に配列を渡すと参照渡しとなり、
呼び出し元の関数でも変更を加えることができると認識しています。

2.&data[0]やdataは配列の先頭のアドレスを指しており、
呼び出し先の関数でdata[i]->nameとすれば、
student型data変数の先頭のアドレスから
i個離れたアドレスのname要素にアクセスすると認識しています。

また、*(構造体ポインタ変数名).要素名 と
構造体ポインタ変数名->要素名 の 使い分けもいまいち理解できていません。
(scanf("%s", *(data[i]).name); としてもエラーが出ました)

C言語

1#include <stdio.h> 2#include <string.h> 3 4typedef struct student_tag { 5 char name[64]; 6 int year; 7 char sex; 8} student; 9 10void student_input(student *data/* あるいは data[] ? */, int count); 11void student_print(student *data/* あるいは data[] ? */, int count); 12 13int main(void) { 14 student data[3]; 15 16 student_input(&data[0]/* あるいは data ? */, 3); 17 student_print(&data[0]/* あるいは data ? */, 3); 18 19 return 0; 20} 21 22void student_input(student *data, int count) { 23 int i; 24 char txt; 25 for (i=0; i<count; i++){ 26 scanf("%s", data[i]->name); 27 scanf("%d", data[i]->year); 28 scanf("%s", data[i]->sex); 29 } 30 31 return; 32} 33 34void student_output(student data[], int count) { 35 int i; 36 for (i=0; i<count; i++){ 37 printf("[名前]\t%s\n", data[i]->name); 38 printf("[年齢]\t%d\n", data[i]->year); 39 printf("[性別]\t%s\n", data[i]->sex); 40 } 41 42 return; 43}

下記追記(2018_0316_1907)

ドット演算子を使うべき場面で、アロー演算子を使っていること。
ドット演算子を使うべき場面が分からなくなってきました...

頭の整理のために下記のようなものを書いてみましたが、コメントの通り混乱しております。Wandbox

1.warning: format '%p' expects argument が出ているのは
pdata[0],pdata[1],pdata[2]にアドレスが入ってないからでしょうか。

2.*pdataというポインタ変数を宣言していますが、配列ではなく
pdataという配列を宣言していないのに、pdata[1]など、要素にアクセスしようとするとエラーになるのでしょうか。
pdata[1]はpdataに格納されたアドレスから1個分離れたアドレスではないのでしょうか。
pdata[0]がpdataに格納されたと同じにならないのはなぜでしょうか。

このあたりの解釈について、上手くまとまっているサイトなどはないでしょうか。

C言語

1#include <stdio.h> 2#include <string.h> 3 4typedef struct student_tag { 5 char name[64]; 6 int year; 7 char sex[64]; 8} student; 9 10int main(void) { 11 student data[3]; 12 student *pdata; 13 pdata = data; 14 printf("_data___ = %p\n", data); //先頭のアドレス 15 printf("pdata___ = %p\n", pdata); //先頭のアドレス 16 printf("&data[0] = %p\n", &data[0]); //先頭のアドレス 17 printf("pdata[0] = %p\n", pdata[0]); //先頭のアドレス+0個離れてるアドレスのはずなのに先頭のアドレス+0と違う? 18 printf("_data[0] = %p\n\n", data[0]); //pdata[0]と同じアドレス? そもそも何者? 19 20 printf("&data[1] = %p\n", &data[1]); //先頭のアドレス+1個離れたアドレス 21 printf("pdata[1] = %p\n", pdata[1]); //先頭のアドレス+1個離れてるアドレスのはずなのに先頭のアドレス+1と違う? 22 printf("_data[1] = %p\n\n", data[1]); //謎 23 24 printf("&data[2] = %p\n", &data[2]); //先頭のアドレスから2個離れたアドレス 25 printf("pdata[2] = %p\n", pdata[2]); //先頭のアドレス+2個離れてるアドレスのはずなのに先頭のアドレス+2と違う? 26 printf("_data[2] = %p\n\n", data[2]); //謎 27 28 //strcpy(data->name, "Mario"); //いける 29 //strcpy(data[0]->name, "MARIO"); //構造体ポインタ変数名じゃないからいけない 30 //strcpy(&data[0]->name, "MARIO"); //構造体ポインタ変数名じゃないからいけない 31 //strcpy(pdata[0]->name, "MARIO"); //構造体ポインタ変数名だけど、要素がついたらいけない? そういう決まり? 32 strcpy(pdata->name, "MARIO"); //いける 33 //printf("data[0]->name = %s\n",data[0]->name); //構造体ポインタ変数名じゃないからいけない 34 //printf("pdata[0]->name = %s\n",pdata[0]->name); //構造体ポインタ変数名なのになぜかいけない? 35 printf("data[0].name = %s\n",data[0].name); //いける 36 printf("pdata[0].name = %s\n",pdata[0].name); //いける 37 38 return 0; 39}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

上から順に行きます。既に皆さんの回答とかぶる部分もありますが。

  1. 関数の引数に配列を渡すと...

Cでは、言葉通りの「配列を渡す」という動作は出来ません。配列の先頭要素へのアドレスを渡します。機能としては参照渡しを実現することにはなりますが、やっていることはアドレスの値渡しです。ので、これを参照渡しと言ってしまうことも多いです。
また、引数として配列のような記述をすることは出来ますが、解釈はポインタ型として行われ、要素数を書いてあったとしても無視されます。
void func( char str[128]) --等価--> void func( char *str )

2.&data[0]やdataは、配列の先頭要素へのアドレスを指しています。&dataとすると配列へのアドレスということになるので、厳密に区別する必要がある場合(ほとんどない)は区別します。
注意すべきは...&data[0]やdataは、その通り、配列の先頭要素へのアドレスです。ですが、data[i]は配列のi番目の要素そのものです。なので、その構造体メンバーへのアクセスを記述するならドット演算子.で繋ぐことになります。
&data[i]や(data+i)であればdata変数の先頭のアドレスからstudent型変数i個分離れたアドレスということになり、アロー演算子で繋げば構造体メンバーへのアクセスを行えます。

ポインタと配列の関係(?)をもしかして把握してないかな?
T型の配列arrayとT型へのポインタptrがあったとして、ptr=arrayであってまたNが整数であれば array[N]と(ptr+N)は等価*、というのがCの規則です。
もう一つ、配列が単独で記述されると、いくつかの例外を除き、配列の先頭要素へのポインタとして解釈されるというのもポインタと配列の話をするときに重要な規則。
(ここからN[array]も有効な記法でarray[N]と等価だ、なんていう有名なヘンな記述が導かれるわけですが)

ココで余計なことを。ぜひ。ポインタにおいてはxxポインタとかxxのポインタとかいう言い方でなく、xx'型への'ポインタ、と呼ぶようにしてみて下さい。ちょっとだけですが、これを意識することで何かが変わってくると思います。'型'は面倒なら略してもいいですけど'への'は必須。

話を戻して。
.の結合力はより強いので、(構造体へのポインタ).メンバー名、としないといけません。カッコの付け所が違っています。(構造体へのポインタ).メンバー名だと、(構造体へのポインタ.メンバー名)という解釈になり、ドットの使いどころではないのでエラーになります。
で、参照演算子を付けたらポインタが指している実体そのものになり、構造体そのものなのでメンバーへのアクセスは.を挟んで行うことになります。( ).と->が同じ役割、と言ってもいいでしょうか。

(scanf("%s", *(data[i]).name); としてもエラーが出ました)

繰返しになりますが、data[i]はもはやポインタではない(だって、*(data+i)と等価で、*が付いてるでしょ)ので、
data[i].name
(data+i)->name
とかでないといけません。

ということで...

C

1/* 全般に、仮引数の student *dataとstudent data[]は等価です。 */ 2/* だからといって記法を混ぜるのは礼儀(?)としてやめましょう。 */ 3/* 配列として扱うことを強く意識するなら、student data[]の方が */ 4/* 適切かと私は思います。そのために配列形式の記法が許されているのです */ 5void student_input(student data[], int count); 6void student_print(student data[], int count); 7 8int main(void) { 9 student data[3]; 10 11 student_input(data, 3); 12/* ここも、「配列を渡す」という意識の現れとして&data[0]よりは */ 13/* dataの方が適切でしょう */ 14 student_print(data, 3); 15 16 return 0; 17} 18 19void student_input(student data[], int count) { /* プロトタイプに合わせる */ 20 int i; 21 /* char txt; 未使用です */ 22 for (i=0; i<count; i++){ 23 scanf("%s", data[i].name); 24 scanf("%d", &data[i].year);/* scanfには変数のアドレスを渡しましょう */ 25/* 前述のように、.の結合力は強いので、敢えてカッコをつけると*/ 26/* &data[i].yearは(&data[i]).yearではなく*/ 27/* &(data[i].year)と解釈されます */ 28 scanf("%c", &data[i].sex);/* 既に指摘ありますね。ここは%c */ 29 } 30 31 return; 32} 33 34void student_print(student data[], int count) {/* 関数名どっちが本当? */ 35 int i; 36 for (i=0; i<count; i++){ 37 printf("[名前]\t%s\n", data[i].name); 38 printf("[年齢]\t%d\n", data[i].year); 39 printf("[性別]\t%c\n", data[i].sex); 40 } 41 42 return; 43}

さて、追記パート

ドットとアローの使い分けはもう上記の通り、としかいいようがないです。構造体の実体にはドット、構造体型へのポインタにはアロー。

1.%pにはアドレスを与えなければいけません。pdataやpdata+1,pdata+2ならアドレスですが、pdata[0]はもはやアドレスではありません。入っている/入ってない、ではなく、アドレスなのか、そうでないのか、です。

2.これも前述の通り。ポインタと配列の関係から、pdataというポインタはまた配列として扱えて、pdata[1]は*(pdata+1)と等価です。pdata[1]は、pdataに格納されたアドレスからstudent1個分はなれたところにある構造体の実体、pdata[0]が同じなのは*pdataです。
printf("pdata[0] = %p\n", pdata[0]);も
printf("_data[0] = %p\n\n", data[0]);も、
本来は%pで表示すべきデータではありませんが、現実としてはdata[0]に格納されているデータそのものを表示しているのでしょう。手元のVisual Cでは、data[0].nameに"1234"を入れておいたら34333231が表示されました。'4'が0x34,'3'が0x33, '2'が0x32, '1'が0x31 ですね。

data[0]も、pdata[0]も、構造体の実体を表している、ここをなにか誤解しているのが混乱のもとのような気がします。

投稿2018/03/18 12:19

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teityura

2018/03/18 16:02

混乱していた部分が少しスッキリした気がします。 1.関数の引数として配列のような記述をするとポインタ型として解釈され、要素数を書いても無視される。 void func( char str[128]) --等価--> void func( char *str ) 2.&data[i]や(data+i)はdata変数の先頭のアドレスからstudent型変数i個分離れたアドレスを指す。 3.data[0]も、pdata[0]も、構造体の実体を表しているので、*(data[i]).name)はエラーになり、 data[i].name や (data+i)->name としなければならない。 まだ、たまに迷ったときにはまりそうですが、頭の整理のためにも xx'型への'ポインタ と呼ぶように意識してみます。ありがとうございました。
guest

0

1.関数の引数に通常の変数を渡すと値渡しとなり、

呼び出し元の変数に変更を加えることができませんが、
関数の引数に配列を渡すと参照渡しとなり、
呼び出し元の関数でも変更を加えることができると認識しています。

表面的には、『配列を渡すと要素を変更できる』という認識は正しいです。

ただし... C言語に参照渡しはありません。どちらも値渡しです。
ポインタを値渡しすれば、同じ領域にアクセスすることが出来るだけです。


2.&data[0]やdataは配列の先頭のアドレスを指しており、

概ね正しいです。 (厳密にはdataが配列そのものを示す文脈もあります。)

呼び出し先の関数でdata[i]->nameとすれば、

data[i]の型はstudentであって、student*ではありません。
非ポインタの構造体変数 の要素を参照するときには、ドット演算子を使わねばなりません。

student型data変数の先頭のアドレスから

i個離れたアドレスのname要素にアクセスすると認識しています。

この認識自体は正しいです。


また、*(構造体ポインタ変数名).要素名 と

構造体ポインタ変数名->要素名 の 使い分けもいまいち理解できていません。
(scanf("%s", *(data[i]).name); としてもエラーが出ました)

どちらも同じです。ただアロー演算子を使った方が素直でしょう。
訂正: 間接参照演算子*よりドット演算子.の方が優先順位が高いため、等価ではありません。

追記:
構造体ポインタのメンバにアクセスする、等価な方法は次の二つです。

  • (*構造体ポインタ変数名).要素名
  • 構造体ポインタ変数名->要素名

追記終わり

void student_input(student data/ あるいは data[] ? */, int count);

student *dataでもstudent data[]でも同じです。
しかし個人的には、後者の方が配列の存在を感じられる分好みです。

(ポインタが配列を指さないことも充分あり得る。)

結局何が問題か?

ドット演算子を使うべき場面で、アロー演算子を使っていること。

それ以外の問題

入力周りに問題があります。

C

1scanf("%s", data[i].name); 2scanf("%d", data[i].year); 3scanf("%s", data[i].sex);
  1. scanfの第二引数以降は、格納先のアドレスです。

data[i].nameはポインタを返すので問題ないですが、yearとsexは非ポインタです。
0. 文字は%cで受け取る必要があります。
0. 必要に応じてバッファのゴミを回収せねばなりません

上記の問題をクリアすると次のようになります。

C

1scanf("%s", data[i].name); 2scanf("%d", &data[i].year); 3 4// %cはそのまま使うと改行文字を読んでしまう 5while(getchar() != '\n'); 6scanf("%c", &data[i].sex);

あるいは、一気に読んでしまうのもアリです。

C

1scanf("%s\n%d\n%c\n", data[i].name, &data[i].year, &data[i].sex);

本当はエラーチェックもすべきところですが、ここでは省きます。(c.f. サンプル)

その他

  • student_output と student_print の表記揺れ。
  • 無駄な変数 txt の存在。 (エラーではなく警告だけど)

サンプル

『とりあえず動く』ようにしたコードです。Wandbox

C

1#include <stdio.h> 2#include <string.h> 3 4typedef struct student_tag { 5 char name[64]; 6 int year; 7 char sex; 8} student; 9 10void student_input(student *data/* あるいは data[] ? */, int count); 11void student_print(student *data/* あるいは data[] ? */, int count); 12 13int main(void) { 14 student data[3]; 15 16 student_input(&data[0]/* あるいは data ? */, 3); 17 student_print(&data[0]/* あるいは data ? */, 3); 18 19 return 0; 20} 21 22void student_input(student *data, int count) { 23 int i; 24 for (i=0; i<count; i++){ 25 scanf("%s", data[i].name); 26 scanf("%d", &data[i].year); 27 28 // %cはそのまま使うと改行文字を読んでしまう 29 while(getchar() != '\n'); 30 scanf("%c", &data[i].sex); 31 } 32 33 return; 34} 35 36void student_print(student data[], int count) { 37 int i; 38 for (i=0; i<count; i++){ 39 printf("[名前]\t%s\n", data[i].name); 40 printf("[年齢]\t%d\n", data[i].year); 41 printf("[性別]\t%c\n", data[i].sex); 42 } 43 44 return; 45}

気が向いたので改造もしてみました。Wandbox

C

1#include <stdio.h> 2#include <stdlib.h> 3#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(*arr)) 4 5typedef struct { 6 char name[64]; 7 int year; 8 char sex; // is_man にする余地あり 9} Student; 10 11int scan_student(Student *dst); 12int print_student(const Student *src); 13 14int main(void) { 15 Student data[3]; 16 for(size_t i = 0; i < ARRAY_LEN(data); ++i) { 17 scan_student(&data[i]); 18 print_student(&data[i]); 19 } 20 21 return 0; 22} 23 24int scan_student_without_check(Student *dst) { 25 return scanf( 26 "%s\n%d\n%c\n", 27 dst->name, &dst->year, &dst->sex 28 ); 29} 30int scan_student(Student *dst) { 31 int ret = scan_student_without_check(dst); 32 if(ret != 3) { 33 fprintf(stderr, "Cannot read student data.\n"); 34 exit(1); 35 } 36 37 return ret; 38} 39 40int print_student(const Student *src) { 41 return printf( 42 "[名前]\t%s\n" 43 "[年齢]\t%d\n" 44 "[性別]\t%c\n", 45 src->name, src->year, src->sex 46 ); 47}

まだまだ改良の余地があるとは思いますが。

投稿2018/03/15 18:35

編集2018/03/16 01:36
LouiS0616

総合スコア35660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Eki

2018/03/16 01:07 編集

「*(構造体ポインタ変数名).要素名」は構文エラーではないでしょうか。この書き方をするときは 「*(構造体変数名).ポインタ型メンバ変数」という場合だと思われます。この場合は変数名を括弧でくくる必要はありませんが。 「構造体ポインタ変数名->要素名」と等価なのは「(*構造体ポインタ変数名).要素名」になると思います。
LouiS0616

2018/03/16 01:28

おっしゃるとおりです。訂正しておきます。 ご指摘ありがとうございます。
teityura

2018/03/18 16:06

> ドット演算子を使うべき場面で、アロー演算子を使っていること。 に気を付けます! inputの問題やサンプルまで丁寧に説明いただき、ありがとうございました。
guest

0

C言語の特例ですが、ポインタは配列と同等に扱うことができます
(ここらへんがややこしいところですが)

1.warning: format '%p' expects argument が出ているのは

pdata[0] はアドレスではなく、student 型です
同様に、pdata[1] と *(pdata+1) は同じ student型変数を指してます

2.*pdataというポインタ変数を宣言していますが、配列ではなく

上記の特例から、pdata[1]やpdata[2] という表現は正当なものです

投稿2018/03/16 10:48

y_waiwai

総合スコア87749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

student_input(&data[0]/* あるいは data ? */, 3);

この書き方だと、data[0] のアドレスをstudent_input に渡しています
この関数の内容からすると、data という(配列)変数のアドレスを渡すべきですから、

student_input(&data, 3);

とするのが正解でしょうね。

#ちなみに、上記2通りの書き方では全く同じ値がstudent_input に渡るんで同じ結果になりますが


void student_input(student *data, int count) { int i; char txt; for (i=0; i<count; i++){ scanf("%s", data[i]->name); scanf("%d", data[i]->year); scanf("%s", data[i]->sex); } return; }

この関数の引数、dataは、student配列のアドレスですから、
data[i] で、student配列のi番目の要素を表します
なので、

data[i].name

という記述でないとダメですね。


で、ホンマは、
dataは、student配列のアドレスですから、

void student_input(student *(data[]), int count) {

と定義するのが正解なんですねー

#まーどっちでも同じ結果になるんでええっちゃーえーはなしですが

投稿2018/03/16 00:02

編集2018/03/16 00:20
y_waiwai

総合スコア87749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2018/03/16 13:21

>dataは、student配列のアドレスですから、 配列は3つの例外を除いて常にポインタに読み替えられることを考えると違和感のある理解方法かなぁと
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問