単純なキャスト方法だけでなく、数字を数値に変換するテクニックや文字列をint型やdouble型の数値に変換する方法も紹介します。
目次
- 数字から数値に変換する(digit char to number,
'9'
→9
) - 単純なキャスト(char to int,
(int)'9'
) - 文字列から数値に変換する(string to number,
"99"
→99
)
数字から数値に変換する
文字型の数字を数値に変換する方法です。単純なキャストではなく、char型の数字'9'
をint型の整数値9
に変換します。今回紹介するテクニックはJavaやPHP等のメジャーなプログラミング言語でも活用できます。
char c = '1';
int i = c - '0';
printf("%d", i); // 1
このように数字 - '0'
という記述を用います。
これは文字型の内部表現が整数値であることを活用したテクニックです。実際に'1'
という文字は49
という整数値で表現されています。文字'0'
の場合は48
です。
printf("%d", (int)'1'); // 49
printf("%d", (int)'0'); // 48
ですから、'1'
と'0'
の文字同士を引き算した場合には、数値49
と48
同士の引き算が行われることになり、結果として1
(49 - 48)という値が導き出されます。
printf("%d", '1' - '0'); // 1
また文字定数'2'
の内部値は50
ですから、'2' - '0'
の結果は当然2
(50 - 48)となります。
printf("%d", '2' - '0'); // 2
ASCIIコードやUTF-8、UTF-16等、多くの文字コードでは、文字0
〜9
は連番で定義されているため、数字 - '0'
という式は多くの場面で汎用的に用いることができます。
参考までに、文字0
〜9
までのASCIIコード表を示しておきます。
文字 | 10進数 | 16進数 |
---|---|---|
'0' | 48 | 0x30 |
'1' | 49 | 0x31 |
'2' | 50 | 0x32 |
'3' | 51 | 0x33 |
'4' | 52 | 0x34 |
'5' | 53 | 0x35 |
'6' | 54 | 0x36 |
'7' | 55 | 0x37 |
'8' | 56 | 0x38 |
'9' | 57 | 0x39 |
これらのように数字文字の連番が保証されている環境であれば、以下のような数字判定処理を書くこともできます。
bool isDigit(char c) {
return c >= '0' && c <= '9';
}
isDigit('8'); // true
isDigit('a'); // false
ctoi関数の実装方法
範囲チェック付きの変換関数です。数字以外の文字が渡された場合には、戻り値のデフォルト値として0
を返します。
int ctoi(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
return 0;
}
assert( 0 == ctoi('0') );
assert( 9 == ctoi('9') );
assert( 0 == ctoi('a') );
assert( 0 == ctoi('A') );
必要に応じて-1
を返すように改変したり、例外を送出するのも良いでしょう。
より確実な変換方法
より確実で安全な変換を実現するためには、対象の文字型の数字を一つ一つチェックするしかありません。
int ctoi(char c) {
switch (c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
default: return 0;
}
}
もっとも、文字型の定数値0
〜9
の値の連続性は、言語仕様によって厳格に保証されている場合もあります(C/C++等)。
単純なキャスト
char型をint型にキャストする場合はunsigned char
型にキャストする必要があります。
char c = 'a';
int i = (unsigned char)c;
printf("%d", i); // 97
符号付きのchar型は符号拡張によって予期せぬ変換が行われる場合があるため注意してください。マルチバイト文字やバイナリーを処理する際に問題となります。
char c = (char)255;
printf("%d", c); // -1
printf("%d", (int)c); // -1
printf("%d", (unsigned char)c); // 255
char型の255は2進数で0b11111111
という表現がされていますが、これをint型に変換すると0b11111111111111111111111111111111
と符号拡張されてしまい結果、値は-1
となってしまうのです。
逆にunsigned char型を経由した変換であれば、符号拡張が起こらないため、int型への暗黙キャスト時には0b00000000000000000000000011111111
という変換が行われ、値255
を維持することができます。
符号なし型への変換が必要なケースと不要なケース
int型の引数を受け取る関数によっては、255
と-1
(EOF)を区別したり、256
以上-2
以下の値の受け取りを未定義動作とするものもあるため注意が必要です。特に文字をint型として受け取るis系の標準関数(isalpha
, isdigit
等々)を利用する際には明示的なキャストが必要となります。
int main(int argc, const char *argv[]) {
// is系関数の場合は符号拡張を考慮する
return isdigit((unsigned char)argv[0][0]);
// この程度なら不要
return isdigit('c');
// キャストが不要な関数もある
return putchar(**argv);
}
なおputchar
関数やfputs
関数, memchr
関数に関しては、受け取った引数を内部的にunsigned char型に変換した状態で処理してくれるため、実質的にはキャストが不要となります。
規格上は問題ありませんが、念のため処理系や実行環境のリファレンスをチェックしておきましょう。
また符号拡張と整数拡張によって引き起こされる先程の問題については、コンパイラ側が事前に警告を行ってくれる場合もあります。
char c = 0xff;
puts(c == 0xff ? "y" : "n"); // "n"
// 警告:Comparison of constant 255 with expression of type 'char' is always false
以下のようなケースでは警告が発生しないため注意が必要です。
void test(int c) { puts(c == 0xE3 ? "y" : "n"); }
int main() {
char s[] = "あ"; // {0xE3, 0x81, 0x82, '\0'}
test((unsigned char)s[0]); // "y"
test(s[0]); // "n"
}
なお、関数側でunsigned char型にキャストすることで、この問題を回避することも可能です。呼び出し側での明示的なキャストも不要となります。
void test(int c) {
puts((unsigned char)c == 0xE3 ? "y" : "n");
}
test(s[0]); // "y"
また、純粋に文字のみを受け取る関数を独自定義する際には、unsigned char
型で値を受け取るようにすると余計な混乱が減ります。
void test(unsigned char c) { puts(c == 0xE3 ? "y" : "n"); }
test(s[0]); // "y"
test(s[0]); // "y"
ちなみに、char型への再変換を内部的に行うstrchr
等のライブラリ関数もあります。この場合もunsigned char型への明示的なキャストは不要です。
char *strchr(char *s, int c) {
char ch = c; // キャスト
while (ch != *s) if (*++s == '\0') return 0;
return s;
}
char s[] = "あ"; // {0xE3, 0x81, 0x82, '\0'}
char c = *s;
assert(strchr(s, c)); // success!
上記の対策がされていない関数に対してunsigned char型への明示的なキャストを行ってしまうと、想定外の動作をしてしまう場合があります。
char *strchr(char *s, int c) {
while (c != *s) if (*++s == '\0') return 0;
return s;
}
char s[] = "あ"; // {0xE3, 0x81, 0x82, '\0'}
char c = *s;
assert(strchr(s, (unsigned char)c)); // failed!
signed charをintに変換する場合
なおsigned char
と言う形で符号付きのchar型であることが明示されているようなケースでは、そのままint型へキャストしたほうが正しい判断と言えます。
signed char c = -2;
int i = (int)c; // 明示的な型キャスト
int j = c; // 暗黙のキャスト
文字列から数値に変換する
文字列型からint型の数値に変換する場合にはatoi
関数を用います。
int i = atoi("9");
printf("%d", i); // 9
変換が失敗した場合には値0
が返されます。なおエラーが発生した場合の動作については処理系依存となっています。
printf("%d", atol("a")); // 0
atoi関数は厳格な変換やエラーの検知が行えない問題があるため、場合によってはより柔軟なstrtol関数を用いる必要があります。
参考: 文字列を数値に変換する方法【危険なatoi関数、厳格なstrtol関数】