MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C言語の基礎 - 標準入力から安全に文字列を取得するのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C言語の基礎 - 標準入力から安全に文字列を取得する
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 指定サイズ内で1行を受け取る == 次のように、文字列の格納領域が256[byte]用意されているとする。<br> <syntaxhighlight lang="c"> char buffer[256] = {'\0'}; </syntaxhighlight> <br> <code>scanf</code>関数と<code>fgets</code>関数の主な違いを纏めると以下のようになる。<br> NULL文字<code>\0</code>が終端に自動付与されることを考慮すると、ユーザが実質入力できるのは255[byte]までである。<br> <br> <u>※注意</u><br> <u><code>scanf</code>関数は<code>scanf("%s", buffer);</code>のようにサイズを指定せずに使用できるが、バッファオーバーランの危険性があるので避けること。</u><br> <center> {| class="wikitable" | style="background-color:#fefefe;" |- ! style="background-color:#66CCFF;" | 説明 ! style="background-color:#66CCFF;" | scanf関数 ! style="background-color:#66CCFF;" | fgets関数 |- | 基本的な使用法 || scanf("%255s", buffer) || fgets(buffer, 256, stdin) |- | 改行の扱い || 改行文字の直前まで読み込む || 改行文字も一緒に読み込む |- | 成功時の戻り値 || 読み込んだパラメータ数 || buffer |- | 失敗時の戻り値 || 0<br>または<br>EOF (-1) || NULL |- | 入力文字の取得 || できる || できない |} </center> <br><br> == scanf関数の利用 == scanf関数は多機能であり、フォーマットを記述することで動作のカスタマイズが可能である。<br> <br> 以下に、<code>scanf</code>関数のフォーマットを記載する。<br> * %sは全ての空白文字(半角スペースも含む)を無視するため、%sの代わりに%[^\n]と指定することで、無視する対象を改行だけに限定できる。<br>例えば、<code>%255[^\n]</code>は、改行以外の文字列を1〜255文字読み込むことを意味する。 * <code>%</code>の代わりに<code>%*</code>を使用すると、その部分を読み飛ばすことができる。<br>例えば、256文字以上入力する場合、最初の255文字までを受け取り、残りを捨てる場合は、<code>%255[^\n]%*[^\n]</code>と記述する。 * 再度<code>scanf</code>関数を使用する場合に備えて、残った改行は<code>%*c</code>で読み飛ばす。<br>これにより、保持されていた入力データは全て処理されたことになる。<br>ただし、次に読み取るものが<code>%d</code>等の数値である場合には、<code>%*c</code>で読み飛ばさなくても特に影響はない。 <br> また、<u>scanf関数は、正常に読み取れた場合は変数に格納したパラメータの数を返すため</u>、この数を確認して成功したかどうか判定することができる。<br> 例えば、<u>%255[^\n]%*[^\n]</u>を実行した場合の戻り値は、以下に示すようになる。<br> * 1文字以上の読み込みに成功した場合は、<code>1</code> * 改行のみが入力された場合は、<code>0</code> * 未入力の状態において、[Ctrl] + [D]キー (Linux) または [Ctrl] + [Z]キー (Windows)でEOFが入力された時は、EOF (EOF定数の値 : -1) * また、読み込みに失敗した場合は、<u>return 1;</u>等としてプログラムを異常終了扱いにするとよい。 <br> <code>%255[^\n]%*[^\n]</code>の場合<br> * %255 *: 最大255文字まで読み込む。 * [^\n] *: 改行以外の文字を読み込む。 * %*[^\n] *: 改行以外の残りの文字を読み込むが破棄する。 *: バッファオーバーフロー防止 <br> 例えば、"aaa\nbbb\nccc\nddd"と入力された場合、変数には"aaa"のみが格納される。<br> これは、[^\n]は最初の改行文字で読み込みを停止、残りの"bbb\nccc\nddd"は%*[^\n]で破棄されるためである。<br> これにより、1行のみを安全に読み込むことが可能となる。<br> <br> 以下の例では、必ず1文字以上入力させている。<br> <syntaxhighlight lang="c"> #include <stdio.h> int main() { char buffer[256] = {'\0'}; printf("Input: "); if (scanf("%255[^\n]%*[^\n]", buffer) != 1) { return 1; } scanf("%*c"); printf("Output: %s\n", buffer); return 0; } </syntaxhighlight> <br> 以下の例では、改行だけの入力を認めている。<br> <syntaxhighlight lang="c"> #include <stdio.h> int main() { char buffer[256] = {'\0'}; printf("Input: "); if (scanf("%255[^\n]%*[^\n]", buffer) == EOF) { return 1; } scanf("%*c"); printf("Output: %s\n", buffer); return 0; } </syntaxhighlight> <br> 以下の例では、入力された文字数も取得している。(<code>%255[^\n]</code>の後ろは、unsigned / intの場合は<code>%n</code>、size_t / ssize_tの場合は<code>%zn</code>を指定する)<br> (<code>%n</code>系は符号付き整数で取得するように定義されているが、負の値が出現する可能性がゼロなので、符号無し整数でも問題ない)<br> <syntaxhighlight lang="c"> #include <stdio.h> int main(void) { char buffer[256] = {'\0'}; size_t length; printf("Input: "); if (scanf("%255[^\n]%zn%*[^\n]", buffer, &length) != 1) { return 1; } scanf("%*c"); printf("Output: %s\n", buffer); printf("Length: %zu\n", length); return 0; } </syntaxhighlight> <br><br> == fgets関数の利用 == <code>fgets</code>関数は、改行も1行に含めて一緒に取得する。<br> 削除する場合は、各自でコードを記述しなければならない。また、<code>strlen</code>関数を使用するなどして、文字列長を求めることも必要である。<br> <br> また、戻り値は以下のようになる。<br> * 1文字以上読み取れた場合は、buffer(改行だけでも1文字以上という扱いになる) * 最初から[Ctrl] + [D](Windowsの場合は[Ctrl] + [Z])でEOFが入力された場合は、<code>NULL</code> * 失敗した場合は、<code>return 1;</code>としてプログラムを異常終了扱いにさせるとよい。 <br> 以下の例では、必ず1文字以上入力させて改行を削除している。<br> <syntaxhighlight lang="c"> #include <stdio.h> #include <string.h> int main(void) { char buffer[256] = {'\0'}; size_t length; printf("Input: "); if (fgets(buffer, 256, stdin) == NULL || buffer[0] == '\n') { return 1; } length = strlen(buffer); if (buffer[length - 1] == '\n') { buffer[--length] = '\0'; } printf("Output: %s\n", buffer); printf("Length: %zu\n", length); return 0; } </syntaxhighlight> <br> 以下の例では、改行だけの入力を認めて改行を削除している。<br> <syntaxhighlight lang="c"> #include <stdio.h> #include <string.h> int main(void) { char buffer[256]; size_t length; printf("Input: "); if (fgets(buffer, 256, stdin) == NULL) { return 1; } length = strlen(buffer); if (length > 0 && buffer[length - 1] == '\n') { buffer[--length] = '\0'; } printf("Output: %s\n", buffer); printf("Length: %zu\n", length); return 0; } </syntaxhighlight> <br> 以下の例では、改行だけの入力を認めて改行を削除している。<br> <syntaxhighlight lang="c"> #include <stdio.h> int main(void) { char buffer[256]; printf("Input: "); if (fgets(buffer, 256, stdin) == NULL) { return 1; } printf("Output: %s\n", buffer); return 0; } </syntaxhighlight> <br><br> == 整数の読み取り == ==== scanf関数の使用 ==== 整数を受け取る場合、一般的に<code>scanf</code>関数の<code>%d</code>を使用することが多い。<br> <br> 以下の例では、複数個の数値が入力された時、2個目以降は無視している。<br> ただし、以下のような欠点が存在する。<br> * 最初が空白文字の場合、<code>scanf</code>関数が終了しない。 * 範囲外の値が入力された場合、対応できない。 <syntaxhighlight lang="c"> #include <stdio.h> int main() { int num; printf("Input: "); if (scanf("%d%*[^\n]", &num) != 1) { return 1; } scanf("%*c"); printf("Output: %d\n", buffer); return 0; } </syntaxhighlight> <br> ==== scanf関数とstrtol関数の使用 ==== <code>scanf</code>関数のみでは上記のような欠点があるため、文字列として読み取り整数に変換する方法がある。<br> <br> 文字列から整数に変換するには<code>strtol</code>関数を使用するため、整数は<code>long</code>型とする。<br> なお、<code>atoi</code>関数や<code>atol</code>関数にはエラー検出機能は無いので使用しないこと。<br> <syntaxhighlight lang="c"> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <limits.h> int main() { char buffer[32], *endptr; long num; // 文字列として読み取る printf("Input: "); if (scanf("%31[^\n]%*[^\n]", buffer) != 1) { fprintf(stderr, "Error: No input specified\n"); return 1; } scanf("%*c"); // 文字列から整数に変換する num = strtol(buffer, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Error: Invalid charcter found: %c\n", *endptr); return 1; } if (errno == ERANGE) { fprintf(stderr, "Error: Out of range (%s)\n", num == LONG_MAX ? "Overflow" : "Underflow"); return 1; } printf("Output: %ld\n", num); return 0; } </syntaxhighlight> <br><br> == 指定サイズ内で複数行を読み取る == <code>fread</code>関数を使用する場合、行という概念にとらわれず,格納領域が全て埋まる、または、入力が<code>EOF</code>に到達するまで読み取ることができる。<br> ただし、バイナリモードで読み取るため改行コードの自動変換の機能が無い。そのため、テキストを処理する場合は不便である。<br> <br> <code>scanf</code>関数では、<code>%4095[\x01-\xff]</code>のように記述することにより、<code>NULL</code>文字以外の文字を全て読み取ることができる。(改行も読み取ることができる)<br> <syntaxhighlight lang="c"> #include <stdio.h> #include <string.h> int main() { char buffer[4096] = {0}, *p = NULL; printf("Input: \n"); if (scanf("%4095[\x01-\xff]", buffer) != 1) { return 1; } printf("Output: \n"); for (size_t i = 0, p = strtok(buffer, "\n"); p; i++, p = strtok(NULL, "\n")) { // 確認用にstrtok関数を使用して行ごとに番号を付加して表示する printf("[%zu] %s\n", i, p); } return 0; } </syntaxhighlight> <br><br> == 任意のサイズの1行を読み取る == C言語において、可変長は基本的に避けるべきであるが、メモリが許容する限りどのような長さの1行でも読み取る<code>getline</code>関数が存在する。<br> <u>ただし、使用後は手動でメモリを解放する必要があることに注意する。</u><br> <br> 以下の例では、改行のみの入力を認めずに、改行を削除している。<br> 最初に確保される領域の大きさを4096文字にしている。<br> サイズがオーバーする場合は自動的に拡張されるが、事前に多くの文字を入力することが分かっている場合、大きめに設定しておくほうが望ましい。<br> <syntaxhighlight lang="c"> #include <stdio.h> #include <stdlib.h> #define _GNU_SOURCE int main() { char *buffer = NULL; ssize_t length; printf("Input: "); if ((length = getline(&buffer, &(size_t){4096}, stdin)) == EOF) { return 1; } if (buffer[length - 1] == '\n') { buffer[--length] = '\0'; } printf("Output: %s\n", buffer); printf("Length: %zd\n", length); free(buffer); return 0; } </syntaxhighlight> <br><br> == 日本語の文字列を文字単位で処理する == ==== 文字コードについて ==== 例えば、マルチバイトにおいて、UTF-8では日本語で使用する1文字には3バイトの領域が必要である。<br> マルチバイト文字では、連続したバイト列としてとして扱う場合は特に問題無いが、1文字ずつ取り出す場合や文字列長を求める場合は特殊な処理を記述する必要がある。<br> <br> ==== ロケール設定 ==== マルチバイト文字を正しく扱う場合、以下の関数を使用する。<br> <syntaxhighlight lang="c++"> #include <locale.h> setlocale(LC_ALL, ""); </syntaxhighlight> <br> ==== 使用するデータ型 ==== マルチバイト文字を1文字ずつ区別できるように格納する場合、<code>wchar_t</code>型を使用する。<br> <code>wchar_t</code>型のリテラルを記述する場合、<code>L'<1文字>'</code>および<code>L"<文字列>"</code>と記述する。<br> <br> また、<code>wchar_t</code>型のフォーマット指定子を使用する場合、1文字の時は<code>%lc</code>、文字列の時は<code>%ls</code>と記述する。<br> <br> ==== サンプルコード ==== 以下の例では、入力された文字列を1文字ずつ鍵括弧で括って表示している。<br> フォーマット指定子において、<code>%n</code>で求めることができるのはバイト長であり文字列長ではないため、ループ条件の変数としては使用できない。<br> <syntaxhighlight lang="c++"> #include <stdio.h> #include <wchar.h> #include <locale.h> int main() { wchar_t buffer[256] = {0}; setlocale(LC_ALL, ""); printf("入力: "); if (scanf("%255l[^\n]%*[^\n]", buffer) != 1) { return 1; } scanf("%*c"); printf("出力: "); for (wchar_t *c = buffer; *c; c++) { printf("「%lc」", *c); } putchar('\n'); return 0; } </syntaxhighlight> <br><br> __FORCETOC__ [[カテゴリ:C]]
C言語の基礎 - 標準入力から安全に文字列を取得する
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse