C++の文字列1

stringクラス

C言語で文字列を扱うにはchar型配列を利用していました。
C++では文字列をより便利、かつ安全に扱える方法が提供されています。


#include <iostream>
#include <string>

int main()
{
    char str1[] = "ABC";      //C言語的な文字列
    std::string str2 = "DEF"; //stringクラスによる文字列

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

    std::cin.get();
}

「std::string」というのが文字列を扱うクラスです。
クラスについては今はcoutなどと同様の「何か便利な機能」という認識で構いません。
(stringは厳密に言えばbasic_stringクラスをtypedefしたものです)

stringクラスを使用するにはコード先頭に#include <string>を記述します。
「<string.h>」ではないので注意してください。
string.hはC言語の文字列操作系関数を使用するためのもので、別物です。

stringクラスはchar型の時のように配列を作る必要はなく、そのまま宣言しただけで文字列を扱えるようになります。
char型の配列による文字列よりも直感的で、とても手軽に扱えます。

初期化と代入


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABCDE";
    
    //空の文字列を宣言
    std::string str2;
    std::cout << str2 << std::endl;

    //str2にstr1をコピー
    str2 = str1;
    std::cout << str2 << std::endl;

    //str2に長めの文字列をコピー
    str2 = "abcdefghijklmn";
    std::cout << str2 << std::endl;

    std::cin.get();
}

stringクラスの変数は初期化せずに宣言しても問題ありません。
その場合、空の文字列で初期化されますので、そのまま出力したとしてもエラーにはなりません。

string文字列同士は「=」で代入するだけで文字列がコピーできます。
C言語のstrcpyなどの関数を使用する必要はありません。

文字列リテラルの代入も簡単です。
現在保持している文字数以上の文字列を代入しても問題ありません。
stringクラスは、文字列配列のように配列のサイズを気にする必要はなく、自動的に必要なサイズをメモリ上に確保してくれます。
そのため、バッファオーバーランは起こりません。

C言語ではちょっとクセのあった文字列ですが、C++ではint型などと同じくらいに手軽に扱えます。

以下の関数等の説明では同じ関数名で複数の使い方ができるものが多数登場します。
全てを解説していては大変なので、比較的使いそうなものだけをピックアップしています。

初期化

初期化には以下の方法を用いることもできます。


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABCDEFG";

    //str1で初期化
    //ABCDEFG
    std::string str2(str1);
    std::cout << str2 << std::endl;

    //str1の先頭から2文字以降で初期化
    //CDEFG
    std::string str3(str1, 2);
    std::cout << str3 << std::endl;

    //str1の2文字目から3文字で初期化
    //CDE
    std::string str4(str1, 2, 3);
    std::cout << str4 << std::endl;

    //str1の2文字目から4文字目手前までで初期化(範囲指定)
    //CD
    std::string str5(&str1[2], &str1[4]);
    std::cout << str5 << std::endl;

    //5個のxで初期化
    //xxxxx
    std::string str6(5, 'x');
    std::cout << str6 << std::endl;

    std::cin.get();
}

丸括弧を使用するのはクラスの機能のひとつであるコンストラクタというものを呼び出しています。
これはクラスの項で詳しく説明しますので、こういう方法もあるということを知っておくだけで構いません。

なお、先頭文字は0文字目とカウントされることに注意してください。
これはchar型配列のときと同じです。

assign関数

代入にはassign関数を使用する方法もあります。


#include <iostream>
#include <string>

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

    //str1を代入
    //ABCDEFG
    str2.assign(str1);
    std::cout << str2 << std::endl;

    //str1の先頭から2文字以降を代入
    //CDEFG
    str2.assign(str1, 2);
    std::cout << str2 << std::endl;

    //str1の2文字目から3文字代入
    //CDE
    str2.assign(str1, 2, 3);
    std::cout << str2 << std::endl;

    //str1の2文字目から4文字目手前までを代入(範囲指定)
    //CD
    str2.assign(&str1[2], &str1[4]);
    std::cout << str2 << std::endl;

    //5個のxを代入
    //xxxxx
    str2.assign(5, 'x');
    std::cout << str2 << std::endl;

    std::cin.get();
}

できることは初期化の時とほぼ同じです。

n文字目にアクセス

char型配列のように、角括弧[]で文字列の特定の文字にアクセスできます。


#include <iostream>
#include <string>

int main()
{
    std::string str = "ABCDE";

    //「C」を表示
    std::cout << str[2] << std::endl;

    str[2] = 'x';
    //「ABxDE」を表示
    std::cout << str << std::endl;

    //不定、クラッシュするかもしれない
    std::cout << str[10] << std::endl;

    std::cin.get();
}

値の取り出しと値の書き換えができるのもchar型配列と同じです。

範囲外の要素にアクセスできないのも同じです。
ただし、コンパイルエラーにはならず、プログラムが落ちない可能性もありますが、メモリ上の予期しない位置の読み書きが行われることに違いはありませんから、絶対に避けるべきです。
(char型配列でも同じです)

