読者です 読者をやめる 読者になる 読者になる

趣味プログラマによるOSS開発日誌

趣味で作っているOSSソフトウェアの紹介や関連技術の紹介、楽曲製作、Webデザイン勉強状況を紹介します。

UTF-8の文字列を一文字ずつ表示する

C言語UTF-8を一文字ずつ表示する方法を知りたいと言われ、すぐに答えが出てこなかった。
ネットでUTF-8のフォーマットを調べて、一応UTF-8の文字列を一文字ずつ表示することが出来たので紹介する。

具体的なプログラムを示す前に、UTF-8エンコード方式について簡単にまとめる。

  1. UTF-8は半角英数字とは違って、最大6byteを使って1文字を表す。
  2. UTF-8の文字の1byte目は、UTF-8文字が使用するバイト数を示している。
    1byte目で先頭から連続するbit"1"の個数が、そのままUTF-8文字の1文字を表すのに必要なバイト数となる。
    ただし後述するように、"10"から始まるパターンが先頭に来る場合があってはならない。
    (例: 11110100 -> 4byte, 11001100 -> 2byte, 01110010 -> 1byte)
  3. 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