第40章 さらなる計算

 前回使ったアドレスの演算を、今回は実用してみます。第35章で言っていた * と [ ] との関係についても、もう少し迫ってみたいと思います。


 では、今回の要点です。


 では、いってみましょう。


 今回は、文字列を扱う関数を作って、ポインタ、アドレスの演算をしてみます。今回作る関数は strlen です。文字列の長さを取得する関数でしたね。string.h をインクルードせず、自分でこの関数を作ってみたいと思います。

 先ずは、普通に関数を作ってみます。

プログラム
// Strlen1.cpp
#include <stdio.h>

int strlen(char* str)
{
    int i;
    for(i = 0; str[i]; i++);
    return i;
}

void DispLength(char* str)
{
    printf("文字列「%s」の長さは %d バイトです。\n", str, strlen(str));
}

int main()
{
    DispLength("ホメホメール");
    DispLength("嘘つきゃばれるぜドルバッキー");
    DispLength("");

    return 0;
}
実行結果例
文字列「ホメホメール」の長さは 12 バイトです。
文字列「嘘つきゃばれるぜドルバッキー」の長さは 28 バイトです。
文字列「」の長さは 0 バイトです。

 for 文で、str[i] が0かどうかを確認します。この確認は for 文の中で済ませてしまいます。で、str[i] が0になったときに i の値を返します。この時の i の値が丁度文字列の長さになっています0,1文字程度の文字列を考えてみて、確認してみて下さい。理屈で考えるより、ごく簡単な例で確認した方が早いです。暇なら理屈も考えて下さい。

 この for 文に実行文は必要ないので、空文を書いておきました。空文はセミコロンだけの文です。

 for 文の条件判定は、初期化文(上の例では i = 0 がそう)を行った直後にも判定されるので、空の文字列(ヌルターミネータはついています)を渡してもきちんと判定できます。

 以上が普通の方法です。


 次に、ポインタの演算を使ってみましょう。

プログラム
// Strlen2.cpp
#include <stdio.h>

int strlen(char* str)
{
    char* p;
    for(p = str; *p; p++);
    return p - str;
}

void DispLength(char* str)
{
    printf("文字列「%s」の長さは %d バイトです。\n", str, strlen(str));
}

int main()
{
    DispLength("ほめ殺し");
    DispLength("嘘つきは泥棒のはじまり");
    DispLength("");

    return 0;
}
実行結果例
文字列「ほめ殺し」の長さは 8 バイトです。
文字列「嘘つきは泥棒のはじまり」の長さは 22 バイトです。
文字列「」の長さは 0 バイトです。

 ポインタの演算として、インクリメント引き算を使っています。

 先ず、p に str を入れます。そして p を1つずつ進ませていって、*p が0になったところで for 文を終わります。そして文字列の長さは、その時の位置と初めの位置との差になります。これも0,1文字の文字列を考えてみて、確認してみて下さい。

 これを見て分かりますが、ポインタの演算をこういう風に使うと見にくいです。何やってるのか分かりにくいです。ポインタの演算の例としては不適当かもしれませんね。まぁ、今回は見逃して下さい。

 昔は「配列でインデックスを指定するよりも、ポインタの演算をした方が早い」だとか言われていましたが、今はコンパイラが優秀なので差は出ないと思います


 では、最後はちょっと変わった形にします。* 演算子を使います。上のも使っていましたが、ちょっと違います。

プログラム
// Strlen3.cpp
#include <stdio.h>

int strlen(char* str)
{
    int i;
    for(i = 0; *(str + i); i++);
    return i;
}

void DispLength(char* str)
{
    printf("文字列「%s」の長さは %d バイトです。\n", str, strlen(str));
}

int main()
{
    DispLength("ベタぼめ");
    DispLength("ウソ エイト・オー・オー");
    DispLength("");

    return 0;
}
実行結果例
文字列「ベタぼめ」の長さは 8 バイトです。
文字列「ウソ エイト・オー・オー」の長さは 23 バイトです。
文字列「」の長さは 0 バイトです。

 上の赤くなっているところが問題の部分です。

*(str + i)

 これはどういう結果になるでしょうか? 順を追って考えてみたいと思います。

 先ず、str + i がカッコでくくられているので、この計算が行われます。アドレスの演算では i に参照先の型のサイズを掛けた値を str に足すのでしたね。str の参照先の型は char です。char のサイズは1バイトなので、ただ単に i と同じ値と str とを足すことになります。

 では、次です。今度はその計算結果に * がついています。つまり、str に i を足したアドレスにある値を取り出しますね。str に i を足したアドレスにある値とは何でしょうか? そうです。ズバリ str[i] です。

 つまりは、*(str + i) というのは str[i] の事なのです。

 このことから分かるとおり、*(<アドレス> + <インデックス>) と <アドレス>[<インデックス>] という2つの表現は全く同じなのです。どっちで書こうが、結果は全く同じです。つまり、* と [ ] は等価な演算子だということです。便利な方を使ってね、ということです。

 ただし、当然ですが変数の宣言においてはこれらの演算子は意味が違います。ポインタと配列の違いがありますね。ただ、第35章で書いたとおり、引数の宣言に使うときはやっぱり同じになります。

 あと、極端なことを言えば、別に (str + i)[0] や (str + i - 5)[5] も str[i] や *(str + i) と同じです。落ち着いて考えれば何の不思議もないことですね。ただ、こういう書き方はまずしないでしょうが。(^ ^;


 今回はこれで終わりです。

 では、今回の要点をもう一度見てみましょう。


 次回はバグを減らす強い味方 const について話します。ポインタを扱う上で無くてはならないものです。しっかり身につけましょう。

 では、次回まで。


第39章 1+1=5?? | 第41章 変えてくれるな

Last update was done on 2002.2.28

この講座の著作権はロベールが保有しています