LoginSignup
4
4

More than 3 years have passed since last update.

c++で、UTF-8のbyte数を判定することでマルチバイトの文字列をカウントする

Last updated at Posted at 2020-12-30

初投稿

自分の学習成果をアウトプットしたいと感じたため、Qiitaにまとめてみます。
初めての投稿のため見づらいと思いますが、温かい目で見守ってください。

概要

c++での半角全角文字を織り交ぜた文字数のカウントにおいては、wstringにおけるカウントの手法がweb上には多いように感じました。
この記事では1文字のbyte数判別して、文字列の文字数をカウントする方法をまとめてみます。
マルチバイトの文字コードはutf-8とします。

char[]

c++では、'あ'などマルチバイトの文字コードの文字を表現するのに、char型の配列を用いられます。
charは1byteの数です。
1byteは8bitですから,16進数では2桁,2進数では8桁で表現できます。
つまり、16進数では00~ff,2進数では00000000~11111111までが表現できます。

このcharを配列として扱い、文字コードを格納することで文字を表現しています。
例えば、
'a'の文字コードは 0x61,
1byte必要なのでcharを一つ、
'あ'の文字コードは0xE38182,
3byte必要なのでcharを3つで表現できます。

具体的に中身を見てみます。

シングルバイト

'a'の文字コードは16進数で0x61、2進数で01100001と表現されます。
そのためcharは一つで表現することが出来ます。
'a'を表現するchar[]型の変数str1を作成してみます。

char str1[] = "a";

str1の配列のサイズと
str1に何が格納されているのかを確認します

// str1のサイズ
int size =  sizeof(str1) / sizeof(str1[0]);
// 各要素になにが格納されているか
printf("size = %d\n",size);
for(int i = 0; i < size ;i++){
   printf("str1[%d] = %#x\n",i,str1[i]);
}

実行結果

size = 2
str1[0] = 0x61
str1[1] = 0

なるほど

確かに文字コードが格納されているのが分かりました。
しかし何故文字コードがchar一つで表現できているのに、str1のサイズは2なのでしょうか?
答えはc++では、配列の最後に終端文字'\0'が格納されるためです。
char配列は一個サイズを多めにとってるんですね。

次に2つ以上のバイトで表現する文字コードのchar[]を確認してみます。

マルチバイト

'あ'の文字コードは16進数で0xE38182、2進数で11100011 10000001 10000010と表現されます。
charは3つで表現できます。
'あ'を表現するchar[]型の変数str2を作成してみます。

char str2[] = "あ";

str2の配列のサイズと
str2に何が格納されているのかを確認します

// str2のサイズ
size = sizeof(str2) / sizeof(str2[0]);
// 各要素になにが格納されているか
printf("size = %d\n", size);
for (int i = 0; i < size; i++) {
   printf("str2[%d] = %#x\n", i, str2[i] & 0xff);
}

実行結果

size = 4
str2[0] = 0xe3
str2[1] = 0x81
str2[2] = 0x82
str2[3] = 0

ふむふむ,

確かに、3つのcharで表現できているらしいですね。
[0]から順に文字コードが格納されているのが分かりますね。

文字列

様々な文字を使った文字列はどのようになっているのかを確認するため、
"aあ亜"を表現するstrsを作成して中身を見てみます。

char strs[] = "aあ亜";
// strsのサイズ
size = sizeof(strs) / sizeof(strs[0]);
// 各要素になにが格納されているか
printf("size = %d\n", size);
for (int i = 0; i < size; i++) {
   printf("strs[%d] = %#x\n", i, strs[i] & 0xff);
}

実行結果

size = 8
strs[0] = 0x61
strs[1] = 0xe3
strs[2] = 0x81
strs[3] = 0x82
strs[4] = 0xe4
strs[5] = 0xba
strs[6] = 0x9c
strs[7] = 0

なんか文字列が分かってきた気がしました。

サイズは文字列を表現するためのバイト数を表していて、
中身を確認すると、どうやら文字は配列で繋がっていることが分かりました。

文字列の文字数をカウントするには、
文字コードがどのようなものを表していて、文字を表現するのに何byte使用しているのかが判別できれば行えそうです。

