Escape from the Memories Planet

思い出星職人をめざし、かつ、とどまらないブログ

C言語でPython風input関数

C言語scanf関数は、競技プログラミングなど入力が正確な場合は非常に便利ですが、人間による入力が絡む場合や、違った型で複数回の入力をする時にはなるべく避けたいものです。

というわけで、Pythonに存在する便利な関数inputC言語で再現してみました。

Pythonのinput関数

Pythonにあるinput関数は以下のように使います。

a = int(input("a : "))
b = int(input("b : "))
print(f"{a} + {b} = {a+b}")

実行例

a : 5
b : 10
5 + 10 = 15

input関数は受け取った引数をプロンプトとして表示し、改行までの入力を受け取って文字列として返すというものです。

上記のプログラムでは受け取った文字列をint型にキャストして計算し、表示しています。

C言語で真似てみる

C言語でも、プロンプト内容(ディスクリプションとし、引数名discとしています。)を受け取り、標準入力で受け取った文字列のポインタを返す関数として実装すればいいわけです。

input.c

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#ifndef _MEM_
#define _MEM_ 256
#endif

char _input_str[_MEM_][_MEM_];
int _input_str_index = 0;

char* input(char *disc, ...) {
    if (_input_str_index == _MEM_) {
        printf("!!coution!! : buffers are lacked!\n");
        // return NULL;
        _input_str_index = 0; // 古いものから上書き
    }

    va_list args;
    va_start(args, disc);
    vprintf(disc, args);
    va_end(args);

    fgets(_input_str[_input_str_index], _MEM_, stdin);
    
    // '\n'削除

    int len = strlen(_input_str[_input_str_index]);
    
    if (_input_str[_input_str_index][len-1] == '\n') {
        _input_str[_input_str_index][len-1] = '\0';
    }

    return _input_str[_input_str_index++];
}

予め、_MEM_個分、_MEM_文字配列を二次配列_input_strとして確保しています。1

本当はmalloc関数で逐次メモリを確保するようにしたかったのですが、free関数を外側で呼ぶ必要があるのは嫌だったので、予め確保するというスタイルを取ってみました。

_input_str_indexによって入力された文字列の連番を管理しています。_MEM_個以上の入力があった場合の例外処理ですが、とりあえず思いつかなかったので警告を出したうえで上書きする仕様としました。

肝心の標準入力はfgets関数で行っています。fgets関数は第一引数に保存用の文字列のポインタ、第二引数に確保されている文字列長(要は最大文字数)、第三引数に読み込むファイルポインタ(今回は標準入力なのでstdin)を指定する関数です。

scanfと異なり、最大文字数以上は切り捨てられるのでバッファオーバーランの危険性がありませんし、改行まできっちり受け取るのでストリーム上(標準入力上)に変なものが残らず安全です。

逆に前述の通り改行まで受け取ってしまっているので、その後ろの部分にて改行は削除しています。その後、受け取った文字列が入ってるポインタを返り値として返しています。

stdarg.h周りのva_listなどは、printf関数のようにフォーマット機能を使用するために使っています。stdarg.hを読み込むと、引数に...と指定することで可変長引数を受け取ることができるようになります。その後、vprintf関数を使用することで、disc文字列にフォーマット機能で可変長引数を代入して表示できるようにしました。この辺りは蛇足的機能なので理解できなくても大丈夫です。(僕もよくわかってない...(^ ^; )

さらに、input.cを読み込むためにinput.hも用意しておきます。

#ifndef _INCLUDE_input_h_
#define _INCLUDE_input_h_

extern char* input(char *disc, ...);

#endif

input.cを使用するときは、例えばmain関数が入ったソースコードと同じ階層にinput.hinput.cを置き、$ gcc test.c input.cとしてコンパイルしましょう。

使用例

例えば最初のPythonプログラムはC言語で以下のように書けます。

#include <stdio.h>
#include <stdlib.h>
#include "input.h"

int main(void) {
    int
        a = atoi(input("a : ")),    
        b = atoi(input("b : "));

    printf("%d + %d = %d\n", a, b, a+b);

    return 0;
}
a : 5
b : 10
5 + 10 = 15

Pythonとほとんど変わらない書き方で同じ機能を実現できました!よきよき

さらにせっかく実装したフォーマット文字列も使用してみましょう。

#include <stdio.h>
#include <stdlib.h>
#include "input.h"

int main(void) {
    int sum = 0, tmp = 1;

    while (tmp != 0) {
        tmp = atoi(input("%2d += ", sum));
        sum += tmp;
    }

    return 0;
}

実行例

 0 += 1
 1 += 2
 3 += 3
 6 += 4
10 += 5
15 += 6
21 += 7
28 += 8
36 += 9
45 += 10
55 += 0

フォーマット文字列機能によりprintf関数みたいにプロンプト内容を自由に指定することができます。2

これで忌々しいscanf関数を使わずに楽に標準入力を受け取れますね!

この記事が参考になれば幸いです。ではまたいつか


  1. 変数名に関して、ヘッダーファイルにてグローバル変数として宣言しているわけではないので、本当は_(アンダーバー)とかつける意味はないのですが、気分です。気分。突っ込んじゃダメ。

  2. ちなみにPythonでもprint(f"{sum:2} += ")と指定すれば同じように表示できますのでご安心?ください。