at関数

もう一つ、at関数を使用する方法もあります。


#include <iostream>
#include <string>

int main()
{
    std::string str = "ABCDE";

    //不定、クラッシュするかもしれない
    std::cout << str[10] << std::endl;

    //例外を発生させる
    std::cout << str.at(10) << std::endl;

    std::cin.get();
}

角括弧[]を使った場合と基本的に同じですが、範囲外の要素にアクセスしようとしたときの挙動が異なります。
角括弧の場合の動作は不定(どうなるかわからない)ですが、at関数は例外を発生させます。

例外についてはまだ説明していませんが、例外はクラッシュとは違い、きちんと処理する方法があるのでat関数の方が安全です。
(ただし若干速度が劣るそうです)

文字列の結合


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABC";
    std::string str2 = "DEF";

    std::string str3 = str1 + str2;
    str3 += "GHI";

    //ABCDEFGHI
    std::cout << str3 << std::endl;

    std::cin.get();
}

文字列の結合は結合したいstring同士を「+」で結ぶだけです。
文字列リテラルともそのまま結合できます。

append関数

文字列の結合にはappend関数を使用することもできます。
初期化やassign関数の時とほぼ同じです。


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABCDEFG";
    std::string str2 = "abc";

    //str1を結合
    //abcABCDEFG
    str2.append(str1);
    std::cout << str2 << std::endl;

    str2 = "abc";
    //str1の先頭から2文字以降を結合
    //abcCDEFG
    str2.append(str1, 2);
    std::cout << str2 << std::endl;

    str2 = "abc";
    //str1の2文字目から3文字結合
    //abcCDE
    str2.append(str1, 2, 3);
    std::cout << str2 << std::endl;

    str2 = "abc";
    //str1の2文字目から4文字目手前までを結合(範囲指定)
    //abcCD
    str2.append(&str1[2], &str1[4]);
    std::cout << str2 << std::endl;

    str2 = "abc";
    //5個のxを結合
    //abcxxxxx
    str2.append(5, 'x');
    std::cout << str2 << std::endl;

    std::cin.get();
}

文字列の長さの取得


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABC";
    std::string str2 = "あいう";

    int len1 = str1.length();
    int len2 = str2.size();

    //3
    std::cout << len1 << std::endl;

    //6
    std::cout << len2 << std::endl;

    std::cin.get();
}

文字列の長さはlength()またはsize()で取得できます。
これらはバイト数を返し、両者に違いはないようです。

また、stringクラスの文字列は末尾にNULL文字がありません。
(Visual C++の場合。これはコンパイラによるらしいです)
そのため、NULL文字を文字列の終了と判定するコードを組むと期待通りの動作にならないかもしれません。

文字列の比較


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABCDEFG";
    std::string str2 = "ABCDEFG";
    std::string str3 = "aBCDEFG";

    if(str1 == str2)
        std::cout << "str1とstr2は同じ" << std::endl;
    else
        std::cout << "str1とstr2は違う" << std::endl;

    if (str1 == str3)
        std::cout << "str1とstr3は同じ" << std::endl;
    else
        std::cout << "str1とstr3は違う" << std::endl;

    if (str1 == "ABCDEFG")
        std::cout << "str1と'ABCDEFG'は同じ" << std::endl;
    else
        std::cout << "str1と'ABCDEFG'は違う" << std::endl;

    std::cin.get();
}

文字列の比較は「==」や「!=」で行えます。

「<」や「<=」などの不等号による比較も可能です。
これは文字コードの順番(AとBならAの方が小さい)となります。
名前を昇順で並び替えなどの時に使用します。

compare関数

文字列の比較はcompare関数を使用することもできます。


#include <iostream>
#include <string>

int main()
{
    std::string str1 = "ABCDEFG";
    std::string str2 = "ABCDEFG";
    std::string str3 = "aBCDEFG";

    std::cout << str1.compare(str2) << std::endl;
    std::cout << str1.compare(str3) << std::endl;
    std::cout << str1.compare("ABCDEFG") << std::endl;

    std::cin.get();
}

文字列同士が同じならば「0」を返します。
str1の方が小さければ0未満を、str1の方が大きければ1以上を返します。

空文字列かどうかを調べる

文字列が空か否かを調べるにはempty関数を使用します。


#include <iostream>
#include <string>

int main()
{
    std::string str1;
    std::string str2 = "";
    std::string str3 = "ABC";

    //1
    //1
    //0
    std::cout << str1.empty() << std::endl;
    std::cout << str2.empty() << std::endl;
    std::cout << str3.empty() << std::endl;

    std::cin.get();
}

文字列が空でない場合は0を返しますので、if文などの条件判定にそのまま記述することができます。

その他、文字列の長さを取得して0か否かで判定するという方法もあります。
ただ、専用の方法が用意されている場合は素直にそれを使用した方が効率面で優れている場合が多いです。


stringクラスにはまだ機能はありますが、ページが長くなりすぎるので分割します。