stringクラス

http://www.geocities.jp/ky_webid/cpp/library/001.html

文字列を便利且つ簡単に扱うためのクラス。

ただしstringクラスというには少し語弊がある。

stringとは実際には下記のように定義されている。

typedef basic_string<char> string;

なので厳密に言えばbasic_stringテンプレートクラスというべきか。まあ細かいことはいい。

とにかくめっちゃ便利。今までの文字列操作のダルさはなんだったんだというくらい便利。

まず一番大きな違いとしてはこれに尽きるでしょう。

char型の配列を使う場合と異なり、宣言する段階で格納する文字数を指定する必要がありません。素晴らしいことに、string は内部で自動的にメモリを確保してくれます。そのため、どれくらいの文字数が必要になるか分からない場合でも効率よく使用できます。

これは便利だ。

正直文字列操作で一番面倒臭い部分なので、これを自動でやってくれるのはかなり魅力的ですね。使わない手はないです。

とりあえず今回はstringクラスの基本的な機能について勉強していきます。


文字列の初期化


まずはstringの初期化からいきます。

int main () {
    string str1; // 引数無しは空文字で初期化される
    string str2("aaaa");
    string str3 = "bbbb";
    string* str4 = new string("cccc");
    
    cout << str1  << endl;
    cout << str2  << endl;
    cout << str3  << endl;
    cout << *str4 << endl;
    
    delete str4;
    return 0;
}
$ main

aaaa
bbbb
cccc

となります。

ちなみにstring型は本来std::string型なのでusingしてない時には注意しましょう。


文字列のコピー


代入演算子で実現できます。

int main () {
    string str1("abc");
    string str2 = "foobarbaz";
    
    str1 = str2; // str2の内容をstr1へコピー
    
    // 同じ値が表示される
    cout << str1 << endl;
    cout << str2 << endl;
    
    return 0;
}
$ main
foobarbaz
foobarbaz

注目すべき点は4文字分のデータしか持ってないstr1に対して、10文字分のデータを持つstr2を代入しても良きに計らってくれているところですね。

これをC言語でやろうと思ったら、そもそもchar*型で宣言し、realloc等でstr1の領域を拡張し、strcpyしないといけないところなんですが、代入するだけでもろもろの作業をやってくれるというのは素晴らしいの一言に尽きますね。

ちなみに上記処理は完全にコピーされてるのでstr1とstr2で保持している値は別々のものとなります。

一応確認してみます。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str1("abc");
    string str2 = "foobarbaz";
    
    str1 = str2; // str2の内容をstr1へコピー
    
    str2 = "hoge"; // str2を別の内容に変更
    
    cout << str1 << endl;
    cout << str2 << endl;
    
    return 0;
}
$ main
foobarbaz
hoge

ちゃんと別々になっていますね。


文字列の連結


+演算子や+=演算子で文字列の連結を行えます。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str1("foo");
    string str2("bar");
    string str3("baz");
    
    str1 += str2;
    cout << str1 << endl;
    
    str1 = str2 + str3;
    cout << str1 << endl;
    
    return 0;
}
$ main
foobar
barbaz

うまくできてますね。

これもC言語でやろうと思ったらメモリ拡張、strcat等を使うことになりますね。


文字列の比較


比較は物凄い直感的です。普通の比較と同様、==演算子を使うだけです。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str1("foo");
    string str2("bar");
    
    if ( str1 == "foo" ) {
        cout << "str1は一致" << endl;
    }
    else {
        cout << "str1は不一致" << endl;
    }
    
    if ( str2 == "foo" ) {
        cout << "str2は一致" << endl;
    }
    else {
        cout << "str2は不一致" << endl;
    }
    
    return 0;
}
$ main
str1は一致
str2は不一致

C言語ではstrcmpを使ってましたよね。

また、大小の比較も>や<演算子で可能です。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str1("foo");
    string str2("bar");
    
    if ( str1 > str2 ) {
        cout << "str1の方がでかいぜ" << endl;
    }
    else {
        cout << "str2の方がでかいぜ" << endl;
    }
    return 0;
}
$ main
str1の方がでかいぜ

ばっちりですね。


文字列の長さ


sizeメンバ関数が提供されています。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str("hogehoge");
    
    cout << str.size() << endl;
    
    return 0;
}
$ main
8

C言語でいうところのstrlenですね。

ちなみに空文字かどうかを判定する場合はsizeメンバ関数を使うよりもemptyメンバ関数を使う方が効率が良いらしいです。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str;
    
    if ( str.empty() ) {
        cout << "空文字だ!" << endl;
    }
    
    return 0;
}

charポインタ型で取り出す


stringクラスが保持している文字列をchar*型で取り出すこともできます。これはchar*型を要求するような関数等を利用したい時に使えますね。

#include <string>
#include <iostream>
using namespace std;

void char_print(const char* str) {
    cout << str << endl;
}

int main () {
    string str("foo");
    
    char_print(str.c_str());
    
    return 0;
}
$ main
foo

ちなみにc_strメンバ関数の戻り値はconst付きになっています。内部で保持している文字列が書き換えられてしまうと問題になるからでしょうね。


一文字単位での要素へのアクセス


stringクラスの変数は[]演算子オーバーロードをしているため、まるで配列のようにアクセスすることが可能です。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str("abcdedf");
    
    str[1] = 'x'; // bをxに変更
    
    cout << str << endl;
    
    return 0;
}
$ main
axcdedf

ただし注意が必要です。もし範囲外のデータにアクセスしてしまったら、普通の配列と同様にどこかのメモリを破壊してしまう恐れがあります。

一応、atメンバ関数というのが用意されているのでそっちを使って要素にアクセスする方が良いのかもしれません。

atメンバ関数経由で範囲外のデータを変更した場合、out_of_rangeという例外クラスを吐いてくれます。

#include <string>
#include <iostream>
using namespace std;

int main () {
    string str("abcdedf");
    
    try {
        str.at(100) = 'a';
    }
    catch( out_of_range e ) {
        cout << "範囲外のデータにアクセスしました" << endl;
    };
    
    return 0;
}
$ main
範囲外のデータにアクセスしました

さて、ざっとこんなもんでしょうか。

他にもまだまだ色々な便利メンバ関数とかあるのですが、それはまた別記事で用途ごとに追々勉強していきたいと思います。

兎に角、このstringクラスは本当に便利だということがわかりました。

これからはじゃんじゃん使っていきたいと思います。