数値をscanf()で読んだあと、文字列をfgets()で読む


学生向け

次のようなコードで、数値と文字列を入力するとします。

#include <stdio.h>

int main(){
  int num;
  char ch,str[5];

  printf("num: ");
  scanf("%d",&num);
  printf("num= %d\n\n",num);

  printf("str: ");
  fgets(str,sizeof(str),stdin);
  printf("str= \"%s\"\n",str);

  return 0;
}

実行すると次のようになってしまいます。

num: 10
num= 10

str: str= "
"

新・C言語入門 シニア編 (C言語実用マスターシリーズ)数値をscanfで読み込むとき、数値の後の文字列(改行を含む)はバッファに残ります。すぐ後に文字列を読み込もうとすると、バッファに残った文字列(ここでは改行)をまず読んでしまうので、期待する結果になりません。

このようなscanfの振る舞いと、ここで見た問題の解決法は、林晴比古『新・C言語入門 シニア編』に載っています。いい本です。C言語の本で、「K&Rの他に1冊」と言えば本書でしょう。

この現象の起きるパターンは決まっています。scanf()実行後に、getchar()や、gets()や、scanf()%c変換のような、バッファにあるものをそのまま読み込む動作をしたときに起きます。(p.336)

fgets()を使う今回の例も、このパターンに当てはまります。「scanf("%d%*c",&num);」あるいは「scanf("%d",&num); getchar();」とすれば改行を読み捨てるので問題は解決します。

この方法は、「10 改行」(つまり、1,0,スペース,改行)のような入力で、また無力になります。そこで紹介されているのが次のような方法です。gets()で残りの文字列を全て読み捨てます。

  printf("num: ");
  scanf("%d",&num); gets(str);
  printf("num= %d\n\n",num);

  printf("str: ");
  fgets(str,sizeof(str),stdin);
  printf("str= \"%s\"\n",str);

エキスパートCプログラミング―知られざるCの深層 (Ascii books)関数getsが危険だというのはよく知られています(有名な例が『エキスパートCプログラミング』で紹介されていますね。警告を出すコンパイラもあります)。この例だと、例えばスペースが10個あったらダメですが、次のように書けば大丈夫になります。

  printf("num: ");
  scanf("%d",&num);
  while((ch=fgetc(stdin))!='\n') {};
  printf("num= %d\n\n",num);

  printf("str: ");
  fgets(str,sizeof(str),stdin);
  printf("str= \"%s\"\n",str);

まだどんな入力にも対応できるという段階には達していませんが、とりあえずこのくらいで。

入力文字列が3文字以下だと結果に改行も含まれます(ヌル文字とあわせて5文字)。それ以上だと改行は含まれません(str[4]はヌル文字になります)。

こういう基本的なことを確認しておかないと、少し大きいプログラムは不安で書けないと思いますよ。

追記:Visual C++だと、「scanf()は危険」という警告が出ますが、入力しているのが数値なのでいいでしょう(コンパイラが推奨するscanf_s()はMicrosoft独自の関数です)。

Related posts:

  1. 配列とポインタは別のもの

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>