MMGamesロゴ  MMGames
Twitterシェアボタン  Facebookシェアボタン   
 English 

しんで覚えるC言語
しんで覚えるC言語

1行の文字列として入力する

gets関数によるキーボード入力
以前、scanf関数でキーボードから入力する場合に、
入力ミスがあってもプログラムでそれを知ることができないことを説明しました。
対策として、文字列として入力し、後から数値に読み替える方法を説明します。

C言語には、キーボードから1行の文字列を入力するgets関数が用意されています。
なお、gets関数を使うには <stdio.h> を #include する必要があります。
gets関数の使い方は次の通りです。

gets関数
gets(文字配列);

gets関数を実行すると、scanf関数と同様に入力待ち状態になります。
ユーザーがキーボードから入力した文字列は指定した文字配列内に格納されます。

また、ついでに、画面に1行の文字列を表示するputs関数も説明してしまいます。
なお、puts関数を使うには <stdio.h> を #include する必要があります。
puts関数の使い方は次の通りです。

puts関数
puts(文字配列);

指定した文字列が画面に表示されます。また、文字列の最あとで必ず改行されます。
printf関数よりも低機能ですが、文字列の表示だけならばputs関数が簡単です。

次のプログラムは、ユーザーが入力した文字列をそのまま表示する例です。

ソースコード
#include <stdio.h>

int main(void)
{
    char str[32];

    gets(str);
    puts(str);

    return 0;
}

このプログラムの実行結果は次の通りになります。

実行結果 1回目
DRAGON QUEST 3 入力した文字列
DRAGON QUEST 3

入力した文字列がそのまま表示されています。
scanf関数で %s を使用したときと異なり、空白が含まれていても問題ありません。
バッファオーバーラン対策
どうやら、筆者のひねくれた性格は依然として直っていないようです。
なぜならば、説明したばかりのgets関数の使用禁止令を出すつもりだからです。

以前、関数に配列を渡すとどうなるかについて説明したときに、
関数に渡されるのは配列の先頭アドレスであり、要素数は無視されると説明しました。

先ほど、gets関数には文字配列しか渡していませんでした。
ということは、gets関数には、文字配列の要素数がわからないと言うことです。
つまり、要素数を超える入力でも受け付けてしまい、バグにつながるのです。

先ほどのプログラムの要素数は32だったので、31文字以上入力するとバグが発生します。
次の実行結果は、実際に入力して見た例です。

実行結果
0123456789012345678901234567890123456789 入力した文字列


実行してみると、エラー画面が表示され、強制終了されてしまいました。

このような問題がある以上、gets関数は使用できません。
そのかわりに、fgets関数を使用できます。
なお、fgets関数を使うには <stdio.h> を #include する必要があります。
fgets関数の使い方は次の通りです。

fgets関数
fgets(文字配列, 配列の要素数, ファイルポインタ);

最あとでファイルポインタを指定していることからもわかるように、
この関数は、ファイルから文字列を読み込むための関数です。

しかし、実は、C言語では、すべての周辺機器はファイルとして扱うことができます。
キーボードには stdin という名前のファイルポインタが割り当てられています。
この stdin を指定すれば、ファイルから読み込む関数がキーボード用に早変わりします。

周辺機器はファイル扱い
周辺機器のファイル扱いは UNIX というOSで取り入れられた機能で、
現在のコンピュータはほとんどすべてが同様の仕組みを備えています。

配列の要素数を知るには、sizeof関数を使用するのが簡単で確実です。
つまり、次のようにすれば、安全なgets関数のかわりが実現します。

安全なgets関数のかわり
fgets(文字配列, sizeof(文字配列), stdin);


改行文字
gets関数は '\n' を格納しませんが、fgets関数は格納します。
入力の終わりを調べるには '\n' を検索してください。

次のプログラムは、安全な入力方法に書き換えた例です。

ソースコード
#include <stdio.h>

int main(void)
{
    char str[32];

    fgets(str, sizeof(str), stdin);
    puts(str);

    return 0;
}

このプログラムで長い文字列を入力して安全度をテストした結果は次の通りです。

