テキストはユーザー、ファイル、コマンドラインから文字として届きます。"42" は3バイト - '4'、'2'、'\0' - であり、変換するまでその中に数の 42 はありません。Cには2世代の変換関数があります。短くて古い、エラーを報告できないものと、長くて報告できるものです。このページは両方を示し、実際のプログラムが後者を使う理由を説明します。
古い一族:atoi、atol、atof
名前のとおりに読めます。ASCII から int へ、long へ、double へ。引数1つ、結果1つです。
先頭の空白は飛ばされ、任意の符号が読まれ、数字が消費され、その後のすべては無視されます。入力が不正になるまでは便利です。
3つとも 0 を表示します。戻りコードもフラグもなく、「うまくいったか?」と尋ねる手段もありません - そして int をあふれさせる値は報告されるエラーではなく未定義動作です。おもちゃのプログラムの中にハードコードしたリテラルなら我慢できます。人やファイルが与えうるものに対しては、静かなバグの製造機です。
正しい道具:strtol
strtol(string to long)は同じ変換に、何が起きたかを報告する2つの経路を備えたものです。
long strtol(const char *text, char **endptr, int base);
endptr には関数が消費しなかった最初の文字が設定されます。base は基数で、10 を使うか、リテラル自身の接頭辞に判断させるなら 0 を使います(16進なら 0x、8進なら先頭の 0)。
endptr を読むことで atoi には答えられない問いに答えられます。
end == inputは何も変換されなかったという意味です - テキストが数で始まっていませんでした。*end != '\0'は後ろに内容が残っていたという意味です。それがエラーかどうかはあなたの判断です。"42 apples"を解析しているなら問題ありませんし、その行全体が数であるべきだったなら問題です。
オーバーフローは別途 errno を通じて報告されます。完全で正しい書き方は、一度書いて使い回す価値があります。
3つの細部がこれを正しくしています。呼び出し前の errno = 0 が重要なのは、ライブラリは errno を設定するだけで決してクリアしないからです。以前の呼び出しの古い値が、今回の失敗として読まれてしまいます。strtol は long を返し、それは int より広いかもしれないので、INT_MIN/INT_MAX に対する範囲検査は ERANGE とは別に必要です。そして成功をステータスとして返し、値はポインタ経由で渡すのが、Cの関数が「値か失敗のどちらか」を報告する方法です - 戻り値は両方の役目を果たせないのです。
strtoul は符号なしの対応物で、strtoll/strtoull は long long を扱います。
その他の基数
基数 16 は16進を読み、基数 0 は接頭辞を調べて自分で判断します。
基数 0 はある特定の場合に罠になります。"012" のようなゼロ詰めの十進が8進として読まれ、10 として出てくるのです。入力が十進なら明示的に 10 と書きましょう。
浮動小数点:strtod
strtod は double に対する同じ形で、atof は atoi と同じ欠点を持っています。
strtod は科学表記("1.5e3")、16進の浮動小数点、そして "inf" や "nan" という語も受け付けます。小数点の区切り文字はロケールの影響を受けるので、他所で書かれたファイルを解析する人を驚かせます - 既定の "C" ロケールでは区切り文字は常にドットです。
数値からテキストへ:snprintf
逆方向には snprintf が標準の答えです。渡したサイズを超えて書くことはなく、必ず終端し、完全な結果に必要だった長さを返します。
戻り値が切り詰めの検査になります。バッファサイズ以上なら、テキストは収まりませんでした。
手を出すべきでないものが2つ。itoa は標準Cの一部ではないので、それを使うコードはどこでもコンパイルできるわけではありません。そして sprintf はサイズ制限のない snprintf であり、文句も言わずバッファの末尾を越えて書き込みます。
1文字と '0'
数字の文字 '0' から '9' は連続していることが保証されているので、単なる引き算で数字の文字とその値を相互に変換できます。
このように振る舞うのは数字だけです - 英字は連続していることを要求されていないので、c - 'a' はアルファベットの計算に対して可搬ではありません。信用する前に isdigit で引き算を守りましょう。
unsigned char へのキャストが必要な理由は文字列と同じです。<ctype.h> の関数は負の引数に対して未定義であり、素の char は符号付きでありうるからです。
どの関数を使うか
- ユーザー、ファイル、
argvからのテキスト → 完全な検査を伴うstrtol/strtod。 - 自分で書いて管理しているリテラル →
atoiでも許容範囲です(特筆すべきことはありませんが)。 - 数値をバッファへ →
snprintf、そして戻り値を確認すること。 - 1文字の数字 →
isdigitのあとで'0'を引く。 - 決して使わない →
gets、sprintf、itoa。
よくある質問
C言語で文字列を int に変換するには?
正解は strtol(text, &end, 10) です。値を返し、end に消費しなかった最初の文字を設定し、オーバーフローを errno で報告します。atoi(text) は短いのですが、"0" にも "hello" にも 0 を返し、その2つを区別する手段がありません。
C言語の atoi の何が問題なのですか?
失敗を報告できないことです。atoi("abc") は 0 を返しますが、これは本物のゼロと区別がつきませんし、オーバーフローは報告されるエラーではなく未定義動作です。テキストがユーザー、ファイル、コマンドライン引数から来たときは必ず strtol を使いましょう。
C言語で int を文字列に変換するには?
snprintf(buf, sizeof buf, "%d", n) です。標準的で、バッファサイズで制限され、完全な結果に必要だった文字数を返します - したがって戻り値が sizeof buf 以上ならテキストが切り詰められたと分かります。itoa は標準Cではありません。
C言語で文字 '7' を数値の 7 にするには?
'0' を引きます:int digit = c - '0';。数字の文字はどのC言語の文字集合でも連続しているので、この引き算で 0 から 9 が得られます。それ以外の文字では意味のない計算になるので、先に isdigit((unsigned char)c) で守りましょう。