はじめに
C/C++では、日本語を扱うのが厄介な問題となる。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "日本語サンプル";
int length = strlen(str);
printf("1文字目: %c\n", str[0]);
printf("長さ: %d\n", length);
return 0;
}
$ gcc -o bad_example1 bad_example1.c
$ ./bad_example1
1文字目: �
長さ: 21
このように、単なるchar型として扱うと日本語を上手く処理できない。まあ、日本語が2バイト以上で表現されているので当たり前といえば当たり前なんだけど。
そこで、C/C++で上手に日本語を処理するための方法を2通り紹介したい。
ワイド文字を使う
ワイド文字は、16ビット固定長で表現される多言語文字体型のことである。
C言語では、wchar_t型を用いてワイド文字を扱うことができる。
しかし、ワイド文字を扱うには、ロケールの設定が必要である。日本語だけ扱い場合はja_JP.UTF-8にセットする。
localeの動作は処理系依存らしいので、下記のコードはWindowsやMacでは正常に動作しない可能性があります。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
int main() {
setlocale(LC_CTYPE, "ja_JP.UTF-8");
char str1[] = "(゚∀゚)( ゚∀)超( ゚)絶( )大(゚ )興(∀゚ )奮(゚∀゚)━キター!!!!";
// 十分なメモリ領域を確保
size_t capacity = strlen(str1) + 1;
wchar_t *str2 = (wchar_t *)malloc(sizeof(wchar_t) * capacity);
// char -> wchar_tの変換
int result = mbstowcs(str2, str1, capacity);
if (result <= 0) {
fprintf(stderr, "マルチバイト文字列の変換に失敗\n");
return EXIT_FAILURE;
}
printf("バイト長: %lu\n", capacity - 1);
printf("長さ: %d\n", result);
// 十分なメモリ領域を確保
capacity = wcslen(str2) * 6 + 1;
char* str3 = (char *)malloc(sizeof(char) * capacity);
// wchar_t -> charの変換
result = wcstombs(str3, str2, capacity);
if (result <= 0) {
fprintf(stderr, "ワイド文字列の変換に失敗");
return EXIT_FAILURE;
}
printf("文字列: %s\n", str3);
printf("1文字目: %lc\n", str2[0]);
}
$ gcc -o example1 example1.c
$ ./example5
バイト長: 86
長さ: 44
文字列: (゚∀゚)( ゚∀)超( ゚)絶( )大(゚ )興(∀゚ )奮(゚∀゚)━キター!!!!
1文字目: (
いろいろと調べたが、UTF-8の場合は最大で1文字6バイトを使って表されるので、char型に変換する際は6倍のメモリを確保しておけば問題ない。
wchar_t型の変数を出力する場合、文字の場合の書式文字列は%lc、文字列の場合は%lsとすればよい。
あと、wprintfやwcoutは、printfやcoutと併用すると挙動がおかしくなることがあるため、なるべく使わないほうが無難である。
一度char型に変換してから出力するのが最も安全だろう。
utfcppを利用する
こちらはC++限定だが、wchar_t型に変換せずにマルチバイト文字列を扱うことができるので便利。
utfcpp: https://github.com/nemtrif/utfcpp
これを使うと、文字列の長さや文字位置のイテレータなどが簡単に構成できる。
#include <iostream>
#include <cstring>
#include <cstdint>
#include <utf8.h>
int main() {
const char* str1 = u8"明日天気にな〜れ";
const char* it = str1;
const char* end = str1 + strlen(str1);
// 文字列の長さを取得
int length = utf8::distance(it, end);
std::cout << "長さ: " << length << std::endl;
std::cout << std::hex;
while (it < end) {
// イテレータを1文字分すすめる
std::uint32_t code = utf8::next(it, end);
// 該当位置の文字コードを出力
std::cout << code << std::endl;
}
return 0;
}
$ g++ example2.cpp -o example4
$ ./example2
長さ: 8
660e
65e5
5929
6c17
306b
306a
301c
308c
他にも便利な関数がたくさんあるので、Githubのほうを参照してほしい。
まとめ
C/C++でも日本語扱えるぞ!