例えば、strsの文字数の判別を行うとしたら、
strs[0]の0x61を読み取り1byteだと判別、1byte先のstrs[1]へ
strs[1]の0xe3を読み取り3byteだと判別、3byte先のstrs[4]へ
strs[4]の0xe4を読み取り3byteだと判別、3byte先のstrs[7]へ
strs[7]で終端文字'\0'を読み取り終了
のようなフローチャートで文字数をカウントすることが可能です。

文字コード判別

1byte

では初めに'a'など1byteで表現するシングルバイトの文字コードを判別してみます。

シングルバイトの文字コードは0x00~0x7fまでが割り振られています。
2進数で表すと
00000000~01111111

つまり1bit目が0であるなら文字コードは1byteであると判別できます。

判別する関数はif文とbit演算子を用いて簡単に実装することができます。

bool is_single(unsigned char c) {
    if ((c & 0x80) == 0) {
        return true;
    }
    return false;
}

str1でテスト

char str1[] = "a";
if(is_single(str1[0])){
   printf("%#x is single",str1[0]);
}

実行結果

0x61 is single

2byte

次に2byteの文字コードを判別します

utf-8では2byteの文字コードの1byte目は
0xc2~0xdfが割り当てられていて
2進数で表すと
11000010~11011111

1bit目が1、2bit目が1、3bit目が0であるなら文字コードは2byteであると判別できます。

bool is_double(unsigned char c) {
    if ((c & 0xe0) == 0xc0) {
        return true;
    }
    return false;
}

str3でテスト

char str3[] = "À";
if (is_double(str3[0])) {
    printf("%#x is double", str3[0] & 0xff);
}

実行結果

0xc3 is double

3byte

次に3byteの文字コードを判別します

utf-8では3byteの文字コードの1byte目は
0xe0~0xefが割り当てられていて
2進数で表すと
11100000~11101111
までです。

1bit目が1、2bit目が1、3bit目が1、4bit目が0であるなら文字コードは3byteであると判別できます。

bool is_triple(unsigned char c) {
    if ((c & 0xf0) == 0xe0) {
        return true;
    }
    return false;
}

str2でテスト

char str2[] = "あ";
if (is_triple(str2[0])) {
    printf("%#x is triple", str2[0] & 0xff);
}

実行結果

0xe3 is triple

文字のbyte数を判別する

上記の判別式から1byte目を読み取ることで何byteの文字コードなのかを判別する関数を作成します

int count_byte(char c){
    if ((c & 0x80) == 0) {
        return 1;
    }
    if ((c & 0xe0) == 0xc0) {
        return 2;
    }
    if ((c & 0xf0) == 0xe0){
        return 3;
    }
    return 4;
}

色々なbyte数の文字コードの文字でテスト

char str4[] = "b";
char str5[] = "©";
char str6[] = "亜";
printf("%sのbyte数は%d\n",str4,count_byte(str4[0]));
printf("%sのbyte数は%d\n",str5,count_byte(str5[0]));
printf("%sのbyte数は%d\n",str6,count_byte(str6[0]));

実行結果

bのbyte数は1
©のbyte数は2
亜のbyte数は3

無事カウントできました。
感動です!

文字列の文字数カウント

いよいよbyte数をもとに文字列の文字数をカウントしましょう。

設計は以下の通りです。

step1: 初めに初期値0の変数countを用意します。
step2: 文字列の先頭のcharを読み取りbyte数を判別して、countを1増加させます。
step3: 判別したbyte数分の先の配列の要素が終端文字'\0'かどうかを判別します。
step4: 終端文字でなければ、要素のcharのbyte数を判別して、countを1増加させます。step3へ戻ります。
step5: 終端文字であれば終了し、countを返却する。

では実装します

int count_char(char* strings){
    int count = 0;
    char c = *strings;
    while(c != '\0'){
        count += 1;
        strings += count_byte(c);
        c = *strings;
    }
    return count;
}

文章でテスト

unsigned char strs2[] = "著作権マーク(ちょさくけんマーク)またはコピーライトマーク(copyright mark)とは、大文字のCを丸で囲んだ記号(©)であり、音声録音[1]以外の作品の著作権表示に使用される記号である。";
printf("strs2 の文字数は%d\n",count_char(strs2));;

実行結果

strs2 の文字数は98

カウント出来ました!

初めてだったので要らないこと結構書いてしまっていると思いますが、丁寧に読んでいただきありがとうございました。

4
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4