C++入門/文字列

柴田祐樹,東京都立大学,情報科学科
戻る

文字を配列に格納し,また,その最後要素が0であるものを文字列とC++では定義する.この文字列はcoutに渡すことでまとめて画面へ出力できたりする.以下に例を示す.ここでは厳密な理解を促すために文字符号を直接代入している.

#include <iostream>
int main(){
    char S[13];
    S[0] = 104;
    S[1] = 101;
    S[2] = 108;
    S[3] = 108;
    S[4] = 111;
    S[5] = 32;
    S[6] = 119;
    S[7] = 111;
    S[8] = 114;
    S[9] = 108;
    S[10] = 100;
    S[11] = 10;
    S[12] = 0;
    char* s = S;
    std::cout << s;
    return 0;
}

文字リテラルを用いて,以下のように記載しても構わない.

#include <iostream>
int main(){
    char S[13];
    S[0] = 'h';
    S[1] = 'e';
    S[2] = 'l';
    S[3] = 'l';
    S[4] = 'o';
    S[5] = ' ';
    S[6] = 'w';
    S[7] = 'o';
    S[8] = 'r';
    S[9] = 'l';
    S[10] = 'd';
    S[11] = '\n';
    S[12] =  0;
    char* s = S;
    std::cout << s;
    return 0;
}

だが日本語を出力しようとなると厄介である.UTF-8 (Universal Coded Character Set)の規格によれば,「あ」を表現するためには2進数で第1, 2, 3文字に11100011,10000001, 10000010とそれぞれ指定しなければならない.具体的には次のとおりである.後にもっと簡素な書き方をすぐに説明するので,これは知識として知っておくだけで良い.C++で2進数を表現するには0bを数値の前につける.

#include <iostream>
int main(){
    char S[13];
    S[0] = 0b11100011;
    S[1] = 0b10000001;
    S[2] = 0b10000010;
    S[3] = 108;
    S[4] = 111;
    S[5] = 0;
    char* s = S;
    std::cout << s;
    return 0;
}

出力は次のようになる.

[shibata@127 cpp]$ ./a.out あlo

このような,複数の数値を組み合わせて文字を表現する方法も,文字と数値の対応付けであり,符号と呼ばれる.UTF-8は符号化方式の一つで,LinuxやWebページなど世界で標準的に使われている文字符号化方式である.もう一つだけ例を挙げる.「こんにちは」と出力するには,次のように記載する.ちなみにこの方法を実用の場面で使う機会はほぼはずなので,このようなことができるという事実だけ覚えておけば良い.

#include <iostream>
int main(){
    char S[20];
    int i=0;
    S[i] = 0b11100011; i = i + 1;
    S[i] = 0b10000001; i = i + 1;
    S[i] = 0b10010011; i = i + 1;
    S[i] = 0b11100011; i = i + 1;
    S[i] = 0b10000010; i = i + 1;
    S[i] = 0b10010011; i = i + 1;
    S[i] = 0b11100011; i = i + 1;
    S[i] = 0b10000001; i = i + 1;
    S[i] = 0b10101011; i = i + 1;
    S[i] = 0b11100011; i = i + 1;
    S[i] = 0b10000001; i = i + 1;
    S[i] = 0b10100001; i = i + 1;
    S[i] = 0b11100011; i = i + 1;
    S[i] = 0b10000010; i = i + 1;
    S[i] = 0b10001111; i = i + 1;
    S[i] = 10;         i = i + 1;
    S[i] = 0;
    char* s = S;
    std::cout << s;
    return 0;
}

ここまで,文字列というのが,文字型charの配列に数値を格納して,最後に0を入れることで表現されるものであることを説明した.そしてC++の標準機能であるcoutはこの文字列を受取り正しく画面へ出力が可能なことを見てきた.しかし,文字列を作るために配列の要素を一つずつ初期化していては多大な手間である.日本語のような複数のcharを用いて符号化される文字の表現においてはこの指定方法は現実的ではない.そこで,C++では,""を用いて,その中に記載された文字列の配列を表現することが可能とされている.具体的に次に例を示す.

