2017-12-28

Cプログラマのための、C++簡単裏道アプローチ(3)

とても便利なC++の文字列型

Cで制御系の処理を書く場合、確かに文字列そのものをほとんど扱わないことも珍しくありません。しかし、そういった場合でもログ出力だったり、デバッグのための情報記録まで含めて考えると、ほとんどの場合で文字列を扱わないということはむしろ珍しいケースではないでしょうか。

ところで、C言語には文字列が文法上存在していない、という言い方がされてきました。確かに、char * だったり、const char * だったりと、文字の配列として扱います。しかしながら、リテラル表現はダブルクォートを使用し、その配列の最後はなからず0で埋められるという規則があります。これは文法上の規則です。
このことを考えると、中途半端かも知れませんが、やはりC言語にも文字列型は存在している、と言っていいのかも知れません。
(昔々、「"abc\0"」と、わざわざ0を追加している初心者のコードを見たことがありましたが、きちんと文法書を読んでください、と言いたくなりました。初心者と言っても学生の演習ではなく、お仕事のプログラムです。もちろん、0を2つ重ねる目的でもありません。)

この文字列リテラルはC++においても相変わらず有効で、これ自体はよく登場します。
しかし、C++には標準ライブラリのSTLに文字列クラス(文字列型)が用意されていて、大変便利に、しかも安全に文字列処理を行うことが可能になっています。

さて、こういった文字列型になれていると、ちょっとした文字列操作も大変面倒に感じてしまいます。たとえば、文字列の結合。

コード1

// Cの場合
void funcC1()
{
    char s1[] = "abc";
    char s2[] = "DEF";

    char * s3 = (char *)malloc( strlen(s1) + strlen(s2) + 1 );
    strcpy( s3, s1 );
    strcat( s3, s2 );

    puts(s3);   // "abcDEF";

    free(s3);
}

// C++の場合
void funcCPP1()
{
    string s1 = "abc";
    string s2 = "DEF";
    string s3 = s1 + s2;
    puts(s3.c_str());   // "abcDEF";
}
// あるいは
void funcCPP1_2()
{
    string s1 = "abc";
    string s2 = "DEF";
    s1 += s2;
    puts(s1.c_str());   // "abcDEF";
}

stringというのはCでいうところの構造体みたいなもの(クラス)ですが、C++では演算子のオーバーライドという機能(演算子の動作を再定義する機能)があるので、あたかも言語組込みの型のように演算子を使うことができます。そして、Cでやっているようなメモリ確保等の面倒も、全部string型の内部に定義された関数で処理されますから、使うときはそういったことは考えなくてもいいのです。

ところで、このコードの中で s1.c_str() という関数の呼出があります。C++の場合、Cの構造体に相当するもの(クラス)に変数だけではなく、関数を定義することができます。メンバ関数とか、メソッド、といいますが、Cの関数と似たようなものだと考えてください。
構造体のメンバにアクセスするとき、「.」(ドット)で構造体の実体名(変数名)とメンバ変数を繋ぎますが、メンバ関数も同様で、s1.c_str() と書いて呼出します。
で、この s1.c_str() は s1 の内部に持っている文字列を const char * に変換して返すので、puts()のパラメータに使えるわけですね。

文字列型が存在することだけでも、プログラミングはずいぶんと違ったものになります。ここでご紹介したのは氷山の一角に過ぎません。C++の新しい仕様では正規表現の処理が標準関数で扱えるようになりました。これは特に文字列の解析処理においてはかなり威力を発揮します。
C言語では似たような発明を毎回しなければならず、問題の本質になかなか迫ることができないもどかしさを感じてしまいます。

いえ、ここまでの記事だけでは、とうていそれを読者に実感してもらうことはできないとは思いますが。

簡単ですが、今回はここまでにします。


コラム:動的配列確保


コード1のfuncC1()ではわざわざmalloc()を使っていましたが、動的な配列の確保を使えば不要になります。ただし、どうやらこの動的な配列の確保についてはCの文法的に将来性がないかも知れません。また、C++では使えないことになっています(C++では他の方法があるのでそもそも不要と言えます)。

コード2

void funcC2()
{
    char s1[] = "abc";
    char s2[] = "DEF";

    // 動的配列確保、自動変数なので開放不要
    char s3[strlen(s1) + strlen(s2) + 1];
    strcpy( s3, s1 );
    strcat( s3, s2 );

    puts(s3);   // "abcDEF";
}
ご参考まで。




0 件のコメント:

コメントを投稿