実行結果
0123456789012345678901234567890123456789 入力した文字列
0123456789012345678901234567890

要素数の限界となる部分で入力を打ち切り、バグの発生を防いでいることがわかります。
文字列から数値などを取り出す
これで文字列の入力は万全ですが、まだ問題が残されています。
それは、scanf関数と異なり、数値の入力ができないという点です。
入力した文字列を数値に読み替える必要があります。

文字列を数値に読み替える一番簡単な方法は、atoi関数を使うことです。
すでに説明済みなので、詳しい説明は省きます。
次のプログラムは、入力された数値の2乗を表示する例です。

ソースコード
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char str[32];
    int val;

    fgets(str, sizeof(str), stdin);
    val = atoi(str);
    printf("%d\n", val * val);

    return 0;
}

このプログラムの実行結果は次の通りになります。

実行結果
5
25

入力した文字列に数字以外が含まれていてもatoi関数は無視するので、
scanf関数の時のような、わけのわからない数値になることはありません。
また、元の文字列の文字数を数えれば入力された数字の桁数がわかるので、
大きすぎる数値が入力されたとしてもチェックできます。
なお、実数の場合にはatof関数を使用します。使い方は同じです。

単純に数値を入力するだけであればこれで十分ですが、
scanf関数のように、複数の数値を記号で区切って入力したい場合には、
自前で記号を検索し、それぞれ数値に変換する必要があります。

sscanf関数もあるが
文字列から数値を切り出すsscanf関数もあるのですが、
scanf関数と似た問題があるので、ここでは扱いません。


文字列の中から単語を取り出すにはstrtok関数を使用します。
なお、strtok関数を使うには <string.h> を #include する必要があります。
strtok関数の使い方は次の通りです。

strtok関数
/* 初めの単語を取り出す */
単語のアドレス値 = strtok(文字配列, 区切り文字);

/* 次の単語を取り出す */
単語のアドレス値 = strtok(NULL, 区切り文字);

strtok関数は区切り記号を検索し、その位置を EOS に置き換えます。
文字列を区切り記号の前と後ろに分割できます。
その後、その単語をatoi関数で変換すれば数値を得ることができます。
単語が見つからない場合は NULL を返します。

次のプログラムは、入力された数値の一覧を表示する例です。

ソースコード
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
    int i, j, val[10];
    char str[32], *ch;

    fgets(str, sizeof(str), stdin);

    ch = strtok(str, ",\n");

    for (i = 0; i < 10; i++) {
        if (ch == NULL) {
            break;
        } else {
            val[i] = atoi(ch);
        }
        ch = strtok(NULL, ",\n");
    }

    for (j = 0; j < i; j++)
        printf("%d\n", val[j]);

    return;
}

このプログラムの実行結果は次の通りになります。

実行結果
85,41,26,956,12 入力した文字列
85
41
26
956
12

数字の桁数を大きくしたり、文字列を入力したりしても、
エラーになってしまうことはありません。


本サイトについて

苦しんで覚えるC言語(苦C)は
C言語入門サイトの決定版です。
C言語の基本機能を体系立てて解説しており、
市販書籍と同等以上の完成度です。

第0部:プログラム概要編
  1. プログラムとは何か?
2章:プログラムの書き方
  1. 書き方のルール
  2. 書き方の慣習
  3. 練習問題2
3章:画面への表示
  1. 文字列の表示
  2. 改行文字
  3. 練習問題3
6章:キーボードからの入力
  1. 入力用の関数
  2. 入力の恐怖
  3. 練習問題6
9章:回数が決まっている繰り返し
  1. 繰り返しを行う文
  2. ループ動作の仕組み
  3. 練習問題9
10章:回数がわからない繰り返し
  1. 回数不明ループ
  2. 入力チェック
  3. 練習問題10
13章:複数の変数を一括して扱う
  1. 複数の変数をまとめて扱う
  2. 配列の使い方
  3. 練習問題13
20章:複数のソースファイル
  1. 最小限の分割
  2. 分割の定石
  3. 練習問題20

コメント
COMMENT

💬 コメント投稿欄を開く