文字列と数値の相互変換

こんにちは、めのんです!

前回まででCの主な言語仕様については解説しましたので、今後は具体的なコードの書き方を扱っていこうと思います。
今回は文字列と数値の相互変換についてです。

数値から文字列への変換にはsnprintf関数を使用する

標準規格の範囲で数値から文字列に変換するには、「snprintf関数」などのprintf系関数を使うか、自分でロジックを書くしかありません。
ここではsnprintf関数を紹介しますね。

snprintf関数は「stdio.h」ヘッダで次のように宣言されています。

int snprintf(char * restrict s, size_t n, conat char * restrict format, ...);

restrict修飾子が使われていますけど、これは仮引数sと仮引数formatが指す領域が重複していないことを表しています。
コンパイラが最適化しやすくなるためのヒント情報なので、最初のうちはそんなに深く考える必要はありません。

書式文字列

仮引数formatで指定するsnprintf関数の書式化はprintf関数と同じですが、PHPのprintf関数やsprintf関数とは微妙に違うので注意が必要です。
一番の違いは実引数のサイズを指定する必要があることでしょう。

PHPでは整数型も浮動小数点型もそれぞれ1種類ずつしかありませんけど、Cにはたくさんの種類の型があります。

既定の実引数拡張された型がint型やdouble型ならいいんですが、そうでなければ次のように修飾してあげる必要があります。

char s[256];
snprintf(s, sizeof(s), "%d", 123);      // int型
snprintf(s, sizeof(s), "%ld", 123L);    // long型
snprintf(s, sizeof(s), "%lld", 123LL);  // long long型
snprintf(s, sizeof(s), "%f", 1.23);     // double型
snprintf(s, sizeof(s), "%Lf", 1.23L);   // long double型

整数定数のあとに「L」を付ければlong型、「LL」を付ければlong long型になります。
また、浮動小数点定数のあとに「L」を付ければlong double型になります(「f」を付ければfloat型になります)。
あとは、%dと%uの違い、%oや%x、%fと%eと%gなどはPHPと同じだと考えて問題ありません。

short型やchar型の実引数を渡した場合は既定の実引数拡張でint型などに暗黙の型変換が起きるのですが、もとの型が何であったのかを明示的に指定する方法もあります。
たとえば、%hdであればshort型ですし、%hhdであればchar型(正確にいえばsigned char型)になります。

結果の格納先

snprintf関数の最初の2つの引数は結果の格納先に関するものです。
第1引数sは結果を格納する配列の先頭要素へのポインタです。
第1引数nはsが指す配列の要素数です。

snprintf関数は末尾のナル文字を含めてnバイトを超えて書き込むことはありません。
また、実際に書き込まれたどうかにかかわらず、出力しようとした文字列のバイト数を返却値として返します。

これによって、先にどれだけのバイト数の配列が必要なのかを調べておいてから、配列を割付け、それから実際に結果をその配列に書き込ませるといったことができます。
たとえばこんな感じです。

int n = snprintf(NULL, 0, "%d", x);   // nが0ならsはNULLでもよい
char s[n];
snprintf(s, n, "%d", x);

上の例では可変長配列を使いましたが、malloc関数で割り付けてももちろんかまいません(その場合は使い終わったらfree関数で解放を忘れないでくださいね)。

文字列から数値への変換にはstrto系関数を使用する

PHPと同様、Cにもsscanf関数があります。
ですが、sscanf関数はエラーが絶対発生しないとわかっている場合以外は使うのは辞めた方がいいと思います。

なぜなら、sscanf関数などscanf系の関数を数値の入力に使った場合、仮にオーバーフローが起きたとしても未定義の動作になってしまってエラーを検出する方法がないからです。

文字列から数値への変換にはatoi関数やatof関数のようなato系関数が使われることが多いのですが、これらの関数もオーバーフローが発生すると未定義の動作になってしまいます。

文字列から数値への変換で正しくエラーチェックができるのは、標準規格の範囲ではstrtol関数やstrtod関数などstrto系関数だけです。
strto系関数は「stdlib.h」ヘッダで次のように宣言されています。

long strtol(const char * restrict s, char ** restrict endptr, int base);
long long strtoll(const char * restrict s, char ** restrict endptr, int base);

unsigned long strtoul(const char * restrict s, char ** restrict endptr, int base);
unsigned long long strtoull(const char * restrict s, char ** restrict endptr, int base);

float strtof(const char * restrict s, char ** restrict endptr);
double strtod(const char * restrict s, char ** restrict endptr);
long double strtold(const char * restrict s, char ** restrict endptr);

たくさんの関数が並んでいますが、返却値の型に応じてこれだけの種類になっています。
整数用と浮動小数点用を覚えれば、あとは同じように使えるはずです。

これらの関数の第1引数sは変換対象の文字列なのでとくに説明はいらないでしょう。
第2引数のendptrは、sが指す配列のうちどこまで解析を行ったかを知るためのものです。
最後のbaseは整数値に変換する関数にしかありませんが、基数(10進数とか16進数とか)を表すものです。

具体例を挙げますね。

char *end;
long x = strtol("12345abc", &end, 10);

上のように使った場合、endにはstrtol関数の第1引数に使った”12345abc”の中の’a’を指すポインタが格納されます。
文章で伝えるのはちょっと難しいので、実際にご自身で試してみてください。

ところで最後の引数のbaseですが、2から36を指定すると指定した値が基数になります。
17進数以上だと、’g’から’z’を数字に使うことになります。

もしbaseに0を指定した場合は、基数を表す”0″や”0x”などの接頭辞で基数を判断します。
浮動小数点数に変換するstrtod関数などでは、基本は10進数ですが、”0x”や”0X”が先行する場合は16進浮動小数点数になります。
16進浮動小数点数についてはまた別の機会に解説しますね。


今回の解説は以上になります。
次回は現在時刻の求め方を解説したいと思います。
どうぞご期待ください!