プロが教える店舗&オフィスのセキュリティ対策術

#include <stdio.h>

int main( void )
{
int air[4];
int i;

i = 0;

printf( "データ入力\n" );
do{
scanf( "%d\t", &air[ i++ ]);
}

while(air[ i - 1 ] > 0 );

printf( "%d\n", i );

return 0;
}
このプログラムはコンパイル出来ますが、期待していたprintf関数での出力ができません。
その理由として何がありますか?関数の使い方が間違っているのか、DO~WHILE文が使えない場所
なのか、もし直すとすればどう直せばいいのですか。

A 回答 (5件)

こんにちは。


整数以外か、0以下の整数が入力されると脱出する仕様で良いのかな?
配列が4つまでしか用意されていないので、値を入れ続けると、飛びますが・・・。
まずは練習と言うことでしょうね。

脱出した時、入力した数が表示される意図のようですね。

一見して問題無さそうに見えますが・・・嫌な予感がしますね。
コンパイラを立ち上げて確認しました。

嫌な予感はしましたが、やはりscanfです。

scanf("%d\t",&air[i++]); ===> scanf("%d",&air[i++]);

私も相当昔にはまったのがscanfです。
今回の例は、入力フォーマットにタブ文字が入ってたからのようです。
改行コードに変えてやってみましたが、やはり不可思議な動作をします。

画面では入力が終わったように見えて、scanfが入力待ちになっています。
入力終了を意味するリターン押下が一回分消えてしまい、
本人(私)は2個目のデータを入力しているつもりが、
最初のscanfを脱出していないようです。

これで一つ入力がズレてしまいます。

脱出条件である、0以外、0以下の整数を入力しても、
入力した気になって、リターン押下は無視されてしまい、実際はまだ入力中と言う事です。

確か、scanfを使ったサンプルは初心者殺しで有名だったと思います。

前述のとおり、タブ文字列を抜いて試してみてください。

以下は蛇足ですが、助言です。

初心者向けの本にはstdioを使ったサンプルが多いのですが、
do,While文もお勧めしませんし、scanf関数もお勧めしません。
いずれは、標準入力に変わる何らかの入力関数を使うようにステップアップすると思いますから、
最初は仕方ないとします。

しかし、
基本的なループ文については、以下の定型で覚えると良いです。

「for if break」 と唱えて覚えましょう。

25年くらいC言語を書いています。
その間に指導もしましたが、初心者を半年でベテラン並みに鍛えるポイントがあります。

「仕組みを教えよう」とか、「スタイルを教えよう」とか誘惑に駆られるものですが、
これはあくまでも知識であって実利に乏しいです。

大事なのは、徹夜でフラフラの状態でも楽にコードが組めるかどうかです。

自己流ですと色々と迷い、どうつくっても大丈夫と思えるものです。
しかし、睡眠不足のときに自分を助けてくれる、疲れない考え方と言うのは存在します。

今回の場合は、
---------------------------------------------------
int air[4];
int i;

for( i=0; i<4; i++ )
{
scnaf( "%d" , &air[i] );
printf( "--->%d\n" , air[i] );

if( air[i] <= 0 ) break;

}

printf( "===>%d\n",i );
---------------------------------------------------
と、for文の役割と脱出条件をわけて書くのがバグを作らないコツです。

for文は、配列等のリソース数をオーバーラン(超えてループする)しない
ための条件だけをシンプルに書きます。

このとき、ループのはじめが0からなのか1からなのか、
大きさで比較するのかイコールを含むのかで、悩みます。

ですのでこう覚えます。

「ループ数は、0からはじまり<で条件を作ると間違いなくその回数ループする」

for( i=0 ; i<回数 ; i++ ) として覚えてしまえば楽です。

演算結果や処理の途中でのイレギュラーにより脱出する場合にifを使います。

if( xxx ) break;

です。こうしておけば、ループが終わる条件を分けて考えることが出来、
後で仕様が変わったときにも直しやすいのです。

例えば、配列の中にあるデータを検索して返却する処理にも応用できます。
---------------------------------------
#define MAXINDEX 20

static int data[MAXINDEX] = { 0, 1, 2 ,3 , 4, 5 };

int sample( int val )
{
int i;
int ret = -1;


for( i=0;i<MAXINDEX; i++)
{
if( data[i] == val )
{
ret = i;
break;
}
}

return( ret );
}
---------------------------------------

何よりも、自分でコードを書くときに、こうやって統一しておくことが大事です。
一つ一つのコードが、自分のルールと反しているところに目が行くようになります。
これでミスが分かる。

長いプログラムを作っても疲れなくなります。

C言語はシンプルすぎて、本を読んで理解する部分が少ないです。
物足りたいと感じて不安になる人が、どうでも良い細かい部分を勉強しがちです。

実際は、一日で1Kラインのプログラムを書く人同士が、寝ないでコードを書き続け、
先に能力が落ちたほうが負けると言う世界です。

疲れないに越したことは無いですよ。

このレベルに達すると、便利な関数を探して使い方を覚えるより早く、自分で作れます。

また、製品として人に売れるもの作るには、プログラミング能力よりもデバッグ能力
が高い人のほうが上にいきます。

ある程度慣れたら。
しっかりとした設計法と試験項目の抽出方法を学ぶと良いと思いますよ。

以上ご参考になれば。
    • good
    • 0
この回答へのお礼

有難うございます。解りやすかったです。

