文字列 | Programming Place Plus C++編【言語解説】 第4章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


関連する話題が、以下のページにあります。

C++標準ライブラリの string の利用

C言語で文字列を表現する際には、char型の配列を利用しました(C言語編第25章)。この方法は C++ でもそのまま使えますが、C++ の標準ライブラリには、より便利で安全な機能があります。それが、この章のテーマである string です。

まずは、簡単なサンプルプログラムをご覧ください。

#include <iostream>
#include <string>

int main()
{
    std::string str1("abcde");
    std::string str2;

    str2 = str1;

    std::cout << str2 << std::endl;
}

実行結果:

abcde

string を使うには、<string> という名前の標準ヘッダをインクルードする必要があります。このヘッダは、C言語の <string.h> とは異なるものですし、その C++版である <cstring> とも異なります。

string は C++標準ライブラリの1つなので、std名前空間内にあります。ですから使うときは、std::string のように記述します。

当面は、std::string は構造体型のような型の一種であると考えておけば良いです。ですから、std::string型の変数を宣言して使用します。

【上級】std::string は、std::basic_string というクラステンプレートに対する typedef です(第20章)。

std::string型の内側には、動的にメモリ割り当てを行う char の配列が入っています。そして、必要なタイミングでメモリの割り当てを行い、不要になれば解放を行うという操作が行われます。 そのため、std::string を使うと、文字列の実際の長さを気にする必要がなくなります。

C言語であれば、メモリの管理を自力で行う必要がありますが、これが自動化されます。これは便利でかつ安全ですから、C++ で文字列を扱うときには原則として、std::string を使うべきです

【上級】C++11 以降では、std::string の内部に小さな固定長の char型配列があり、文字列が短いときにはこの領域を使うようにして、動的メモリ割り当てを避ける実装になっていることがあります。これは、SSO (Small String Optimization) と呼ばれる最適化手法です。

初期化

main関数の最初で行っているように、以下のような方法で、std::string型の変数を宣言できます。

std::string str1("abcde");
std::string str2;

str1 の方は、初期値として “abcde” が与えられます。

str2 の方は、C言語の感覚では未初期化状態のようですが、そうではありません。きちんと、"" という空文字列で初期化されます。そのため、定義された std::string型の変数が不定な状態になっていることはなく、安全であるといえます。

また、もう1つの方法として、C言語と同じように、

std::string str3 = "abcde";

と書くことも可能です。この場合も “abcde” で初期化されます。

【上級】str1、str3 はいずれも、仮引数が const char*型のコンストラクタ第13章)を呼び出しています。

代入

続いて、次のようなコードがあります。

str2 = str1;

これは、変数str1 の内容を 変数str2 へ代入しています。

char型の配列では、このような代入はできないため、strcpy関数を使いました(C言語編第25章)。std::string を使っていれば、代入演算子を使った自然な形の代入が行えます。

出力

最後に、標準出力への出力を行っています。これは簡単で、いつものように std::cout を使えます。

std::cout << str2 << std::endl;

std::string には、このほかにも、文字列操作を簡潔にする各種機能が備わっています。

比較

文字列同士の比較については、strcmp関数のように関数を使う必要はなく、等価演算子が使えます。

#include <iostream>
#include <string>

int main()
{
    std::string s1("abcde");
    std::string s2("abcde");

    if (s1 == s2) {
        std::cout << "OK" << std::endl;
    }
    else {
        std::cout << "NG" << std::endl;
    }
}

実行結果:

OK

長さを調べる

文字列の長さは、std::string型自身が持っている機能を使って調べられます。

#include <iostream>
#include <string>

int main()
{
    std::string str("abcde");

    std::cout << str.size() << std::endl;
    std::cout << str.length() << std::endl;
}

実行結果:

5
5

length関数または size関数で取得できます。これらの関数はどちらを使ってもまったく同じ結果になります。

これらの関数は、std::string という型自身が持っているため、使い方が通常の関数とは異なっています。構造体のメンバにアクセスするときと同じように、ドット演算子(ポインタ経由ならアロー演算子)を使って呼び出しを行います。

また、空文字列かどうかを empty関数で知ることができます。

#include <iostream>
#include <string>

int main()
{
    std::string str;
    std::string* p = &str;

    std::cout << str.empty() << std::endl;
    std::cout << p->empty() << std::endl;
}

実行結果:

1
1

empty関数は、文字列が空であれば真、空でなければ偽を返します。

empty関数の戻り値の型は bool型(第7章)です。そして、true か false を返します。

