10.ポインタ

10-7. ポインタ文字列と標準文字列の違い

ポインタの初期化によって作られた文字列と配列で作られた文字列の違いを解説します。

◇ポインタ文字列と標準文字列の違い

まずはそれぞれのプログラムから見てみましょう。

main()
{
    char *str ="Cプロダクション";
    printf("%s\n",str);
}

main()
{
    char str[] ="Cプロダクション";
    printf("%s\n",str);
}

これでは、記述以外に何が違うのかがまったくわかりませんね(^^;;

実際にどのような動作をしているのかを説明します。

ポインタの場合:
まずポインタstrを宣言します。
メモリにstrの領域(長さはint)が作られます。
次に”Cプロダクション”をメモリに格納します。
そして、その文字列の先頭アドレスをstrに代入します。

配列による文字列の場合:
配列strを宣言します。
次に”Cプロダクション”をメモリに格納します。
その先頭アドレスを識別子strとして定義します。

・・・といった感じです。

◇注意

 実際はコンパイラの設計によって違うかもしれません。
また、文字列がプログラム領域に存在する状態で使われるのか
データ領域にコピーして使うのかもまちまちなので保証はできかねます。
因みに私はコンパイラの設計をしたことがないのでアセンブリの経験を元に予想して書いています。
別にわからなくてもC言語プログラミング上影響は無いと思うので追求はしないつもりです。

そして、決定的な違いは、

ポインタ型はポインタの分(int1つ)だけ余分にメモリを消費するがポイントする文字列を換えることができる。(文字列中の先頭位置も換えれる)

ということです。これは文字列処理で力を発揮します。

#include<stdio.h>
main()
{
    char *str ="Cプロダクション";
    for(;*str!='\0';str+=2)
    printf("%s\n",str);
}

/* 結果-------------
Cプロダクション
プロダクション
ロダクション
ダクション
クション
ション
ョン
ン

------------------*/

これはprintfの%sの性質を利用したものです。
このようにポインタによる文字列の場合先頭の位置を換えることが可能で上のような処理が簡単に行えます。
配列では結果的に同じ物は記述できますが、内部動作まで同じ物は書けません。

ポインタと配列は似たような操作ができますがポインタが配列を定義できなかったり配列がポインタ特有のアドレス換えができないことから両者は似て非なるものである。

そして配列をポインタとして定義した『ポインタ配列』というものが
存在します。ややこしいー(^^;;;

ポインタは配列では在りませんが、今度は配列をポインタとして定義します。
(言葉で説明するとドンドン深みにハマリそう・・・)

char *str[]={“おはよう”,”こんにちは”,”こんばんは”};

このようなものをポインタ配列といいます。
これと同じようなものを配列で作ると

char str[][11]={“おはよう”,”こんにちは”,”こんばんは”};

となります。
この2つの違いはさっきの例と同じでポインタ配列の方はポインタが配列になっていてそれぞれが各文字列の先頭アドレスを記憶しています。
そしてその分余計にメモリを消費しています。
ただし、メモリへの文字列の格納には次の点が違います。
配列は、一番長い文字列に合わせてメモリを確保するので合計33バイト消費しています。それに比べポインタ配列の方は必要な分だけ確保すればいいので文字列の分の確保量は31バイトで済みます。

これは配列の場合、全体の先頭アドレスを識別子に当てているだけなので列の長さは一定にしないといけないためです。

それに比べポインタ配列は各行のアドレスを保持しているため列の長さは揃える必要が無いのです。

ポインタ配列:
str[0]———>おはよう\0
str[1]———>こんにちは\0
str[2]———>こんばんは\0

2次元文字列:
str[0]———>おはよう\0\0\0
str[1]———>こんにちは\0
str[2]———>こんばんは\0

矢印ははそれぞれ文字列の先頭アドレスを指します。
\0はnull文字です。配列では初期化で指定されない部分はnull文字になります。

2次元文字列のstr[1]やstr[2]は個別にアドレスを記憶しているわけではないので列の長さを一定にしてアクセスできるようにしています。

逆にいえばポインタ配列の方は各行を指すアドレスを換えることが可能なのです。

#include<stdio.h>
main()
{
    int i,buff;
    char *str[] ={"おはよう","こんにちは","こんばんは"};
    for(i=0;i<3;i++)printf("%s\n",str[i]);
    printf("\n");buff=str[0];str[0]=str[1];str[1]=buff;
    for(i=0;i<3;i++)printf("%s\n",str[i]);
}

/* 結果 ———
おはよう
こんにちは
こんばんは

こんにちは
おはよう
こんばんは
—————*/

どうでしょう。「おはよう」と「こんにちは」が入れ替わってしまいました。
直接文字列の書き換えなしで位置の交換ができるのはポインタによる利点なのです。これはソート(並べ替え)処理に力を発揮します。
また応用すれば文字の検索・置換や挿入・削除が効率的に行えるプログラムが
できます。
配列のみで同じ事を実現しようとするとポインタを使用するのに比べ余分な書き換えが生じて効率的ではありません。