お礼日時:2014/03/08 15:26

あなたの期待する入力、期待する出力をもっと具体的に説明してはいかがでしょうか。


あなたが何を期待しているのか誰にもわからないので、このままではまず的を射た回答は得られないと思います。

一応これまでの質問を読ませてもらいました。
http://oshiete.goo.ne.jp/qa/8503397.html
http://oshiete.goo.ne.jp/qa/8503486.html
これらから予想すると、いくつかの数字を入力させて、それを表示させたいのではないかと思ったのですがどうでしょうか。

まあ、人が書いたプログラムというのも何かの勉強の足しになると思うので一応書いてみます。
プログラムの説明)
ユーザーからn (n <= 4) 個の自然数を受け取り、それをスペースで区切って表示する。
ユーザーが0以下の値を入力すると受け取りが終了する。なお、表示にその0以下の数値は含まれない。
また、数値が4つ入力されると自動的に受け取りを終了し、表示を行う。

動作例)
input?
(ユーザーからの入力)10(改行)
(ユーザーからの入力)25(改行)
(ユーザーからの入力)-1(改行)
10 25

コード)
#include <stdio.h>

#define INPUT_SIZE 4

int
main(void)
{
int input[INPUT_SIZE];
int input_size;
printf("input?\n");
for (input_size = 0; input_size < INPUT_SIZE; input_size++) {
scanf("%d", &input[input_size]);
if (input[input_size] <= 0)
break;
}

for (int i = 0; i < input_size; i++) {
printf("%d ", input[i]);
}
printf("\n");

return 0;
}


あと、http://oshiete.goo.ne.jp/qa/8503486.htmlを見ていて、数値をシャッフルして出力するということもやりたいのかと思ったので、0から9までの数値をシャッフルして表示するプログラムも参考までに乗せておきます。
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMELMS(X) (sizeof(X) / sizeof(X[0]))

int
random_int(int upper_bound)
{
if (upper_bound < 2)
return 0;

int min = ((RAND_MAX % upper_bound) + 1) % upper_bound;
for (;;) {
int r = rand();
if (r >= min)
return r % upper_bound;
}
}

void
shuffle(int x[], size_t s)
{
for (size_t i = 0; i < s; i++) {
int r = random_int(i + 1);
int t = x[i];
x[i] = x[r];
x[r] = t;
}
}

void
print_all(int x[], size_t s)
{
for (size_t i = 0; i < s; i++) {
printf("%d ", x[i]);
}
printf("\n");
}

int
main(void)
{
srand(time(NULL));
int x[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
shuffle(x, NUMELMS(x));
print_all(x, NUMELMS(x));

return 0;
}

一応、すべてFreeBSD 10.0Rのclangでコンパイルして、動作確認はしていますが、C99を理解しないコンパイラーを使っているとコンパイルが出来ないかもしれません。

余談ですが、
okikayo さんのコードでいつも気になるのは配列のサイズ以上に値が入力される可能性を考慮していないことです。これはセキュリティー上の欠陥やプログラムのクラッシュを引き起こす欠陥になりえるので注意したほうがよいです。
あと、好みの問題も多々あると思いますが、関数を使うことや制御構造を変えることを考慮して、それでもどうしてもこの構造がよい時にのみdo-while文を使ったほうがよいと思います。条件の確認があとに来るという構造上、本来実行すべきでないコードを実行してしまったり、配列の添字の取り扱いを間違ったりとミスを起こしやすいですので。
    • good
    • 0
この回答へのお礼

ありがとうございます。まだ始めて一月なもんでわからない事だらけ

お礼日時:2014/03/08 15:24

しつこいようですけど



何を「期待」していて、それがどうなっているのですか?

それがはっきりしないことには、直すことなどできません。

「コンパイルできる」というのは。文法的に間違っていない、というだけです。
プログラムが期待通りになっているかどうかは、まったく関知していません。


「配列airに値を読み込んだ数」なら、現在の記述の通りです。

「配列airに値を読み込んだ**正の値の数**」なら、最後に読んだ「0以下の数」の分を除く必要があります。
あるいは、最初から「正の値の数」だけを数えるようにします。



初級者なら、まずは「日本語」で「手順書」を書きましょう。

そして、その指示通りに自分で「実行」してみましょう。
その時、「書いてある通りにやる」こと。曖昧な言葉とかがあっても勝手に都合のいい解釈はしないことです。

C言語に直すのは、その後です。
    • good
    • 0
この回答へのお礼

ありがとうございました。解りやすかったです。

お礼日時:2014/03/08 15:22

何を期待していて何が出力されたのですか?



それから 90,91,92,93,94,95 等と次々に正の数を入力し続けた時にどうなりますか?

入力が正しくair[]の配列に入ったかどうかを確認しなくてよいのですか?

この程度の簡単なプログラムは自分で1ステップずつ机上デバッグで直ぐに見つけるようにして下さい。
    • good
    • 0

>このプログラムはコンパイル出来ますが、期待していたprintf関数での出力ができません。



以前の質問もそうなのですが、「期待していたprintf関数での出力」と書かれても他者には、それがどのような期待なのかわかりません。

例えば、

#include <stdio.h>
int
main(int argc, char **argv)
{
printf("%d\n", argc);
return 0;
}

上記プログラムで私が「期待する動きをしません。どこを直せばよいでしょうか」といった質問を場合、あなたには上記プログラムに私が期待する動作わかりますか?
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!