文字へのアクセス

std::string が持っている文字列の中の文字には、添字を使ってアクセスできます。

std::string の内部にある配列は char型なので、1つ1つの文字の型は char型です。添字アクセスに加えて、&演算子を使って、文字を指すポインタを得ることも可能です。

#include <iostream>
#include <string>

int main()
{
    std::string str("abcde");
    std::cout << str[2] << std::endl;

    str[2] = 'x';
    std::cout << str[2] << std::endl;

    char* p = &str[2];
    *p = '?';
    std::cout << str[2] << std::endl;
}

実行結果:

c
x
?

C++編では今後、文字列を使う場面では原則として std::string を使っていきます。

std::string に関するより詳細な機能は、【標準ライブラリ】第2章を参照してください。ただし、【標準ライブラリ】の方は、高度な内容も含まれている上、かなり詳細に渡って書かれており分量が多いので、深入りする必要はありません。


string への書き込み (std::ostringstream)

C言語では、sprintf関数を使って、整形された文字列を char型の配列へ書き込むことができました。同様に、std::string に対して書き込みを行いたければ、std::ostringstream を使います。

std::ostringstream を使うには、<sstream> という名前の標準ヘッダをインクルードします。

std::ostringstream の使い方は、std::cout に似せてあります。つまり、書き込みたいものを <<演算子を使って次々に指定すればよいだけです。ただし、std::cout のような事前に定義済みの存在がありませんから、まずは std::ostringstream型の変数を定義することが必要です。

目的は std::string への書き込みですが、文字列はいったん std::ostringstream型の変数に蓄えます。文字列化したい値を次々に std::ostringstream へ送り込んでいくと、その内部に文字列が作られます。最後にその結果を取り出せば目的が果たせるという流れです。

次のサンプルプログラムで確認しましょう。

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::ostringstream oss;
    oss << "otringstream sample: " << 123;

    std::string str(oss.str());

    std::cout << str << std::endl;
}

実行結果:

otringstream sample: 123

std::ostringstream型の変数へ、<<演算子で値を引き渡します。連続していくつも渡せますし、型のこともほとんど気にする必要がありません。メモリの割り当ても自動的に行うので、バッファオーバーフローの心配も不要です。

感覚的には std::cout を使うときと同じですが、弾みで最後に改行したり、std::endl を置いたりしないように気を付けましょう。少なくとも、フラッシュの処理は不要です。

std::ostringstream で文字列化された結果は、str関数を使って取り出せます。結果は、std::string型で返されます。

std::ostringstream については、【標準ライブラリ】第29章で、詳細に説明しています。

string からの読み取り (std::istringstream)

今度は反対に、std::string の内容を読み取る方法です。つまり、C言語の sscanf関数のような作業を行う方法を確認します。

std::string からの読み取りには、std::istringstream を使います。std::istringstream を使うには、<sstream> という標準ヘッダをインクルードします。

std::istringstream の使い方を、以下のサンプルプログラムで説明します。

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::string str("100 2.34");

    std::istringstream iss(str);

    int num;
    double d;
    iss >> num >> d;

    std::cout << num << " " << d << std::endl;
}

実行結果:

100 2.34

まず、読み取り対象の std::string型の変数str を定義しています。

std::istringstream型の変数を定義し、ここで読み取り対象(str)を渡しておきます。こうすると、渡した std::string の内容が std::istringstream の内部へコピーされます。コピーなので、元の std::string の内容を書き換えても影響しません。

その後は、>>演算子を使って、読み取った結果を受け取る変数を指定します。出力のときと比べると、シフト演算子の向きが反対になっています。

なお、空白文字が現れるまでを1つの区切りとして動作します

細かく動作を調整したければ、標準ライブラリのストリーム処理関連の機能を理解する必要があります。【標準ライブラリ】第29章第30章で取り上げています。

std::istringstream については、【標準ライブラリ】第29章で、詳細に説明しています。

ワイド文字列

今度はワイド文字列の話です。

C言語ではワイド文字列を、wchar_t型の配列を使って表現しました(C言語編第47章)。C++ でも同じ方法は使えますが、新しい手段である std::wstring を使った方が良いです。

std::wstring は、std::string と同様に、<string> という標準ヘッダをインクルードして使用します。

機能面では、std::string と std::wstring はまったくの同一です。違いは、1つ1つの文字を char型で表現しているのか、wchar_t型で表現しているかということだけです。

【上級】std::string の正体は std::basic_string<char> であり、std::wstring の正体は std::basic_string<wchar_t> です。どちらも std::basic_string を具体化したものであり、機能面では同一です。

