C++でC風ライブラリを作る(文字列→整数変換編)
高木です。おはようございます。
ずいぶん間があいてしまいましたが、何とか再開したいと思います。
今回からは文字列から整数への変換を行う関数を定義することにします。
そこそこ複雑になるので、何回かに分割して書いていきますね。
標準Cライブラリには、strtol
、strtoul
、strtoll
、strtoull
、strtoimax
、そしてstrtoumax
関数があります。
また、それらの簡易版ともいうべきatoi
、atol
、およびatoll
関数もあります。
atoi
関数だけは、int
型とlong
型のサイズが異なる処理系では効率面で有用ですが、それを除けば、本当はstrto~
だけあれば十分です。
今回設計&実装するのは、strto~
系の関数とato~
系の関数のいいところ取りをしたいと思います。
どういうことかというと、strto~
系同様のエラー処理ができて、なおかつato~
系同様の効率のよさを実現します。
具体的には、基数をテンプレート引数で指定することにします。
まあ、普通に実行時の引数で基数を指定するバージョンも作ってもよいのですが、今回は面倒なので省略することにしましょう。
ほとんどのケースで基数は決め打ちで十分だと思いますので。
今回は話を簡単にするために、符合無し整数だけを扱うことにします。
また、strtoul
などでは先行する空白類文字を読み飛ばしますが、今回は数字から始まる前提で作ります。
数字から始まる前提ですので符合も扱いません。
文字コードに関しては、以前書いたように0x00~0x7fはASCII相当を仮定しています。
CまたはC++の規格上はアルファベットが連続していることは保証されていないのですが、ASCII相当という仮定によってアルファベットが連続している前提での実装が可能になります。
それでは実装を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | namespace cloverfield { namespace detail { template <typename Type, int Radix, typename Iterator> Type strto_helper(Iterator first, Iterator last, Iterator* endit) { static_assert(2 <= Radix && Radix <= 36, "Radix must be from 2 to 36."); static_assert(std::is_unsigned<Type>::value, "Type must be unsigned integral type."); using char_type = typename std::iterator_traits<Iterator>::value_type; Type result = 0; constexpr auto max = std::numeric_limits<Type>::max(); for (; first != last; ++first) { auto c = tolower(*first); int d; if (char_type('0') <= c && c <= char_type('9')) d = c -'0'; else if (char_type('a') <= c && c <= char_type('z')) d = c - 'a' + 10; else break; if (d >= Radix) break; if (result > (max - d) / Radix) { result = max; break; } result = result * Radix + d; } if (endit) *endit = first; return result; } } } |
オーバーフローを検出するあたりがちょっと難しいかもしれませんね。
実際にオーバーフローさせてしまってからだと検出が困難なので、その前に検出を行っています。
あとは見ての通りだと思います。
それでは、今回の内容を踏まえて、次回は符合無し整数型に関して、完成まで持っていきたいと思います。