文字列を数値に変換
文字列操作5
ほとんどのプログラミング言語では、文字と数値は別物として扱われます。
同じ「1」という値でも、文字としての「1」と数値としての「1」は別物なのです。
#include <stdio.h>
int main()
{
char kazu = 1;
char moji = '1';
printf("kazu: %d\n", kazu);
printf("moji: %c\n", moji);
printf("\n");
printf("kazu: %c\n", kazu);
printf("moji: %d\n", moji);
getchar();
}
kazu: 1 moji: 1 kazu: ・ moji: 49
このコードでは同じchar型の変数に、数値としての「1」と文字としての「1」を代入しています。
8、9行目はそれぞれ正しく出力されますが、13、14行目ではprintf
関数の変換指定子をそれぞれ逆にしています。
一見同じ値でも、内部的には全く別の値が保存されていることがわかります。
getchar
関数などによるキーボード入力やファイルからの文字の読み込みなどで得られるデータはすべて「文字」です。
ユーザーが「123」と入力しても、そのままでは「123」という数値を得ることはできず、単純な足し算すらできません。
(直接数値に変換して取得する方法も存在しますが)
//こういうことはできない
char moji[] = "123" + "456";
int kazu = "123" + "456";
atoi関数
数字を数値に変換するにはatoi
関数を使用します。
この関数を使用するにはコード先頭に#include <stdlib.h>
を追加する必要があります。
#include <stdio.h>
#include <stdlib.h>
int main()
{
const char moji[] = "123";
int kazu = atoi(moji);
printf("%d", kazu);
getchar();
}
-
int atoi(
const char *str
); -
文字列strを数値に変換して返す。
数値と評価できない文字列が渡された場合は0を返す。
int型の範囲を超える数値表現の文字列が渡された場合の動作は未定義。
atoi
関数は引数の文字列が数値として読めるならば、数値にして返します。
「abc」や「123」(全角の数字)などは数値に変換できず、0を返します。
数字と数字以外の文字が混在する場合は、先頭から数字と評価できる分だけを数値に変換して返します。
先頭に空白文字(半角スペースや改行文字など)がある場合は何らかの文字が出現するまで読み飛ばされます。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char moji1[] = "12a3b";
char moji2[] = "-123ab";
char moji3[] = " \n\t123ab";
char moji4[] = "a123b";
int kazu1 = atoi(moji1);
int kazu2 = atoi(moji2);
int kazu3 = atoi(moji3);
int kazu4 = atoi(moji4);
printf("%d\n", kazu1);
printf("%d\n", kazu2);
printf("%d\n", kazu3);
printf("%d\n", kazu4);
getchar();
}
12 -123 123 0
このコードのmoji1
は先頭から2文字が数字なのでkazu1
には「12」が代入されます。
moji2
は先頭がハイフンですが、これはマイナス符号と解釈されます。
moji3
は先頭に半角スペースや改行文字、タブ文字がありますがこれらは読み飛ばされます。
moji4
は途中に数字がありますが、先頭が数字ではないので変換できず、kazu4
には「0」が代入されます。
なお、int型の範囲を超える数字の文字列を指定した場合の動作は未定義です。
atol、atof関数
atoi
関数の類似関数にatol
関数、atof
関数があります。
atol
関数は文字列をlong型に変換します。
atof
関数は文字列をdouble型に変換します。
使い方はatoi
関数と同じなのでサンプルコードは省略します。
strtol関数
文字列を数値に変換するにはstrtol
関数を使用することもできます。
(string to long)
atoi
関数に比べて使い方がやや難しいものの、より安全かつ柔軟な変換が可能です。
-
long strtol(
const char *strSource,
char **endptr,
int base
); -
文字列strSourceをbase進数の文字列と解釈した値をlong型で返す。
ポインタendptrには読み取った次の文字への位置を格納する。
#include <stdio.h>
#include <stdlib.h>
int main()
{
const char str[] = "123 456 7ab";
char *end1, *end2, *end3;
long num1 = strtol(str, &end1, 10);
long num2 = strtol(end1, &end2, 10);
long num3 = strtol(end2, &end3, 10);
printf("%d\n", num1);
printf("%d\n", num2);
printf("%d\n", num3);
printf("%s\n", end3);
getchar();
}
123 456 7 ab
strtol
関数の第一引数は変換対象となる文字列です。
先頭から文字列を読み取り、数値と解釈できない文字が現れるまでの文字列を数値に変換します。
先頭に空白文字がある場合は無視されます。
(何らかの文字が現れるまで読み飛ばされる)
第二引数はポインタという機能を学習しないと意味が分からないと思います。
とりあえず第二引数に渡す変数は、char型変数の変数名の前に*
記号を付けて宣言し、実引数には&
記号を指定する、と覚えてください。
必要がない場合はNULLを渡すことも可能です。
この引数については後述します。
第三引数は文字列の基数を指定します。
「10」を指定すれば文字列中の数値は10進数であると解釈され、「16」を指定すれば16進数と解釈されます。
2~36の数値が指定できます。
「0」を指定すると、文字列の形式から自動的に判別します。
文字列の先頭が「0」ならば8進数、「0x」「0X」の場合は16進数、それ以外の場合は10進数と解釈されます。
#include <stdio.h>
#include <stdlib.h>
int main()
{
long num1 = strtol("123", NULL, 10); //10進数と解釈
long num2 = strtol("123", NULL, 8); //8進数と解釈
long num3 = strtol("123", NULL, 16); //16進数と解釈
long num4 = strtol("123", NULL, 0); //自動判別(10進数)
long num5 = strtol("0123", NULL, 0); //自動判別(8進数)
long num6 = strtol("0x123", NULL, 0); //自動判別(16進数)
printf("%d\n", num1);
printf("%d\n", num2);
printf("%d\n", num3);
printf("\n");
printf("%d\n", num4);
printf("%d\n", num5);
printf("%d\n", num6);
getchar();
}
123 83 291 123 83 291
※この実行結果はすべて10進数表記です。
第二引数endptr
第二引数endptr
は「ポインタ」への「ポインタ」を指定します。
ポインタはまだ説明していないので、できるだけ省いて説明します。
char *end;
long num = strtol("123", &end, 10);
上記の形式でchar型変数を宣言して第二引数に渡すと、この変数には「数値と解釈できない文字が表われた位置」の情報が格納されます。
文字列をすべて数値と解釈できた場合は、終端のNULL文字の位置の情報が格納された状態となります。
この変数から文字を取り出すには変数名の前に*
記号を付けます。
char *end1, *end2;
long num1 = strtol("abc", &end1, 10);
long num2 = strtol("123def", &end2, 10);
printf("%c\n", *end1);
printf("%c\n", *end2);
a d
「abc」は数値(10進数)とは解釈できないので文字列の先頭である「a」の位置の情報が変数end1
に格納されます。
「123def」は「123」までは数値と解釈でき、以降は数値とは解釈できないため「d」の位置の情報が変数end2
に格納されます。
strtol
関数は先頭の空白文字は読み飛ばすため、最初のサンプルコードのように、第二引数endptr
を利用すると空白で区切られた文字列から複数の値を連続して変換することができます。
const char str[] = "123 456 789";
char *end1, *end2, *end3;
long num1 = strtol(str, &end1, 10); //123
long num2 = strtol(end1, &end2, 10); //456
long num3 = strtol(end2, &end3, 10); //789
変換エラーへの対処その1
atoi
関数は数値に変換できない文字列を指定した場合に0を返しますが、正常に変換された場合にも0を返す可能性があります。
また、int型の範囲を超える値を渡した時の動作は未定義です。
つまり、関数が成功したのか失敗したのかを戻り値からは判別することができません。
このためatoi
関数の使用は推奨されていません。
strtol
関数は第二引数のendptr
を使用することで正常終了か否かを判断することができます。
#include <stdio.h>
#include <stdlib.h>
int main()
{
const char str1[] = "0";
const char str2[] = "a";
char *end1, *end2;
long num1 = strtol(str1, &end1, 10);
long num2 = strtol(str2, &end2, 10);
printf("%d\n", num1);
printf("%d\n", num2);
printf("\n");
if(*end1 == '\0')
printf("str1の変換は成功\n");
else
printf("str1の変換は失敗\n");
if (*end2 == '\0')
printf("str2の変換は成功\n");
else
printf("str2の変換は失敗\n");
getchar();
}
0 0 str1の変換は成功 str2の変換は失敗
第二引数endptr
は文字列の終端まで変換に成功すると終端のNULL文字の位置を指した状態となります。
つまりendptr
とNULL文字が等しいかを比較することで、変換成功か失敗かを判別できます。
ただしこの方法では文字列の途中まで読み取れた場合に正しく判定できません。
char *end;
long num = strtol("0a", &end, 10);
printf("%d\n", num);
if (*end == '\0')
printf("変換成功\n");
else
printf("変換失敗\n");
0 変換失敗
実際には「0a」の先頭の「0」は変換できていますが失敗と表示されています。
(全部を変換できなかったと考えれば失敗と言えますが)
以下のように、endptr
とstr
の「メモリ上の位置」同士を比較することで成功か失敗かを判定することは可能です。
メモリ上の位置が異なる場合、少なくとも一文字以上は読み取れたことになります。
(これの意味の理解にはポインタの知識が必要です)
const char str[] = "0a";
char *end;
long num = strtol(str, &end, 10);
printf("%d\n", num);
if (end != str)
printf("変換成功\n");
else
printf("変換失敗\n");
0 変換成功
変換エラーへの対処2
strtol
関数は文字列をlong型の数値に変換しますが、これよりも大きな値を解釈しようとすると変換に失敗します。
long型の最大値を超える値の文字列を変換しようとすると戻り値はlong型で扱える最大値に、最小値よりも小さな値の文字列を変換しようとすると戻り値はlong型で扱える最小値になります。
long型の最大値はLONG_MAX
、最小値はLONG_MIN
という定数が使用できます。
そしてこのとき、errno
という特殊な変数にERANGE
という定数がセットされます。
errno
はいくつかの標準関数で使用される、何らかのエラーが発生した場合にエラーの情報を格納する特殊な値です。
この値はグローバル変数(のようなもの)で、プログラム中のどこからでもアクセス可能です。
errno
を正しく使用するには、エラーチェックしたい関数を呼び出す直前にerrno
に0をセットします。
そして、エラーチェックしたい関数の呼び出し直後に、errno
の値を調べてエラーの有無を判断します。
ちなみにプログラムの起動時は0がセットされています。
なお、C言語標準関数の全てがエラー発生時にerrno
を書き換えるわけではないので注意が必要です。
(atoi
関数は書き換えません)
errno
を使用するには<errno.h>
をインクルードします。
定数LONG_MAX
/LONG_MIN
は<limits.h>
をインクルードすると使用できます。
(インクルードしなくても戻り値自体は受け取れます)
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
long MyStrToL(const char* str, char** ptr, int radix)
{
//他の関数で書き換わっている可能性があるので
//事前にerrnoを0にセットしておく
errno = 0;
long num = strtol(str, ptr, radix);
if(num == LONG_MAX && errno == ERANGE)
printf("long型の最大値を超えた変換を行おうとしました。\n");
else if (num == LONG_MIN && errno == ERANGE)
printf("long型の最小値を超えた変換を行おうとしました。\n");
//errnoを0に戻しておく
errno = 0;
return num;
}
int main()
{
//LONG_MAX/LONG_MIN
printf("LONG_MAX: %d\n", LONG_MAX);
printf("LONG_MIX: %d\n", LONG_MIN);
printf("\n");
//long型の最大値より1大きい値
const char strMax[] = "2147483648";
//long型の最小値より1小さい値
const char strMin[] = "-2147483649";
printf("%d\n", MyStrToL(strMax, NULL, 10));
printf("%d\n", MyStrToL(strMin, NULL, 10));
getchar();
}
LONG_MAX: 2147483647 LONG_MIX: -2147483648 long型の最大値を超えた変換を行おうとしました。 2147483647 long型の最小値を超えた変換を行おうとしました。 -2147483648
なお、細かい点ですが、関数がエラーの可能性を示す値を返すようになっている場合はまずその値が返されたことをチェックしてからerrno
の値を調べるのが良いそうです。
strtol
関数ならばLONG_MAX
またはLONG_MIN
が返されたことを先にチェックします。
C言語の仕様上、関数でエラーが発生したときにerrno
を書き換えることになっていますが、エラーが発生していないときに書き換えることは禁止されていないためです。
その他のデータ型への変換
strtol
関数はlong型への変換ですが、他のデータ型へ変換するための関数も存在します。
関数名 | データ型 | 最大値/最小値を超える場合 | 備考 |
---|---|---|---|
strtol | long | LONG_MAX LONG_MIN |
|
strtoul | unsigned long | ULONG_MAX ULONG_MIN |
|
strtoll | long long | LLONG_MAX LLONG_MIN |
|
strtoull | unsigned long long | ULLONG_MAX ULLONG_MIN |
|
strtof | float | HUGE_VALF -HUGE_VALF |
第三引数(基数の指定)はない 定数の使用には math.h をインクルードする |
strtod | double | HUGE_VAL -HUGE_VAL |
第三引数(基数の指定)はない 定数の使用には math.h をインクルードする |
strtold | long double | HUGE_VALL -HUGE_VALL |
第三引数(基数の指定)はない 定数の使用には math.h をインクルードする |
使い方は基本的にstrtol
関数と同じです。
ただし小数型への変換は第三引数の基数の指定はありません。
また、最大値を超える変換を行った場合の戻り値はHUGE_VAL
という定数となります。
(float型はHUGE_VALF
、long double型はHUGE_VALL
)
これは「巨大な正の値」を表す定数で、具体的な値は処理系によりますがおおむね無限大を表すものです。
最小値を下回る場合は-HUGE_VAL
で、「巨大な負の値」となります。
この定数はmath.h
をインクルードすることで使用できます。
さらに小数型は、限りなく0に近い値となる文字列を数値に変換しようとしたとき、アンダーフローが発生します。
アンダーフローが発生した場合は0が返されます。
errno
にERANGE
をセットするかどうかはコンパイラにより異なります。
ここに無い型への変換はキャストが必要です。
その際にはオーバーフローに注意してください。