からの入力の読み取りstdin
からのユーザー入力をプレーンなC
。問題は、エラーに対して堅牢で、ユーザーを特定の入力に制限し、複雑さの点で問題がない、健全な実装が必要なことです。関数get_strings()
は次のように入力文字を読み取ります。改行(\n
)、EOF
がなく、すべての文字がisalpha()
テスト。ただし、スペースを確保したい。
レビュー中に(私が思うに)特別な注意を払うに値するいくつかのポイント:
- -削除したい基本的に、ユーザーが意味のある入力なしでEnterキーを押したかどうかをテストする外側のwhileループ。
- -ferror()を使用する必要もありますか?fgetcは、問題が発生したときにすでにEOFを返しますが、ユーザーに問題が発生したことを通知するのではなく、ストリームからの読み取りを停止します。
- -毎回発生するわけではありませんが、発生します。\ nはstdinストリームに残り、次に意味のある入力を取得したいときfgetc()はスキップされます。ここでは問題ではありませんが、1文字の「はい/いいえ」の質問をしている場合は問題になります。この問題を取り除くには、stdinに残っている以前のすべてのものをクリアする構造を使用する必要があります。これについては2番目のコードブロックを参照してください。 。
それで、私はこれを完全に間違って処理していますか?受け入れるためのより良い方法はありますか?それは私には本当に不格好に見え、不格好は常に悪いです。
/** @brief Contains the dictionary */ static char **strings = NULL; /** @brief Helps with containing the dicionary */ static char *string; /* Reads input char by char with fgetc() */ static char *get_strings() { char *string = NULL; char ch; size_t len = 0; while (string == NULL && ch != EOF) { while (EOF != (ch = fgetc(in_stream)) && ch != "\n") { if (ch != " " && isalpha((int)ch) == 0) { fprintf(stderr, "Only [a-z] is a valid input. | \t" "| Input another or end with CTRL+D: "); continue; } string = (char*) realloc(string, len+2); if (string == NULL) { bail_out(EXIT_FAILURE, "realloc(3) failed"); } string[len++] = toupper(ch); if (len >= MAX_DATA) { bail_out(EXIT_FAILURE, "Input too long\n"); } } if (ferror(in_stream)) { bail_out(EXIT_FAILURE, "Error while reading from stream"); } } if(string) { string[len] = "\0"; } else { printf("\nFinished dictionary...\n"); } printf("Added string: %s | Input another or end with CTRL+D: ", string); return string; } /* Saves the returned strings from get_strings() in a linked list */ static void read_dict() { int index; for (index = 0; (string = get_strings()); ++index) { if (string[0] == "\0") continue; strings = (char**) realloc(strings, (index+1)*sizeof(*strings)); if (strings == NULL) { bail_out(EXIT_FAILURE, "realloc(3) failed"); } strings[index] = string; } /* Take a note of how many entries we have yet. */ dict_size = index; }
より単純なケースの2番目のCodeBlock:
while(1) { char tmp; printf("Please enter your guess [a-z]: "); guess = fgetc(stdin); /* Jump back to start of loop */ if (guess == "\n") { continue; } /* HERE IS THE CLEAR FOR STDIN This part really just eats all remaining \ns from the user, so that later inputs can start uninterrupted. Can I get rid of it in some better way? */ while((tmp = getchar()) != "\n" && tmp != EOF); if(!isalpha(guess)) { fprintf(stderr, "Enter a valid letter [a-z]!\n"); continue; } }
コメント
回答
アーキテクチャ
stdin
は通常ラインバッファリングです。したがって、ユーザーが Enter を押すまで、fgetc()
には何も与えられません。 OPコードは、「Hello123」のような入力で複数のエラーメッセージを表示します。ユーザー入力を入力検証から分離することをお勧めします。 fgets()
にはいくつかの弱点があるため、fgets()
または独自のバージョンでユーザー入力の行を読んでください。 次に入力を検証します。
char *input; while ((input = my_gets()) != NULL) { if (valid_phrase(input)) { foo(input); } else { fprintf(stderr, "Invalid input\n"); } free(input); }
「外側のwhileループを削除したい」について。そのループは、"\n"
をサイレントに消費するために存在します。ループでこれを実行する場合は、内側のループの直前に
int ch; while ((ch = fgetc()) == "\n") ; ungetc(ch, stdin);
ch
は最適なタイプではありません。 fgetc()
は、通常257の異なる値[0-255]
とEOF
を返します。それらを正しく区別するには、結果をint
に保存します。
// bad char ch; .. while (string == NULL && ch != EOF) { while (EOF != (ch = fgetc(in_stream)) && ch != "\n") { // better int ch; .. while (string == NULL && ch != EOF) { while (EOF != (ch = fgetc(in_stream)) && ch != "\n") {
realloc()
キャストは必要ありません。
メモリ不足に変更してstring
を解放します-コードが単に終了する場合は必要ありませんが、おもちゃを置くことをお勧めします(コード「spointer)away。
// string = (char*) realloc(string, len+2); char * new_string = realloc(string, len+2); if (new_string == NULL) { free(string); bail_out(EXIT_FAILURE, "Out of memory"); } string = new_string;
以下のsizeof(*strings)
を適切に使用します。簡略化をお勧めします。
strings = (char**) realloc(strings, (index+1)*sizeof(*strings)); strings = realloc(strings, sizeof *strings * (index+1));
size_t len
size_t
を適切に使用して配列サイズを表します。不思議なことに、コードはint index;
と同じことをしません。 size_t index;
is...()
int ch
を使用すると、キャストの必要がなくなります。これは論理テストです。算術演算== 0
ではなく!
を使用することをお勧めします。
// if (ch != " " && isalpha((int)ch) == 0) { if (ch != " " && !isalpha(ch)) {
以下は理解しやすいかもしれません-否定が少なくなります。 (スタイルの問題)
if (!(ch == " " || isalpha(ch))) {
ferror()
if (ferror(in_stream))
の適切なチェック変数名
string
、strings
は次のように役立ちます整数integer
を呼び出します。たぶんphrase
、代わりにdictionary
。
// OK /** @brief Contains the dictionary */ static char **strings = NULL; // Better // comment not truly needed static char **dictionary = NULL;
get_strings()
の名前が間違っています。一般的に聞こえますが、コードは入力を文字とスペースに制限します。たぶんget_words()
?
コメント
- 同じ回答を2回投稿したような気がしますか?とにかく、これが私が探していた答えです!私は最初はfgetcsind fgets did ‘の使用に完全に集中していました(stdinの不正な\ nsのため)。これははるかに優れているようで、コードに組み込みます。ありがとう!
- @Haini承知しているだけでなく、気に入った場合は回答に賛成することもできます;-)
回答
悪いrealloc戦略
現在、読んだすべての文字でrealloc()
を呼び出しています。これにより、文字列を読み取る時間が\ $ O(n ^ 2)\ $になります。これは、realloc()
を呼び出すたびに、現在の内容を新しいバッファにコピーする必要がある場合があるためです。 。サイズMAX_DATA
のバッファーを割り当ててから、realloc
を使用して最後に割り当てを縮小するか、再割り当て戦略に変更する必要があります。ここで、再割り当てサイズは毎回乗法係数(2xなど)で増加します。
これは、同じことを行う文字列の配列にも当てはまります。
奇妙なインデント
ネストされたwhile
ループが、外側のwhile
ループと同じインデントレベルにあるため、インデントがおかしいです。
fgets()を使用しますか?
個人的にはfgets()
(または)文字列を読み取ります。 fgets()
は、すべてのハンドコードされたロジックなしで、ループが行うこととほぼ同じことを行います。
stdbool.h
を使用しますが、’自分のブール値をロールしようとしないでください。