LoginSignup
0
1

More than 3 years have passed since last update.

fgetsとsscanfで可変個のint型変数に代入する

Last updated at Posted at 2020-03-03

概要

・fgetsで受け取った文字列を適宜操作しながらsscanfを使って数値を読み取る
・数値が入力されているときの操作は関数にまとめる
・関係のない文字列が入力されていれば読み飛ばす
・その他負の数への対応や入力数が不足している場合への対処などを行う

本題と説明

以下に実際作ったコードがあります。
まず初めにユーザーからの入力をfgetsで一行読み取ります。
その後入力された行を一文字ずつ確認して以下の処理を行います。

値が入力されている場合は次の処理を行う

  1. 関数Adj_String_For_Nextを呼び出す
    1. 入力された数値の桁数を確認する
    2. 必要なだけ数字を空白文字(' ')に置き換える
    3. indexの値を調節し現在のindex_stringの値を返す
  2. is_negativeがtrue(1)ならdata[index_integer]に-1を掛ける
  3. is_negativeをfalse(0)に設定しなおしindex_integerを回す

関係ない文字列が入力されている場合は次の処理を行う

  1. 文字列をsscanfで一時的に読み取る
  2. 読み取った文字列の長さをstrlenを使って求める
  3. 必要なだけ文字を空白文字に書き換える
  4. 添え字index_stringの値を調節する
  5. is_negativeがtrue(1)の状態でこの処理に入った場合はそれをfalse(0)に設定しなおす

その他の処理

現在確認している文字が空白文字であるならばそれはcontinueで読み飛ばす。

また、それがNULL文字('\0')であるならば文字列が終わっているのにもかかわらず
数値を読もうとしているということなので、エラーを表示してプログラムを終了する。


全てを読み終わったら数値をそのまま出力します。

実際のコード

hoge.c
#include <stdio.h>
#include <string.h>
int main()
{
    int Adj_String_For_Next(char[], int, int);  /* 文字列の調整を行う */

    int     N;              /* 受け取りたい整数の数 */
    int     index_integer;  /* 整数用の配列のインデックス */
    int     index_string;   /* 文字列用の配列のインデックス */
    int     is_negative;    /* 負の数かを判別する */
    int     data[100];      /* 整数データ */
    char    line[500];      /* ユーザーの入力行 */

    int     index_temp;     /* temp文字配列のインデックス */
    char    temp[500];      /* 文字列を一時的に受け取る */

    /* 入力 - ここから */
    fgets(line, sizeof(line), stdin);
    sscanf(line, "%d", &N);

    is_negative =  0;
    fgets(line, sizeof(line), stdin);
    for(index_integer = 0, index_string = 0;
        index_integer < N;
        ++index_string)
    {
        /* NULLを読もうとしている -> 入力の数がそもそも足りない */
        if(line[index_string] == '\0'){
            fprintf(stderr, "Error: Numerical lack of input\n");
            exit(7);
        }
        /* 空白文字 -> 読み飛ばす */
        else if(line[index_string] == ' ')
            continue;
        /* ハイフン -> 負の数なのでフラグを付けてindex_stringを回す */
        else if(line[index_string] == '-'){
            line[index_string] = ' ';
            is_negative = 1;
            continue;
        }
        /* ASCII '0' <= line[index_string] <= '9' であれば値を読み込める */
        else if((line[index_string] >= '0') && (line[index_string] <= '9')){
            sscanf(line, "%d", &data[index_integer]);
            index_string
                = Adj_String_For_Next(line, index_string, data[index_integer]);

            if(is_negative == 1)
                data[index_integer] *= -1;

            ++index_integer;
            is_negative = 0;
        }
        /*
         * それ以外は全く関係ない文字列なので長さを読み取って空白文字にする
         * ただし、例えば "-abc" のような文字列であればis_negativeは1のままなので
         * 次に入力される値に-1がかかってしまうからこれを0にする。
         */
        else{
            sscanf(line, "%s", &temp);
            for(index_temp = 0; index_temp < strlen(temp); ++index_temp){
                line[index_string + index_temp] = ' ';
            }
            index_string += (index_temp - 1);
            is_negative = 0;
            continue;
        }
    }
    /* 入力 - ここまで */

    /* 出力 */
    for(index_integer = 0; index_integer < N; ++index_integer){
        printf("%d ", data[index_integer]);
    }
    printf("\n");
    return (0);
}
/*
 *   Adj_String_For_Next -- 入力値によって文字列の調整を行う
 *
 *   引数
 *       line            -- 操作対象の文字列
 *       index_string    -- 操作を行う場所
 *       value           -- 読み取った値
 *
 *   戻り値
 *       操作後にあるべき配列のインデックスの値 -> index_string
 */
