12.5 ポインタの演算

ポインタにも演算が可能である。そこで重要なのが、ポインタが指しているデータ型 の情報をポインタが持っているという事である。例えば、文字型と整数型であれば、 変数の実際にメモリ中に占める大きさが違っているので、文字型変数が2個連続して あった場合の 2 個目のアドレスと、整数型変数が 2 個連続してあった場合の2個目の アドレスは違う。つまり、変数が連続してメモリ中にある場合に、 単純にアドレスが 1 増えた場所に常に次の目的の変数があるとは限らないのである。 従って、どの型の変数を相手にしているかで、アドレスの演算方法は異なってくる。 そのために、C 言語では、ポインタにその指しているデータ型の情報を持たせている のである。

例 7
        char *s = "this";
        char *p;
        for(p=s; *p!='\0'; p++){
             putchar(*p);
        }

例 7 では、文字型ポインタ s は文字列 "this" を指している。p=s としているので、最初は p も文字列 "this" を指している。ここで、先に 説明したように実際には p は、文字 't' を指している点に注意しよう。 さて、先に説明したように、 文字列の終わりには '\0' を書き込んで文字列の終わりを表す 決まりになっているので、for 文の意味は、*p が文字列の終わりに来ない限り継続されるという事になる。 次に、putchar*p 、すなわち 't' が出力され、p++ で ポインタ p がインクリメントされている。この時、p は単純に 1 増える のではなく、文字型変数の大きさ 1 個分増えるのである。そのために、p は 'h' を指すことになる。このようにして、't', 'h', 'i', 's' が順次出力され、 そこで文字列の終わりに達するので、その時 p'\0' を指すので、先の for 文の条件によりループが終了するのである。以上の状況を図に表した のが下図である。

\begin{figure}\begin{center}
\epsfile{file=string2}
\end{center}\end{figure}

重要
ポインタ変数に対する整数演算は、そのポインタ変数の持つ 型のデータ個数分のアドレスを変更することを意味する。

例 8
        char *s = "This is a pen.";
        char *p;
        p = s;
        for(p=s; *p!='\0'; p++){
            if(*p==' '){
                 *p = ':';
            }
        }
        printf("%s\n", s);

上の例 8 では、"This is a pen." という文字列の一文字づつをポインタ p で 見ているが、もしその文字が ' ' だったら、そのアドレスに ':' を書き込んでいる。 即ち、' ' 文字を全て ':' で置き換えている事になるので、最後に出力される文字列 は "This:is:a:pen." になる。

参考 1.
gcc などの新しいコンパイラで上のプログラムをコンパイルすると メモリ領域の破壊で core dump する場合がある。これは、gcc などでは、プログラム 中の文字列を変更できないものと考えているからである。こうした場合には、 -fwritable-strings または -traditional オプションをコンパイル時に加えれば良い。 (gcc -traditional test.c ) Sun の古いコンパイラなどではそのまま実行 出来る。

参考 2.
-traditional について

このオプションは gcc (Linuxでは標準がgcc)では利用出来ない場合がある。 その場合、これをコンパイル するためには、先で習う内容になるが、

     char *s = "This is a pen.";
     char s[] = "This is a pen.";
と変更すれば良い。これで、コンパイルオプションはなしで、コンパイル・実行 が出来るようになる。これの意味については、配列とポインタの所で学習する 予定である。

例 9
        char *s = "This is a pen.";
        char *p;
        for(p=s; *p!='\0'; p++){
             if(*p >= 'a' && *p <= 'z'){
                   *p = *p - 'a' + 'A';
             }
        }
        printf("%s\n", s);

この例 9 は少し難しい。まず、for 文の中の if 文では、ポインタ p の指す文字が 'a' 以上、'z' 以下である場合を判定しているが、英小文字は 連続した数値を持つので、結局英小文字の判定をしていることになる。その時、 if の中の計算式は、*p が 'a' ならば *p - 'a' + 'A' は 'A' となり、*p が 'b' ならば *p - 'a' + 'A' は 'A'+1 を意味する ので 'B' となる。同様に、*p が 'z' ならば *p - 'a' + 'A' は 'A'+25 を意味するので 'Z' となる。つまり、この部分の式によって小文字を大文字に 置き換えている。従って、最終的に "THIS IS A PEN." が出力されるのである。

 

例 7 から例 9 は全てインクリメントを用いて、直接ポインタ変数の値を変更してい た。勿論、このようにポインタ変数の値を変更することでメモリ中のデータを次々に 参照できるのは C 言語の非常に便利な点であるが、別にポインタ変数の値を変更しなく ても別のアドレスのデータを参照することが出来る。

例 10
        char *s = "this";
        putchar( *s );
        putchar( *(s+1) );
        putchar( *(s+2) );
        putchar( *(s+3) );

例 10 でも、今迄説明したように (p+1) はアドレスを 1 足すという意味では なく、文字型変数一個分アドレスを足すという意味なので、*(p+1) は 'h' を意味し、以下順次 *(p+2) は 'i'、*(p+3) は 's' を意味する。 これは下図のように考えれば良い。

\begin{figure}\begin{center}
\epsfile{file=string3}
\end{center}\end{figure}

これを、もう少しスマートに書いたのが例 11 である。

例11
        char *s = "this";
        int n;
        for(n=0; *(s+n)!='\0'; n++){
             putchar( *(s+n) );
        }

とは言え、例 7 のように直接ポインタ変数を書き換えるのに比していささか面倒である のは否めない。



最初のページ 戻る 次へ 最後のページ 目次
Hiroyasu Asami