C言語での数値の入力
はじめに
今回はC言語での(int型の)数値の入力について考える。
正しい入力だけに対応するなら
scanf関数を用いても構わないかもしれないが、
様々な入力に対応しようとすると厳しい。
(私が力不足なだけだが)
今回は入力された数字を文字列として格納し、
数値に変換することにより数値の入力を実現しようと思う。
文字列として入力
文字列としての入力に、今回はfgets関数を用いる。
fgets関数は改行コードも読み取るため、
改行コードをNULL文字に変えるためのtrimEnd関数も用意する。
#include <stdio.h> #include <string.h> void trimEnd(char str[]){ int i = 0; while(str[i] != '\n' && i < strlen(str)){ i++; } str[i] = '\0'; } int main(void){ char s[256]; fgets(s, 256, stdin); trimEnd(s); printf("%s\n", s); return 0; }
atoi関数
atoi関数は文字列型からint型の数値に変換する関数である。
詳しくはリファレンスを見てほしいが大まかな機能を以下に記す。
・変換する文字列は先頭から見る
・変換できない文字列(先頭の空白類文字を除く)がきた時点で終了する
・数値へ変換できずに終了したとき0を返す
(例)
"123Hello" → 123
"Hello123" → 0
" 3" → 3
"11 11" → 11
まずatoi関数を利用するにあたっての問題点が
0が入力された数値なのか、エラーなのかわからない
ということである。
私はこの問題をstrcmp関数を利用することで対応した。
変換前の入力文字列が"0"かつ、変換後の数値が0なら0。
返還前の入力文字列が"0"でなく、変換後の数値が0ならエラーである。
以下にこれを利用したコードを載せる
#include <stdio.h> #include <stdlib.h> #include <string.h> void trimEnd(char str[]){ int i = 0; while(str[i] != '\n' && i < strlen(str)){ i++; } str[i] = '\0'; } int main(void){ char s[256]; int n; do{ printf("0以上100以下の番号を入力してください: "); fgets(s, 256, stdin); trimEnd(s); printf("番号 = %d\n", atoi(s)); }while((atoi(s) == 0 && strcmp(s, "0") != 0) || atoi(s) < 0 || atoi(s) > 100); printf("\n入力成功\n"); return 0; }
実行してみる
0以上100以下の番号を入力してください: aa 番号 = 0 0以上100以下の番号を入力してください: af 番号 = 0 0以上100以下の番号を入力してください: A 番号 = 0 0以上100以下の番号を入力してください: 番号 = 0 0以上100以下の番号を入力してください: 101 番号 = 101 0以上100以下の番号を入力してください: -1 番号 = -1 0以上100以下の番号を入力してください: 0 番号 = 0 入力成功
whileの条件式が長くなるが無事予想通りの挙動を見せた。
しかしint型で表せる範囲を超えた数値を入力されたとき、
不定の動きとなる。
また、数字のあとに文字列を入力したときにエラーがでない。
以下にその実行例を載せる
0以上100以下の番号を入力してください: 1213134334333233123123 番号 = -1 0以上100以下の番号を入力してください: 123hello 番号 = 123 0以上100以下の番号を入力してください: -3146124687346244768346 番号 = 0 0以上100以下の番号を入力してください: 0 番号 = 0 入力成功
正直atoi関数とstrcmp関数を組みわせれば問題ないが、
より厳格にするにはstrtol関数を用いる。
strtol関数
strtol関数はatoi関数より複雑だが、エラーしたときにちゃんと対応してくれる。
文章にすると長いので以下のリファレンスとコードを参考にしてほしい。
以下がサンプルコードである。
上のコードの値の範囲が0から100ではなくなったものである。
なおtrimEnd関数は省略してある。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <limits.h> #include <errno.h> void trimEnd(char str[]); int main(void){ char s[256]; long int n; char *endptr; do{ errno = 0; printf("数字を入力してください: "); fgets(s, 256, stdin); trimEnd(s); n = strtol(s, &endptr, 10); printf("n = %ld\n", n); printf("endptr = |%s|\n", endptr); }while(*endptr != '\0'|| errno == ERANGE || strcmp(s, "\0") == 0); printf("\n*正しく入力されました\n"); printf("n = %ld\n", n); printf("endptr = |%s|\n", endptr); if(n > INT_MAX || n < INT_MIN){ printf("%ldはint型に変換できません\n", n); }else{ printf("(int)%d\n", (int)n); } return 0; }
実行例
数字を入力してください: n = 0 endptr = || 数字を入力してください: 11111111111111111111111111111111111111 n = 9223372036854775807 endptr = || 数字を入力してください: -111111111111111111111111111111111111 n = -9223372036854775808 endptr = || 数字を入力してください: 123hello n = 123 endptr = |hello| 数字を入力してください: aa aa aa n = 0 endptr = |aa aa aa| 数字を入力してください: 1111111111111 n = 1111111111111 endptr = || *正しく入力されました n = 1111111111111 endptr = || 1111111111111はint型に変換できません
オーバフロー、アンダーフローの判定、
読み込み終了した位置の文字列のアドレスの保存が可能である。
そのため数値への変換が不可能なときatoi関数と同じく0を返すが、
atoi関数と比べ、どのようなエラーか判断がしやすい。
おわりに
正直int型で返ってこないstrtol関数は使いにくい。
よほどでなければatoi関数でいいだろうな。たぶん。