UTF-8の文字列を一文字ずつ表示する
C言語でUTF-8を一文字ずつ表示する方法を知りたいと言われ、すぐに答えが出てこなかった。
ネットでUTF-8のフォーマットを調べて、一応UTF-8の文字列を一文字ずつ表示することが出来たので紹介する。
具体的なプログラムを示す前に、UTF-8のエンコード方式について簡単にまとめる。
- UTF-8は半角英数字とは違って、最大6byteを使って1文字を表す。
- UTF-8の文字の1byte目は、UTF-8文字が使用するバイト数を示している。
1byte目で先頭から連続するbit"1"の個数が、そのままUTF-8文字の1文字を表すのに必要なバイト数となる。
ただし後述するように、"10"から始まるパターンが先頭に来る場合があってはならない。
(例: 11110100 -> 4byte, 11001100 -> 2byte, 01110010 -> 1byte) - 2byte目以降は、UTF-8文字であることを確認するために、"10"で始まるビットパターンである必要がある。
上記のエンコード方式を考慮して作成した、UTF-8文字を1行ずつ改行して表示するプログラムを以下に示す。
*このプログラムが正常に動くのは、ソースコードをUTF-8で保存した場合に限るので注意して欲しい。
// UTF8文字のバイト数を調べる例 #include <iostream> // 次のUTF8の文字で使用するバイト数を取得 int get_utf8char_byte( char* p ) { int byte = 0; // バイト数 // 最初に1となるbitを検索 for( int i = 0; i < 6; ++i ){ if( ( ( *p >> ( 7 - i ) ) & 0x01 ) == 0 ){ byte = i; break; } } // UTF8は1文字で7byte以上を使用することはない if( byte == 6 ){ return -1; } // 10から始まる先頭バイトは存在しない else if( byte == 1 ){ return -2; } // 0から始まる時は、1byteで1文字を表すことを考慮した計算 else if( byte != 0 ){ --byte; } // 2バイト目以降は、10から始まるビットパターンでなければならない for( int i = 1; i < byte; ++i ){ if( ( ( p[ i ] >> 6 ) & 0x03 ) != 0x02 ){ return -3; // ビットパターンの不一致 } } return byte + 1; } // UTF8文字を1文字ずつ改行して表示 int main() { char* str = "★と☆の違い -> bit列と、見やすさ?"; // UTF8文字列 std::cout << "UTF8文字列 : " << str << "\n" << std::endl; std::cout << "---UTF8文字列を1文字ずつ、UTF8文字のバイト数と一緒に表示---\n" << std::endl; while( *str ){ int utf8char_size; // 次のUTF8文字のサイズ char utf8char[ 20 ]; utf8char_size = get_utf8char_byte( str ); if( utf8char_size < 0 ){ std::cout << "UTF8文字列ではありません。 (Error Code:" << utf8char_size << ")" << std::endl; return -1; } strncpy( utf8char, str, utf8char_size ); utf8char[ utf8char_size ] = '\0'; std::cout << utf8char << " -> byte数:" << utf8char_size << std::endl; str += utf8char_size; } return 0; }
プログラムの実行結果を以下に示す。
UTF-8で保存された文字列の各文字が、期待通りに1文字ずつ改行されて表示されることを確認できる。
また、このプログラムでは、各文字を表すのに必要なbyte数も表示している。
UTF8文字列 : ★と☆の違い -> bit列と、見やすさ? ---UTF8文字列を1文字ずつ、UTF8文字のバイト数と一緒に表示--- ★ -> byte数:3 と -> byte数:3 ☆ -> byte数:3 の -> byte数:3 違 -> byte数:3 い -> byte数:3 -> byte数:1 - -> byte数:1 > -> byte数:1 -> byte数:1 b -> byte数:1 i -> byte数:1 t -> byte数:1 列 -> byte数:3 と -> byte数:3 、 -> byte数:3 見 -> byte数:3 や -> byte数:3 す -> byte数:3 さ -> byte数:3 ? -> byte数:3
[2013.11.27追記]
結合文字列の場合に問題では、という反応があって調べてみた。
Mac環境では結合文字が多用されていて、2文字を1文字のように見せているらしく、「が」の場合は「か」と「゛」を1つにして表示するようである。
「か」のほうを基底文字と言い、「゛」を結合文字と言うらしい。
今回の例では、結合文字列の対応が出来ていないため、正確に1文字ずつ表示することはできない。
結合文字列については全く知らなかったので、非常に勉強になりました。
反応していただいた方、ありがとうございます。
[参考文献]
http://ja.wikipedia.org/wiki/UTF-8
http://tama-san.com/?p=54