#include <iostream>
int main(){
    const char* s = "hello world\n";
    std::cout << s;
    return 0;
}

繰り返しになるが,上記コード中"hello world\n"は,配列を表す.その配列は,hello worldのそれぞれの文字と最後に入れる数値0を入れるのに十分な長さのものがコンパイラにより用意される.配列を指定するためには,その記憶域のアドレスをポインタ変数に格納して扱うことになっている.実際に,上記3行目の通り,初期化によりアドレスをポインタ(ここではs)で受け取ることができる.

以下のように日本語を含む文字列を定義することも可能である.この場合も,UTF-8の方式に従った場合の必要な配列の長さが自動で計算され,またその記憶域が用意されたうえ,その配列に符号が格納された状態でアドレスが s へ格納される.

#include <iostream>
int main(){
    const char* s = "こんにちは.😀.文字列の試験です⚠Σλη\n";
    std::cout << s;
    return 0;
}

実行結果は次のとおりである.

[shibata@127 cpp]$ g++ string.cpp [shibata@127 cpp]$ ./a.out こんにちは.😀.文字列の試験です⚠Σλη

""で指定されたものが配列なのだから,以下のようなコードも書くことができるはずであり,実際に動作する.文字符号を数値とみなしてその和を求めるプログラムである.ついでに配列の長さも測っている.

#include <iostream>
int main()
{
    int a=0;
    int i=0;
    while(true){
        char b = "こんにちは.😀.文字列の試験です⚠Σλη"[i];
        if(b == 0) {
            break;
        }
        a = a + b;
        i = i + 1;
    }
    std::cout << i << "\n";
    std::cout << a << "\n";
    return 0;
}

実行結果を以下に示す.string.cppは上記を保存したファイルの名前である.

[shibata@127 cpp]$ g++ string.cpp [shibata@127 cpp]$ ./a.out 58 -4497

配列の長さは58であるとわかる.見た目では20字しかないので,日本語がcharを複数使って符号化されていることがわかるだろう.繰り返しの終了判定は,0を配列の中に発見したときとしている.この0は""で用意される配列の末尾に必ず存在するため,このような判定が可能となる.

以下のような代入を行うことは不可能である.とくに,7行目は,緩いコンパイラならコンパイルを通すようだが,実行時に動作不良となるため,発見困難なバグのもとであるから気をつけていただきたい.

#include <iostream>
int main()
{
    "hello world\n"[0] = 0;

    char *p = "hello world\n";
    p[1] = 0;

    return 0;
}

std::cout は char* 型の変数を受け取ると,対応する配列の開始アドレスを取得し,文字を一つづつ読み取っていき,0 が現れた時点で読み込みを終了する.この最後に 0 を置くことで配列の終端を表すという習慣があるため,配列の長さがわからなくても,繰り返し回数を判断することができるようになっている.一方で, 0 を入れ忘れると永遠と読み出していき,文字列以外が定義されている領域の値まで読み出すため,意図しない値が渡されることになる.これはバグの原因であり,文字列を表すための配列の最後に 0 を入れ忘れることによるバグは非常に多いと言われる.これを避けるために,実用の場面で文字列を操作するなら,std::string を使ったほうがいい.ここで紹介したような危険なコードが書けないようになっている.また,必ず 0 が最後に挿入されるようになっている.不要なときも 0 が挿入されるため,速度の低下を嫌う者が居ると聞くが,他のインタプリタ言語で書くことに比べればそれでもずっと高速なので,それほど気にする必要はないと思う.不要な 0 の挿入コードを削減するような最適化をコンパイラができるようになるまで待ったほうが賢明だろう.実際そういったことを行うために情報科学の研究はなされている.