金曜日, 1月 25, 2008

C++解説6(string型)

今回は一旦文法的な話から離れてC++は便利だって事に説明を割きます。
最初にタイトルになっているstring型とはありません。
stringとはクラスの名前です。
つまり、クラス宣言をしているようなものです。
しかし、ここではstring型と表現します。

文字列を表現する上でC言語というのは非常にダメ言語でした。
何を行うにも、演算子が使えず
strcmp、strcpy、strcat
など様々な関数を使用して処理の実現を行ったきました。

これはC++においても同じで結局プログラミング言語というのは
文字列を扱うのが難しいのでしょう。

それでもいろいろなプログラミング言語は様々な工夫を行って
文字列の扱いを簡単にする努力を行ってきたようです。
C++ではstringがそれにあたります。

stringとはクラスです。
クラスとは一言でいうと型です。

int i;
という文があれば、これは「i」という変数名で「int」型を宣言した事になります。
クラスとは型ですから、
string str;
という文があれば、「str」という変数名で「string」型を宣言したという事です。
準備はこれだけで終わりです。

それではC言語で苦労、又は著しく可読性を下げた
プログラムの簡素化のスタートです。


1.1.文字列比較
char str1[4] = "abc";
char str2[4] = "abc";

if (strcmp(str1, str2) == 0) {
}

C言語では上記のように記述しますが、stringを使用すると以下のようになります。

string str1("abc");
string str2("abc");

if (str1 == str2) {
}

これはどう考えても下の方が良いでしょう。
数値を比較しているかのようです。
どうしてこのような比較ができるのかは
クラス、演算子オーバーロードの2つを理解すれば分かります。
ここでは、上記比較が行えるという事が重要であり
char[]ではなくstringを使用した方が良いという点です。


1.2.文字列複写
文字列比較が「==」という演算子を使用して記述できると
いろいろと試して見たくなる事があるでしょう。
そして、そのほとんどが期待通りに動作すると思います。
string str1("abc");
string str2("def");
char *str3 = "ghi"
char str4[4] = "jkl";

str1 = str2;
str1 = str3;
str1 = str4;
この全てが成立します。
数値型では代入といい、文字列だけ複写という表現を使用すると思います。
同じ事を行うのですから、代入という表現で何も問題がなかったにも関わらず
そこに抵抗があったのは、記述方法に違いがあったからでしょうね。


1.3.文字列連結
連結も何の事はありません。
string str1("abc");
string str2("def");
char *str3 = "ghi"
char str4[4] = "jkl";

str1 += str2;
str1 += str3;
str1 += str4;
特に説明は不要だと思います。


1.4.要素へのアクセス
要素へのアクセスも同じ事です。
char str1[4] = "abc";
string str2("abc");

if (str1[0] == str2[0]) {
cout << "agree" << endl;
} else {
cout << "disagree" << endl;
}

は成立します。
本当に簡単だと思いませんか?
「strxxx」なんて関数は一切使用しなくなってしまいます。
しかも可読性が非常に高い。
C++はここへの拘りが非常に強いと思います。


1.5.検索
文字列からある特定の文字を探したい場合は
C言語ではどのようにするか?

ポインタを取得したい場合は
char *p
char str[] = "abcde";
char c = 'c';
p = strchr(str, c);

要素位置を確認したい場合は
int i;
char str[] = "abcde";
char c = 'c';
for(i = 0; i < strlen(str); ++i) {
if (str[i] == c) {
break;
}
}
とか書くのでしょうね。
下の例はポインタを知りたい場合でも有効ですが、
上の例と比べると見た目が今一です。

これが文字列検索に変わると
「strstr」という関数を使用するようになり
下の例では「strcmp」などを使用して書くことになります。

当たり前の事で何も思わないかもしれませんが
今一な事は常に頭に置いておく事で新しい事への発見ができます。
はっきり言って今一です。
結局、行っているのは「検索」です。
それを何で、何を検索するかで使い分けなくてはいけないのか?
頂けませんね。

上と同じ事を実現する場合のC++の例です。
string str("abcde");
char str1[] = "cd";
char c = 'c';
long l;

l = str.find(c, 0);
l = str.find(str1, 0);
見ただけで何を行っているのかピンと来る事でしょう。
findしているのです。
また、findにはいくつか種類があります。

find_first_of : 前方から検索し、最初に検出された箇所の位置を返します。
find_first_not_of : 前方から検索し、最初に違った箇所の位置を返します。
find_last_of : 後方から検索し、最初に検出された箇所の位置を返します。
find_last_not_of : 後方から検索し、最初に違った箇所の位置を返します。

また、探し方も文字でも文字列でも可能で本当に便利になりました。
細かく理解をしょうとするとここでも覚える事は沢山あります。
でも難しい事は後にして、便利になった箇所を沢山使っていきましょう。
最後に理屈を知っていれば十分です。


1.6.挿入、削除
文字列操作において、良く使用する機能って何なのか?
人によるのでしょうが難しい問題です。
文字列を操作する事よりは文字列を一つのデータとして扱う場合に
非常に扱いづらかっただけで
ここから記述する事に関しては、C言語だけが抱える問題ではない気がします。
様々な言語は容易にする仕組みを考えています。

挿入とは文字列のある位置に別の場所に保存している
文字、文字列を入れる事を指します。
C言語であれば、以下のようなプログラムになるかと思います。
char str1[10] = "abcf";
char str2[] = "de";
char *strInsPos;

strInsPos = strchr(str1, 'c');
strInsPos++;
memmove(strInsPos + strlen(str2), strInsPos, strlen(strInsPos));
memcpy(strInsPos, str2, strlen(str2));
こんな感じでしょうか?
さらに安全に記述すると
if ((strlen(str1) + strlen(str2)) < 10) {
memmove(strInsPos + strlen(str2), strInsPos, strlen(strInsPos));
memcpy(strInsPos, str2, strlen(str2));
} else {
/* 拡張サイズ不足 */
}
要するにメモリ範囲外へのアクセスを未然防止ってロジックが必要になります。


C++のstringを使用するとになると、以下の通りで済みます。
string cppstr("abcf");
char cppstr2[] = "de";
long l;

l = cppstr.find('c', 0);
cppstr.insert(l + 1, cppstr2);
メモリに関するいろいろな処理が無くなっています。
そんな事に苦労する必要がないって事ですね。

反対に削除の場合はどうするかと言うと
string cppstr("abcf");
long l;

l = cppstr.find('c', 0);
cppstr.erase(l + 1);
これは'f'が削除される例です。

この辺りになってくると頻繁に使用するかどうかは微妙なところもありますが
使う場合はこんなにも違うって事ですね。


1.7.文字列トークン分割
私の経験では、それなりに使用する場面がありましたが
一般的にはどうなのか分りません。
csvファイルを解析する際に、必要となる機能です。
C言語ではこれを「strtok」関数で実現していました。
char str[] = "abc,def,hij,klm";
char *tmp;

tmp = strtok(str, ",");
while(tmp != NULL) {
printf("%s\n", tmp);
tmp = strtok(NULL, ",");
}
C++では...
実はこいつが非常にイケてません。
はっきり言って相当するものはありません。
C言語を知っている人であれば、strtokを使用して下さい。
もちろん、これを実現するクラスを作成すれば良いのですが
ここでは見送りです。


と言う事でstringについてはお終いです。
char*より優れている事は分って頂けましたでしょうか?
分かって頂ければ光栄です。

1 件のコメント:

Unknown さんのコメント...

初めまして,通りすがりの者です

トークン分割ですが,sstreamのgetline(char*,const int,char)を使えば簡単にできますよ.