日記

日本語の勉強のためのブログ

【C言語】変数内の複数行文字列をfor文で一行ずつ読み込む方法

分かりづらいタイトルで申し訳無い…

やりたいこと

例えば

1,2
3,5
3,7
...

のような複数行の文字列が入った変数txtが与えられたとしよう。つまり、

char *txt = "1, 2\n3, 5\n3,7\n";

といった形で与えられている状況を想定する。

このとき、

int i, n, length = 3;    // lengthはtxtの行数
int a[length], b[length];
char *txt = "1, 2\n3, 5\n3,7\n";
for (i = 0; i < length; i++) { 
    sscanf(txt, "%d,%d", &a[i], &b[i]);
}

のようにfor文で一行ずつ取り出して、各行ごとに処理を行いたい。この例では、カンマに区切られた2つの数を配列a, bに代入することが「処理」にあたる。  

問題

ファイルに書かれた文字列や標準入力から受け取った文字列ならfscanfが使えるが、すでに変数に格納されている文字列については使えない。

そのためsscanfを使うしかないように思うのだが、そうすると別の問題が発生してしまう。
fscanfは現在の読み込み位置を示すポインタを勝手に進めてくれるため上のコードでも動くが、sscanfにはそうしたポインタがなく、次呼び出したときはまた文字列の最初から読み込むことになってしまうため、正常に動作しないのだ。

例えば上の例に挙げたコードを実行すると、延々と1行目のみを読み込むこととなる。
そのため、実際の読み込み結果は
a = {1, 1, 1, ...}, b = {2, 2, 2, ...}
となってしまい、意図していた結果
a = {1, 3, 3, ...}, b = {2, 5, 7, ...}
とは異なる結果が返されてしまう。

%nを使う

そこでsscanfのフォーマット指定子"%n"を用いる。

www.tamasoft.co.jp

これを用いると、読み込んだバイト数を取得することができる。

例えば

int a, b, n;
char *txt = "1,2";
sscanf(txt, "%d,%d%n", &a, &b, &n);
printf("%d", n);

とすると、3が出力される。これは%nの直前までに3文字("1,2")を読み込んだからである。

よって%nを利用すれば、以下のような方法で前回の続きから読み込むことが可能となる。

  1. sscanf時に、%nを用いて適当な変数nに読み込んだバイト数を格納する
  2. 文字列変数のポインタをnだけ進める
  3. その文字列変数を用いてsscanfを行う。以下繰り返し

これをコードにした例を以下に示す。

int main(void) {
    int i, n, length = 3;    // lengthはtxtの行数
    int a[length], b[length];
    char *txt = "1, 2\n3, 5\n3,7\n";
    for (i = 0; i < length; i++) {           
        sscanf(txt, "%d,%d%n", &a[i], &b[i], &n);
        txt += n;            // 文字列の開始位置をn文字進める
        printf("%d,%d\n", a[i], b[i]);
    }
    return 0;
}

実行結果は以下のようになり、各行を読み込めていることが確認できる。

1,2
3,5
3,7