int Adj_String_For_Next(char line[], int index_string, int value)
{
    int     index;                  /* 配列のインデックス */
    if(value >= 1000000000)         /* 1000000000以上(10桁) */
    {
        for(index = 0; index < 10; ++index){
            /* (value >= 10^9)であれば10個の数字を空白文字に置き換える  */
            /* '1''0''0''0''0''0''0''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 100000000)     /* 100000000以上(9桁) */
    {
        for(index = 0; index < 9; ++index){
            /* (value >= 10^8)であれば9個の数字を空白文字に置き換える   */
            /* '1''0''0''0''0''0''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 10000000)      /* 10000000以上(8桁) */
    {
        for(index = 0; index < 8; ++index){
            /* (value >= 10^7)であれば8個の数字を空白文字に置き換える   */
            /* '1''0''0''0''0''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 1000000)       /* 1000000以上(7桁) */
    {
        for(index = 0; index < 7; ++index){
            /* (value >= 10^6)であれば7個の数字を空白文字に置き換える   */
            /* '1''0''0''0''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 100000)        /* 100000以上(6桁) */
    {
        for(index = 0; index < 6; ++index){
            /* (value >= 10^5)であれば6個の数字を空白文字に置き換える   */
            /* '1''0''0''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 10000)         /* 10000(5桁) */
    {
        for(index = 0; index < 5; ++index){
            /* (value >= 10^4)であれば5個の数字を空白文字に置き換える   */
            /* '1''0''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 1000)          /* 1000(4桁) */
    {
        for(index = 0; index < 4; ++index){
            /* (value >= 10^3)であれば4個の数字を空白文字に置き換える   */
            /* '1''0''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 100)           /* 100(3桁) */
    {
        for(index = 0; index < 3; ++index){
            /* (value >= 10^2)であれば3個の数字を空白文字に置き換える   */
            /* '1''0''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else if(value >= 10)            /* 10(2桁) */
    {
        for(index = 0; index < 2; ++index){
            /* (value >= 10^1)であれば2個の数字を空白文字に置き換える   */
            /* '1''0' */
            line[index_string + index] = ' ';
        }
        --index;
    }
    else                            /* 1(1桁)*/
    {
        for(index = 0; index < 1; ++index){
            /* (value >= 10^0)であれば1個の数字を空白文字に置き換える   */
            /* '1' */
            line[index_string + index] = ' '; 
        }
        --index;
    }
    return (index_string + index);
}

Adj_String_For_Next関数内のif文が多いかもしれないですが
同じ処理を繰り返し書いているだけで複雑でもなく、大きく可読性が落ちるわけでもないので
特に気にしていません。
また、strlenを数値の場合の文字列操作に適応しても同様に動くと思いますが
わざわざ一個一個場合分けしているのは自分の為です。
確認自体はしていませんが、if-else節を増やせばlong形にも対応できるようになると思います。

追記

可読性に関する指摘がありました。
strlenを使った方法が想像よりも単純な形で実装でき、かつ読みやすくなりました。
ありがとうございました。

Adj_String_For_Next
/*
 *   Adj_String_For_Next -- 入力値によって文字列の調整を行う
 *
 *   引数
 *       line            -- 操作対象の文字列
 *       index_string    -- 操作を行う場所
 *
 *       戻り値
 *          操作後にあるべき配列のインデックスの値 -> index_string
 */
int Adj_String_For_Next(char line[], int index_string)
{
    int     index;             /* 配列のインデックス */
    char    temp[500];         /* 値を一時的に受け取る */

    sscanf(line, "%s", &temp);
    for(index = 0; index < strlen(temp); ++index){
        line[index_string + index] = ' ';
    }
    --index;
    return (index_string + index);
}

fgetsの戻り値及びオーバーフローのチェックに関して指摘がありました。
C言語で安全に標準入力から数値を取得によれば
fgetsの戻り値がNULLであるかつそれが行終端でないならば何かしら入力時にエラーが発生している
ということなので次のようにして入力機構をエラーを検知できるように修正します。
feof関数に関してはこちらのサイトに説明がある通り

ファイルfpの終端指示子をチェックします。

【戻り値】
終端指示子が設定されている場合  : ≠0
終端指示子が設定されていない場合 : =0

とあるのでfeofで受け取った値が0であるときにプログラムを終了させればよいことになります。

fgetsの戻り値のチェック
    char    *is_null;       /* fgetsの戻り値はNULLか */
    char    line[100];      /* ユーザーの入力行 */

    is_null = fgets(line, sizeof(line), stdin);
    if(is_null == NULL){
        if(feof(stdin) == 0){
            fprintf(stderr, "Error: Invalid input\n");
            exit(7);
        }
    }

値のオーバーフローについては次のようなDoes_Overflow関数を作成して対応しました。

Does_Overflow
/*
 *  Does_Overflow       -- 入力の値がオーバーフローかどうかをチェックする
 * 
 *  引数
 *      line            -- 読み取ろうとしている文字列
 *      is_negative     -- 読み取ろうとしている数値が負の数かどうか
 * 
 *  戻り値
 *      オーバーフローを起こすような値であるかどうか
 *          0   - スタックオーバーフローは起きない
 *          1   - スタックオーバーフローが発生する
 */
int Does_Overflow(char line[], int is_negative)
{
    int     temp_i;
    char    temp_c;
    char    temp_str[MAX_LINE]; /* 値を一時的に受け取る */
    sscanf(line, "%s", &temp_str);

    /* 11桁以上=> 確定でオーバーフロー */
    if(strlen(temp_str) > 10){
        return 1;
    }
    /* 9桁以下=> オーバーフローは起きない */
    else if(strlen(temp_str) < 10){
        return 0;
    }
    /* 10桁=> オーバーフローするかどうかを検証する */
    temp_c      = temp_str[9];
    temp_str[9] = '\0';
    sscanf(temp_str, "%d", &temp_i);

    /* 上位7桁が2,147,483,64より大きいなら確定でオーバーフローを起こす */
    if(temp_i > 214748364)
        return 1;
    /* 上位7桁が2,147,483,64より小さいなら確定でオーバーフローは起こらない */
    else if(temp_i < 214748364)
        return 0;
    /* それ以外の場合は一の位を比べて判定する */
    switch (is_negative)
    {
        /* 正の整数であれば一の位は7以下 */
        case 0:
            if((temp_c - '0') < 8)
                return 0;
            else
                return 1;
            break;
        /* 負の数であれば一の位は8以下 */
        case 1:
            if((temp_c - '0') < 9)
                return 0;
            else
                return 1;
            break;
    }
}

恐らくこれでオーバーフローの検知ができるようになったと思います。
fgetsの戻り値のことは今回初めて知りました。
指摘ありがとうございました。

現在の最終的なコード全体は以下のようになります。

#include <stdio.h>
#include <string.h>

const int MAX_LINE = 500;   /* 受け取る文字列の最大の大きさ */
int main()
{
    int Adj_String_For_Next(char[], int);   /* 文字列の調整を行う */
    int Does_Overflow(char[], int);         /* 入力はスタックオーバーフローを起こすかどうか */

    int     N;              /* 受け取りたい整数の数 */
    int     index_integer;  /* 整数用の配列のインデックス */
    int     index_string;   /* 文字列用の配列のインデックス */
    int     is_negative;    /* 負の数かを判別する */
    int     does_overflow;  /* 入力値はオーバーフローを起こすか */
    int     count_overflow; /* オーバーフローを起こした値を数える */
    int     data[100];      /* 整数データ */
    char    *is_null;       /* fgetsの戻り値はNULLか */
    char    line[MAX_LINE]; /* ユーザーの入力行 */

    int     index_temp;     /* temp文字配列のインデックス */
    char    temp[MAX_LINE]; /* 文字列を一時的に受け取る */

    /* 入力 - ここから */
    is_null = fgets(line, sizeof(line), stdin);
    if(is_null == NULL){
        if(feof(stdin) == 0){
            fprintf(stderr, "Error: Invalid input\n");
            exit(7);
        }
    }
    sscanf(line, "%d", &N);

    is_negative =  0;
    count_overflow = 0;
    is_null = fgets(line, sizeof(line), stdin);
    if(is_null == NULL){
        if(feof(stdin) == 0){
            fprintf(stderr, "Error: Invalid input\n");
            exit(7);
        }
    }
    for(index_integer = 0, index_string = 0;
        index_integer < N;
        ++index_string)
    {
        /* NULLを読もうとしている -> 入力の数がそもそも足りない */
        if(line[index_string] == '\0'){
            fprintf(stderr, "Error: Numerical lack of input\n");
            fprintf(stderr, "%d Value(s) are skipped because of overflow\n", count_overflow);
            exit(7);
        }
        /* 空白文字 -> 読み飛ばす */
        else if(line[index_string] == ' ')
            continue;
        /* ハイフン -> 負の数なのでフラグを付けてindex_stringを回す */
        else if(line[index_string] == '-'){
            line[index_string] = ' ';
            is_negative = 1;
            continue;
        }
        /* ASCII '0' <= line[index_string] <= '9' であれば値を読み込める */
        else if((line[index_string] >= '0') && (line[index_string] <= '9')){
            /* オーバーフローが起こるかどうかをチェックする */
            does_overflow
                = Does_Overflow(line, is_negative);
            /* オーバーフローが起こらないなら代入を行いindex_integerを回す */
            if(does_overflow == 0){
                sscanf(line, "%d", &data[index_integer]);
                if(is_negative == 1)
                    data[index_integer] *= -1;
                ++index_integer;
            }
            else
                ++count_overflow;
            /* オーバーフローが起きても起きなくても文字列を処理する */
            index_string
                = Adj_String_For_Next(line, index_string);

            is_negative = 0;
        }
        /*
         * それ以外は全く関係ない文字列なので長さを読み取って空白文字にする
         * ただし、例えば "-abc" のような文字列であればis_negativeは1のまま
         * なので次に入力される値に-1がかかってしまうからこれを0にする。
         */
        else{
            sscanf(line, "%s", &temp);
            for(index_temp = 0; index_temp < strlen(temp); ++index_temp){
                line[index_string + index_temp] = ' ';
            }
            index_string += (index_temp - 1);
            is_negative = 0;
            continue;
        }
    }
    /* 入力 - ここまで */

    /* 出力 */
    for(index_integer = 0; index_integer < N; ++index_integer){
        printf("%d ", data[index_integer]);
    }
    printf("\n");
    return (0);
}
/*
 *   Adj_String_For_Next -- 入力値によって文字列の調整を行う
 *
 *   引数
 *       line            -- 操作対象の文字列
 *       index_string    -- 操作を行う場所
 *
 *       戻り値
 *          操作後にあるべき配列のインデックスの値 -> index_string
 */
int Adj_String_For_Next(char line[], int index_string)
{
    int     index;                  /* 配列のインデックス */
    char    temp[MAX_LINE];         /* 値を一時的に受け取る */

    sscanf(line, "%s", &temp);
    for(index = 0; index < strlen(temp); ++index){
        line[index_string + index] = ' ';
    }
    --index;
    return (index_string + index);
}
/*
 *  Does_Overflow       -- 入力の値がオーバーフローかどうかをチェックする
 * 
 *  引数
 *      line            -- 読み取ろうとしている文字列
 *      is_negative     -- 読み取ろうとしている数値が負の数かどうか
 * 
 *  戻り値
 *      オーバーフローを起こすような値であるかどうか
 *          0   - スタックオーバーフローは起きない
 *          1   - スタックオーバーフローが発生する
 */
int Does_Overflow(char line[], int is_negative)
{
    int     temp_i;
    char    temp_c;
    char    temp_str[MAX_LINE]; /* 値を一時的に受け取る */
    sscanf(line, "%s", &temp_str);

    /* 11桁以上=> 確定でオーバーフロー */
    if(strlen(temp_str) > 10){
        return 1;
    }
    /* 9桁以下=> オーバーフローは起きない */
    else if(strlen(temp_str) < 10){
        return 0;
    }
    /* 10桁=> オーバーフローするかどうかを検証する */
    temp_c      = temp_str[9];
    temp_str[9] = '\0';
    sscanf(temp_str, "%d", &temp_i);

    /* 上位7桁が2,147,483,64より大きいなら確定でオーバーフローを起こす */
    if(temp_i > 214748364)
        return 1;
    /* 上位7桁が2,147,483,64より小さいなら確定でオーバーフローは起こらない */
    else if(temp_i < 214748364)
        return 0;
    /* それ以外の場合は一の位を比べて判定する */
    switch (is_negative)
    {
        /* 正の整数であれば一の位は7以下 */
        case 0:
            if((temp_c - '0') < 8)
                return 0;
            else
                return 1;
            break;
        /* 負の数であれば一の位は8以下 */
        case 1:
            if((temp_c - '0') < 9)
                return 0;
            else
                return 1;
            break;
    }
}

問題点として0だけが11個続くような値が入力されるとそれをオーバーフローを起こす数値として
処理してしまうことが挙げられますが、解決策が浮かばないのでしばらくは見なかったことに
するしかなさそうです。

終わりに

今回は入力文字列の一文字目が数字の場合はその後に文字列が続いていようが
その値を数値として読むようにしています。
実際に組んでないので詳しいことは分かりませんが
前半に数値があるパターンを考えるとなると何となく処理が複雑になりそうです。
完全に感想ですが、変数名の初めに数字が使えないことと関係してくるのかな、と思います。

0
1
5

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
1