0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C言語】scanfでデータの組内の文字を読む方法

Posted at

最初の行にデータ数が書かれていて、次以降の行にデータが1行に1組書かれている形式の入力を考える。
この記事では、データ数は100以下、「整数」は int 型で表現できる範囲の整数とする。

整数のみからなるデータの読み込み

このように、データが整数のみからなる場合は、scanf の書式 %d を用いて読み込むことができる。

3
10 20
31 41
52 62

自分の書き方は、以下のような感じである。

#include <stdio.h>

int N;
int a[128], b[128];

int main(void) {
	int i;
	if (scanf("%d", &N) != 1) return 1;
	for (i = 0; i < N; i++) {
		if (scanf("%d%d", &a[i], &b[i]) != 2) return 1;
	}

	/* 読み込んだデータの処理を行う */

	return 0;
}

文字を含むデータの読み込み (旧)

次に、以下のようにデータの組に文字を含む場合を考える。

3
15 a
26 b
37 c

文字を読み込むとき、%c を用いると、%d と違って空白を読み飛ばさないため、トラブルの原因になることがありそうである。
なので、読み込むのが1文字の場合でも、%s を使って読み込むことにしている。
このとき、一旦文字列用の配列を用意してそこに格納し、そこからデータ用の配列にデータを移していた。

#include <stdio.h>

int N;
int a[128];
char b[128];

int main(void) {
	int i;
	if (scanf("%d", &N) != 1) return 1;
	for (i = 0; i < N; i++) {
		char b_buf[4];
		if (scanf("%d%3s", &a[i], b_buf) != 2) return 1;
		b[i] = b_buf[0];
	}

	/* 読み込んだデータの処理を行う */

	return 0;
}

b_buf[0] のかわりに *b_buf を用いてもよい。

文字を含むデータの読み込み (新)

よく考えると、このように

  • データのうち文字だけを1個の配列にまとめて読み込む
  • 読み込み先の配列の要素数に余裕がある

場合は、%d で整数を読み込むときと同じ書き方で、%s で文字を読み込むことができることがわかる。

#include <stdio.h>

int N;
int a[128];
char b[128];

int main(void) {
	int i;
	if (scanf("%d", &N) != 1) return 1;
	for (i = 0; i < N; i++) {
		if (scanf("%d%3s", &a[i], &b[i]) != 2) return 1;
	}

	/* 読み込んだデータの処理を行う */

	return 0;
}

このとき、書式 %3s により b[i] だけでなくその後の数要素にもデータが書き込まれるが、このデータは次以降のデータ行を読み込む際に上書きされ、最初の文字 (読み込みたいデータの文字) だけが残る。

ただし、この方式は最初の条件が成り立たず、文字を格納する場所の後に余計なデータを書き込んでよい領域が無い場合には使えないことに注意したい。
すなわち、たとえば

  • データを1組ずつ構造体などにまとめて読み込む場合
  • 読み込み先の配列の要素数に余裕が無い場合

には使えない。
(パディングなどの関係で問題が起こらないかもしれないが、気持ち悪いしやめたほうがいいだろう)

#include <stdio.h>

int N;
struct data_s {
	int a;
	char b;
} data[128];

int main(void) {
	int i;
	if (scanf("%d", &N) != 1) return 1;
	for (i = 0; i < N; i++) {
#if 0
		/* NG (範囲外への書き込みが発生する) */
		if (scanf("%d%3s", &data[i].a, &data[i].b) != 2) return 1;
#else
		/* OK (従来の方法) */
		char b_buf[4];
		if (scanf("%d%3s", &data[i].a, b_buf) != 2) return 1;
		data[i].b = b_buf[0];
#endif
	}

	/* 読み込んだデータの処理を行う */

	return 0;
}

結論

文字データを文字の配列に読み込み、読み込んだデータを書き込む要素の後の要素に余計なデータを書き込んでもよい場合は、整数を %d で読み込むのと同様の書き方で文字を %s で読み込むことができることがわかった。
これにより、わざわざ文字列用の配列を別に確保しなくてよくなり、コードがスッキリすることが期待できる。

しかし、このように「余計なデータが書き込まれるが無視できる」ということは一見してわかりにくく、「文字の読み込みなのに誤って %c ではなく %s を使っている!」と誤解してしまう原因になりうる。
競技プログラミングであれば用いてもよいかもしれないが、より高いわかりやすさやメンテナンス性が求められる場面では使用を避けたほうがいいかもしれない。
さらに重要な処理では、scanf 自体の使用を避けるべきという主張も…?

そもそも、%c の前に空白を置くことにより空白を読み飛ばせばいいという説も…?

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?