トップC言語入門 > データの読み込み(scanf関数)

データの読み込み(scanf関数)

1.書式

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

2.書式の決まったデータの読み込み

例えば、日付は 2013/3/10 、2013年3月10日、 2013.3.10 のようにある決まったフォーマットで表記されることが多い。 このようなある決まった書式から年、月、日にあたる 2013, 3, 10 を取り出すには次のようにする。

  int year, month, day;
  sscanf("2013/3/10", "%d/%d/%d", &year, &month, &day);
  sscanf("2013年3月10日", "%d年%d月%d日", &year, &month, &day);
  sscanf("2013.3.10", "%d.%d.%d", &year, &month, &day);

文字の読み込み

1バイト領域に読み込み場合は %c である。 但し、sscanf("1 45", "%c %d", &c, &d); としたとき、c に格納されるのは 1 ではなく 文字 '1' のアスキーコード 49 である。 printf("%c", c) とすれば 1 が出力されるが、 printf("%d", c) とすれば 49 が出力される。

#include <stdio.h>

int main() {
    char c;
    int d;

    sscanf("1 45", "%c %d", &c, &d);      // 正しい
    printf("'%c' %d %d\n", c, c, d);      // '1' 49 45

    sscanf("1 45", "%d %d", &c, &d);      // 間違い(1バイトエリアに4バイトは読み込めない)
    printf("'%c' %d %d\n", c, c, d);      // 実行時にエラーが起きる
}

3.読み込む文字列数の制限

文字列は書式指定子 %s で読み込める。 しかし、この場合、用意したメモリ領域を超える長さの文字列が入力されるとエラーが起こり、 プログラムが暴走することも多い。

用意したバッファを溢れることを避けるには %5s、%127s のように指定する。 文字列の末尾にヌル文字 '\0' を置くため、入力できる文字数はメモリのサイズより1バイト小さくなる。

#include <stdio.h>

int main() {
    char buf[6], buf2[16];

    scanf("%5s", buf);
    printf("'%s'\n", buf);

    scanf("%15s", buf2);
    printf("'%s'\n", buf2);
}

上のプログラムで、123456789 を入力し、エンターキーを押した場合、 最初の 6バイトサイズのバッファ buf には入りきらない。 %5s としているので、先頭から5文字だけが bufに読み込まれる。

溢れた残りの 6789 は捨てられるわけではなく、その後、scanf関数を実行した場合、 こちらのバッファ buf2 に読み込まれることに注意を要する。

c:\mh\mcc>scanf01
123456789
'12345'
'6789'

4.スキャン集合指定子

scanf関数では多数のフォーマット指定子が使用される。 殆どが printf関数で使われるフォーマット指定子であるが、scanf関数独特のものとして、 スキャン集合指定子がある。

これは、%s や %d の代りに %[a-zA-Z]、%[0-9] を使う。 それぞれ英字のみ、10進数のみ読み込むことを意味し、それ以外の文字が現れれば、読み込みは終わる。

%[^a-zA-Z] と書けば、英字以外となる。

これらは正規表現の表記法であるため、正規表現について知っておく方がよい。

初心者には少し難しいかと思うが、C言語をマスターしょうという場合には、 集合演算子を使いこなせるようになって貰いたい。

プログラム実行時の引数(コマンドパラメータ)は文字列として引き渡される。 ある引数が att532-30 とか pcb1173 のように、1文字以上英字が続き、その後に10進数が続く。 更にその後に - 符号と10進数が続くケースもあるものとする。 英字の数は15文字未満、数値は int 型の範囲にあるものとする。

このような引数を "att", 532, 30 および "pcb", 1173, 0 のように、分解することを考える。

この場合、次のようにすればよい。

        sscanf(argv[1], "%[a-zA-Z]%d-%d", alpha, &num1, &num2);

プログラム例を下に示す。

[test.c]
#include <stdio.h>

int main(int argc, char *argv[]) {
    char alpha[16];
    int  num1 = 0, num2 = 0;

    if (argc > 1) {
        sscanf(argv[1], "%[a-zA-Z]%d-%d", alpha, &num1, &num2);
        printf("alpha=%s, num1=%d, num2=%d\n", alpha, num1, num2);
    }
    return 0;
}

実行例を下に示す。2番目の例では num2 の読み込みは起こらず、初期値の 0 が使われる。

c:\sys>test att532-30
alpha=att, num1=532, num2=30

c:\sys>test pcb1173
alpha=pcb, num1=1173, num2=0

読み込む文字数をバッファサイズに合わせて 15文字以下とするには "%15[a-zA-Z]%d-%d" とすればよい。 しかし、16文字以上の場合、読み残された英字を %d が誤って取り込むため、数値が誤った値となる。 従って、誤入力に対応するには、文字列のバッファサイズは大きくしておき、 argv[1]の長さがバッファサイズ以上のときはエラーとする、という風にするのが簡単である。

5.fscanf_s関数

文字列入力では、入力バッファのサイズをオーバーしないように注意がいる。 VC では scanf関数の代りに scanf_s関数を使うことが推奨されている。 下に、プログラム例を示す。

なお、入力データが "taro,10,10.12" のように、スペース区切りではなく、カンマ区切りの場合、 "taro,10,10.12"の全体が最初の %s でstudents[n].nameに取り込まれてしまうため、 この方法では正しく読み込めない。

#include 

typedef struct _Student {
    char name[60];
    int  age;
    double weight;
} Student;
Student students[10];

int main() {
    FILE *fp = fopen("students.txt", "r");
    for (int n = 0; n < 10; n++) {
        int nRead = fscanf_s(fp, "%s %d %lf\n", students[n].name, 60, &students[n].age, &students[n].weight);
        if (nRead == EOF) break;
        printf("%s %d %lf\n", students[n].name, students[n].age, students[n].weight);
    }
}
[students.txt]
taro 10 10.12
hanako 8 8.34