C++ の wchar_t型はC言語と違って、キーワードです。何らかのヘッダをインクルードする必要はなく、どこでも使用できます。

C言語の wchar_t型は typedef された別名であり、wchar.h をインクルードする必要がありますC言語編第47章)。

実際に試してみます。

#include <iostream>
#include <string>

int main()
{
    std::wstring str1(L"abcde");
    std::wstring str2;

    str2 = str1;

    std::wcout << str2 << std::endl;
}

実行結果:

abcde

C言語と同様に、ワイド文字やワイド文字列のリテラルには、Lプリフィックスを付加します。

また、ワイド文字列を標準出力へ出力する際には、std::wcout を使います。

なお、std::ostringstream のワイド文字列版は std::wostringstream、std::istringstream のワイド文字列版は std::wistringstream です。

【上級】std::string、std::wstring の関係性と同じです。たとえば、std::ostringstream の正体は、std::basic_ostringstream<char>、std::wostringstream の正体は、std::basic_ostringstream<wchar_t> です(【標準ライブラリ】第29章)。


C++11 (生文字列リテラル)

C++11

文字列リテラルの中で、タブ文字や改行文字のような特殊な文字を使う場合、「\」によってエスケープする必要があります。しかし、これによって非常に読み辛くなってしまうことがありました。たとえば、次の例は、改行文字を入れ込んで、5行分の文字列を表現しています。

const char* s = "abc\ndef\nghi\njkl\nmlo";  // 5行からなるメッセージ

C++11 では、エスケープを行うことなく、見た目どおりに素直に文字列リテラルを表現できる表現方法が追加されています。この機能を使うと、上記の例を次のように書けます。

const char* s = R"(abc
def
ghi
jkl
mno)";

「R“(」で始まり、「)”」で終える構文です。括弧も必要であることに注意してください。この表記方法は、生文字列リテラルと呼ばれます。

生文字列リテラルでは、改行文字に「\n」を使うのではなく、実際にソース上で改行して書きます。同様に、「\t」ではなく実際にタブを入力すればいいですし、「\\」のように重ねずとも「\」で「\」を表現できます。

その代わり、「)"」という2文字が使えなくなってしまいます。これは、文字列リテラルの終端がどこか分からないとコンパイラが困るからです。この問題を解決するため、開始と終端を表す文字の並びを指示できるようになっています。たとえば次のように書きます。

const char* s = R"delimiter(\n)delimiter";

「R“delimiter(」で始まり、「)delimiter”」で終える構文です。delimiter と書いた部分は、16文字以内の任意の文字(ソースコード上で一般的に使える文字に限る)を使えます。

なお、L、u、U のようなプリフィックスを同時に使う場合、これらのプリフィックスを先に置き、続いて R を置きます。反対は許可されません。

const wchar_t* s1 = LR"(\n)";
const wchar_t* s2 = RL"(\n)";  // コンパイルエラー


練習問題

問題① 次のプログラムを、std::string を使って書き換えてください。

#include <cstring>
#include <iostream>

namespace {
    void func(const char* s, std::size_t size)
    {
        if (size > 0) {
            std::cout << s << " " << size << std::endl;
        }
        else {
            std::cout << "empty" << std::endl;
        }
    }
}

int main()
{
    char s1[10] = "abcde";
    char s2[10] = "";

    func(s1, std::strlen(s1));
    func(s2, std::strlen(s2));

    std::strcpy(s2, "abcde");
    func(s2, std::strlen(s2));

    if (std::strcmp(s1, s2) == 0) {
        std::cout << "OK" << std::endl;
    }
}


解答ページはこちら

参考リンク


更新履歴

’2018/7/29 「C++11 (UTF-16、UTF-32 の表現)」の項の内容を削除。同じ内容を解説している Modern C++編のページへのリンクだけを残した。

’2018/7/26 「C++11 (UTF-16、UTF-32 の表現)」の内容を、C言語編での解説内容に合わせて修正。

’2018/7/16 全体的に見直し修正。
C++標準ライブラリの string の利用」の後ろにあった「ワイド文字列」の項を、後ろの方へ移動した。

’2018/7/12 「C++標準ライブラリの string の利用」の項の内容を分割して、内容整理。

’2018/4/5 VisualStudio 2013 の対応終了。

≪さらに古い更新履歴を展開する≫



前の章へ (第3章 名前空間)

次の章へ (第5章 標準入出力ストリームの基礎)

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る