36章 テキストの読み書きその2

http://www.geocities.jp/ky_webid/c/036.html

fprintfとfscanfというのが出てきました。

名前の通りprintf、scanfとほぼ同じ仕様です。

というか

fprintf( stdout, "%s\n", str );
fscanf( stdin, "%d", &num );

のように書けば同じ動作になるのでファイルポインタを明示的に指定できるのがfprintfだということなんでしょう。

問題1

氏名、年齢、性別が5人分書かれた次のようなテキストファイルを読み込んで、画面に表示するプログラムを作って下さい。このとき、構造体を利用するようにして下さい。(日本語は1文字で、2バイトの領域を必要とします)

田中 31 男
河合 24 男
鈴木 22 女
伊藤 40 女
杉田 37 男

fscanfを使う問題ですね。やってみましょう。

int main () {
    FILE *fp;
    struct {
        char name[5];
        int age;
        char sex[3];
    } client;
    int i;
    
    fp = fopen("text.txt","r");
    if ( fp == NULL ) {
        return 1;
    }
    
    for(i=0;i<5;++i){
        fscanf(fp,"%4s %d %2s",client.name,&client.age,client.sex);
        printf("名前:%s 年齢:%d 性別:%s\n",client.name,client.age,client.sex);
    }
    
    fclose(fp);
    return 0;
}

とりあえず当初5行決めうちでやってみたんですが、後からwhileとfeof(fp)でも出来るんじゃないかと思って変更してみました。

int main () {
    FILE *fp;
    struct {
        char name[5];
        int age;
        char sex[3];
    } client;
    
    fp = fopen("text.txt","r");
    if ( fp == NULL ) {
        return 1;
    }
    
    while(1){
        fscanf(fp,"%4s %d %2s",client.name,&client.age,client.sex);
        if ( feof(fp) ) {
            break;
        }
        printf("名前:%s 年齢:%d 性別:%s\n",client.name,client.age,client.sex);
    }
    
    fclose(fp);
    return 0;
}

うまくいきました。これで5人分じゃなくてもうまく動くので良い感じじゃないでしょうか

答え合わせ

上のサンプルプログラムでは、読み込む文字数に制限を加えましたが、仮に21バイト以上の文字列がテキストファイルに書かれていて、読み込む文字数を20バイトに制限すると、読み込みは20バイト目で止まります。その直後の%dフォーマットによる読み込みのとき、文字列の21バイト目のところから読み込みが行われてしまい、結局うまくいきません。厳密にエラーチェックを行うには、色々と面倒なことをしないといけません。今回はサンプルということで、そこまで厳密なことはしていません。

なるほど。でも実際のファイルの読み込み処理ではこういう例外に対応しないといけないのでその辺の厳密にチェックする方法も後々紹介されるんでしょうか。気になるところです。

問題2

九九表を作り、テキストファイルに書き出すプログラムを作って下さい。(九九表は以前、第25章 の問題1で作りました)

吐き出すだけなので簡単そうですね。fprintfを使います。

int main () {
    int array[9][9];
    int i,j;
    FILE *fp;
    
    fp = fopen("99.txt","w");
    if ( fp == NULL ) {
        return 1;
    }
    
    for ( i=0; i<9; i++ ) {
        for ( j=0; j<9; j++ ) {
            array[i][j] = (i+1) * (j+1);
            fprintf(fp,"%02d ",array[i][j]);
        }
        fprintf(fp,"\n");
    }
    
    fclose(fp);
    return 0;
}

非常に簡単ですね。25章の時に作った九九の処理を持ってきてprintfをfprintfに変えたくらいであとは殆どいじってませんw

問題3

問題2で生成したテキストファイルから、キーボードで指定された数の段だけを画面に表示させるプログラムを作って下さい。1〜9以外の入力があったときはエラーメッセージを表示させます。なお、テキストファイルからの読み込みにはfscanf関数を使って下さい。

なるほど。指定された場所の行だけを表示させるわけですね。またfscanf縛りがあるので注意が必要です。

int main () {
    FILE *fp;
    int num,i,j,line;
    
    fp = fopen("99.txt","r");
    if ( fp == NULL ) {
        return 1;
    }
    
    printf("1〜9の間で入力してください\n");
    scanf("%d",&num);
    if ( num < 1 || num > 9 ) {
        printf("入力エラーです\n");
        return 1;
    }
    
    for(i=1;i<10;++i){
        if ( i == num ) {
            for(j=0;j<9;++j) {
                fscanf(fp,"%2d",&line);
                printf("%02d ",line);
            }
            break;
        }
        else {
            // 指定行までは取得しても捨てる
            for(j=0;j<9;++j) {
                fscanf(fp,"%2d",&line);
            }
        }
    }
    
    fclose(fp);
    return 0;
}

実装方法ですが、かなり迷いました。

最初

fscanf(fp1,"%2d %2d %2d %2d %2d %2d %2d %2d %2d ",&line[0],&line[1],&line[2],&line[3],&line[4],&line[5],&line[6],&line[7],&line[8]);

みたいにしてたのですが、どうも綺麗じゃないなぁと思い、%2dのマッチを9回繰り返せば良いということに気付き、今の形になりました。

他にもっと良い実装があるのでしょうか。解答例が気になるところです。

答え合わせ

ほぼ同じ感じの実装で安心しました。

僕の場合、大外のループは一つにして指定行の場合は処理を変更するという実装になっていますが、解答例の方ではそもそも大外のループを指定行よりひとつ前までループしておいて、あとから指定行の内容を取得するという処理になってます。

解答例の方が読みやすいですね。処理は適切に分けるべきだと思いました。