ページ 1 / 1
ポインタの基本部分についての質問
Posted: 2015年2月23日(月) 15:42
by kikku
初学者です。下は3人分の名前と年齢と性別を入力、表示するプログラムなのですが
main関数内でscan_student関数とprint_student関数を呼び出す行でエラーが起こりました。
しかしどうしてエラーになるのか分かりません。どういう理由でエラーが起こっているのかどなたか教えてくださいませんか。
コード:
#include<stdio.h>
typedef struct{
char name[64];
int age;
char sex[4];
}student;
void scan_student(student *data[]);
void print_student(student data[]);
int main(void){
student data[3];
scan_student(&data[]);
print_student(data[]);
return 0;
}
void scan_student(student *data[]){
int i;
for(i=0;i<3;i++){
scanf("名前:%s\n",(*data[i]).name);
scanf("年齢:%d\n",(*data[i]).age);
scanf("性別:%s\n",(*data[i]).sex);
}
}
void print_strdent(student data[3]){
int i;
for(i=0;i<3;i++){
printf("名前:%s\n",data[i].name);
printf("年齢:%d\n",data[i].age);
printf("性別:%s\n",data[i].sex);
}
}
Re: ポインタの基本部分についての質問
Posted: 2015年2月23日(月) 16:02
by Rittai_3D
引数の[]は要らないと思います。
[]を外したところコンパイルは通りました。 →
Ideoneでの結果
オフトピック
ドット演算子ではなくアロー演算子を使いましょう。その方が見やすいです。
Re: ポインタの基本部分についての質問
Posted: 2015年2月23日(月) 17:38
by usao
なんかいろいろと間違っている気がします.
コレ↓
コード:
void scan_student(student *data[]){
int i;
for(i=0;i<3;i++){
scanf("名前:%s\n",(*data[i]).name);
scanf("年齢:%d\n",(*data[i]).age);
scanf("性別:%s\n",(*data[i]).sex);
}
}
…の,student *data[] は, student **data と同じ意味ですが,
studentの配列を渡す型として果たして適切でしょうか.
仮に,強引にこの型を使うとしても,
(*data
).name
等は,意図したところを指していないでしょう.
( ( (*data) ).name なら意図した場所を指すかな.)
(あと,scanfには入れる場所を指定する必要があるので,ageの行はさらに間違っている)
一度,studentの配列ではなく,intの配列とかで考えてみてはどうでしょうか.例えば
int data[3];
とかいうのを関数の引数に引き渡したいのだとして,関数を
void f( int **p ){ ... }
とは書かないですよね.
Re: ポインタの基本部分についての質問
Posted: 2015年2月23日(月) 21:47
by かずま
配列、ポインタ、構造体以前の問題をまず解決しましょう。
次のプログラムは、間違っているわけではありません。
しかし、あなたの思っている通りの動作はしないでしょう。
コード:
#include <stdio.h>
int main(void)
{
char name[64];
scanf("名前:%s\n", name);
printf("名前:%s\n", name);
return 0;
}
起動しても何も表示されないので、
と入力してみると、
のように表示されます。
コード:
名前:kikku [Enter]
q [Enter]
と入力すると
と表示されます。
プログラムを次のように書き換えると、
思っていた通りの動くのではありませんか?
コード:
#include <stdio.h>
int main(void)
{
char name[64];
printf("名前:"); scanf("%s", name);
printf("名前:%s\n", name);
return 0;
}
次は、単独の age の読み込みに挑戦してみてください。
その次は、age の配列でやってみてください。
それから、関数 scan_student、print_student へ進みます。
そのあと、構造体というように順を追って進めていきましょう。
Re: ポインタの基本部分についての質問
Posted: 2015年2月23日(月) 23:43
by box
kikku さんが書きました:
main関数内でscan_student関数とprint_student関数を呼び出す行でエラーが起こりました。
どんなエラーが出ているのか明記する方がよいでしょう。
なお、仮にコンパイルが通ったとしても、
kikku さんが書きました:
コード:
void print_strdent(student data[3]){
この関数の名前はおそらくprint_studentが正しいはずで、
スペル違いによるリンク時の外部参照エラーが出ると思います。
Re: ポインタの基本部分についての質問
Posted: 2015年2月24日(火) 03:27
by taaayu
正しくコンパイル出来ました。
► スポイラーを表示
コード:
#include <stdio.h>
typedef struct{
char name[64];
int age;
char sex[10];
}student;
void scan_student(student data[]);
void print_student(student data[]);
int main(void){
student data[3];
scan_student(data);
printf("データを出力します。\n");
print_student(data);
return 0;
}
void scan_student(student data[]){
int i;
for (i = 0; i<3; i++){
printf("名前:");
scanf("%s", data[i].name);
printf("年齢:");
scanf("%d", &(data[i].age));
printf("性別:");
scanf("%s", data[i].sex);
}
}
void print_student(student data[]){
int i;
for (i = 0; i<3; i++){
printf("名前:%s\n", data[i].name);
printf("年齢:%d\n", data[i].age);
printf("性別:%s\n", data[i].sex);
}
}
しょうもないところから一つ一つ見ていきますと
まず初めに性別の配列なんですがsex[4]は少なすぎて危険です。
sex[10]でも怪しいのですが…、name[64]のようにすこし大き目にサイズを用意するのが良いと思います
次にscanfなんですが何人かの指摘が入ってますのでそちらを参考に
ちなみにscanfはint scanf(const char* format, ...);のような形式であり、
format:フォーマット指定文字列
…:入力された値を受け取る
アドレスとなっています。
ポインタではざっくり言うと*(間接演算子)を用いずに表記することでアドレスを示すのでscanfでは*を使わない書き方のほうがよいでしょう
また、今回のようにscanfを使用してアドレスを渡す場合、各メンバの型を考慮して引数の指定を行う必要があります。
(たまたま文字列指定%sでは&をつけなくてもよかった。というか&をつけても問題なく動いちゃいますが何故かは自分で確認するといいと思います。)
また、おそらく質問主さんは配列のアドレスのアドレスを引数に渡したいのかなと思ったので、それで考えてみると…やりたいこととは違う気がしますが一応コンパイルは通りました。
► スポイラーを表示
コード:
#include <stdio.h>
typedef struct{
char name[64];
int age;
char sex[10];
}student;
void scan_student(student (*data)[3]);
void print_student(student data[]);
int main(void){
student data[3];
scan_student(&data);
printf("データを出力します。\n");
print_student(data);
return 0;
}
void scan_student(student (*data)[3]){
int i;
for (i = 0; i<3; i++){
printf("名前:");
scanf("%s", (*data)[i].name);
printf("年齢:");
scanf("%d", &((*data)[i].age));
printf("性別:");
scanf("%s", (*data)[i].sex);
}
}
void print_student(student data[]){
int i;
for (i = 0; i<3; i++){
printf("名前:%s\n", data[i].name);
printf("年齢:%d\n", data[i].age);
printf("性別:%s\n", data[i].sex);
}
}
void scan_student(student *data[]);
void scan_student(student (*data)[]);
void scan_student(student (*data)[3]);はどれも別物です。(おそらく三つめしかうまく動作しません)
結合の関係上うまくいかないみたいです
Re: ポインタの基本部分についての質問
Posted: 2015年2月24日(火) 10:08
by みけCAT
taaayu さんが書きました:まず初めに性別の配列なんですがsex[4]は少なすぎて危険です。
sex[10]でも怪しいのですが…、name[64]のようにすこし大き目にサイズを用意するのが良いと思います
バッファオーバーランの危険がある実装であれば、要素数に関係なくバッファオーバーランの危険があります。
危険を減らすためには、配列の要素数を上げるだけではなく、scanfで最大の入力の長さを指定するといいと思います。
コード:
#include <stdio.h>
int main(void) {
char sex[10];
printf("input sex:");
scanf("%9s", sex); /* 終端文字のぶん要素数-1を指定する */
printf("input = \"%s\"\n", sex);
return 0;
}
Re: ポインタの基本部分についての質問
Posted: 2015年2月25日(水) 01:49
by taaayu
みけCAT さんが書きました:
バッファオーバーランの危険がある実装であれば、要素数に関係なくバッファオーバーランの危険があります。
危険を減らすためには、配列の要素数を上げるだけではなく、scanfで最大の入力の長さを指定するといいと思います。
確かにそうですね…すいません
scanf関数自体、柔軟性が高いのであまり理解できてないんですよね・・・("%[^\n]"や"[0123456789]"や"*c"などなど)
自分が作るとこんな感じになるのかなあ・・・
► スポイラーを表示
コード:
#include <stdio.h>
#include <string.h>
int main(void)
{
char sex[10];
printf("性別を入力してください:");
fgets(sex, sizeof(sex), stdin);
if (sex[strlen(sex) - 1] == '\n') {
sex[strlen(sex) - 1] = '\0'; //10文字未満で入力されたときの改行文字を消す
}
//10文字以上で入力されたときのバッファを消す
fflush(stdin); //C言語では定義されていないためC++での利用になってしまう…
printf("性別:%s\n", sex);
return 0;
}
でもこれだと一つの入力が長ったらしいんですよねえ
なら確かに、
コード:
scanf("%9s", sex);
fflush(stdin);
のほうが全然読みやすいんですよね。なるほど
結構考えずにfgets+sscanfでやってたので見直したほうがよさそうですね
(まあでも個人的にscanfで扱いにくい理由はマクロ置換してくれないとこていう・・・)
Re: ポインタの基本部分についての質問
Posted: 2015年2月25日(水) 10:30
by YuO
taaayu さんが書きました:コード:
scanf("%9s", sex);
fflush(stdin);
のほうが全然読みやすいんですよね。
fflush(stdin);の意図はなんでしょうか。
未定義の振る舞いなので使うべきでは無いですし,特定の環境においての動作を想定しているのであればその環境を提示する必要があります。
# scanfだけなら標準の範囲内なので意味がわかるけれども,fflush(stdin);があるので意図が全く想像できない。
► スポイラーを表示
オフトピック
ISO/IEC 14882:2011はfflush関数に関してISO/IEC 9899:1999の規格を参照するだけで,ISO/IEC 9899:1999では「出力ストリーム」か「最終動作が入力でない更新ストリーム」以外に対して呼び出すとthe behavior is undefinedと明確に記述されています。
refs)
- ISO/IEC 14882:2011
- 1.2 Normative References
- 17.6.1.2 Headers
- 27.9 File-based streams / 27.9.2 C library files
- ISO/IEC 9899:1999
- 7.19.5.2 The fflush function
ISO/IEC 14882:2014に関して,少なくとも
Committee Draftの段階では記述に変更がないようです (Cへの参照のバージョンも含め)。
また,ISO/IEC 9899:2011に関しても,少なくとも
Committee Draftの段階では記述に変更は無いようです
Re: ポインタの基本部分についての質問
Posted: 2015年2月25日(水) 11:08
by みけCAT
taaayu さんが書きました: fflush(stdin); //C言語では定義されていないためC++での利用になってしまう…
C++では定義されているのですか?そのことを示す出典は提示できますでしょうか?
N1256(C言語について書かれた文章)では、7.19.5.2 The fflush functionにおいて
If stream points to an output stream or an update stream in which the most recent
operation was not input, the fflush function causes any unwritten data for that stream
to be delivered to the host environment to be written to the file; otherwise, the behavior is
undefined.
と書かれており、確かにffush(stdin);のふるまいは未定義のようです(stdinが指しているものが入力ストリームの場合)。
しかし、C++について書かれた
N3337および
N4296をSumatraPDFの検索機能を用いてチェックしましたが、
fflushのふるまいがC言語と異なるという記述は見つかりませんでした。
Re: ポインタの基本部分についての質問
Posted: 2015年2月25日(水) 11:55
by 超初級者
質問者さんがいないところで
当初の話とは全然違う方向へ
向かっています。
標準入力に対するfflushの是非に
ついての話を続けたいならば、
別トピックを立てるなどしてください。
このトピックの趣旨とはかけ離れていて、
邪魔です。