C 文字列配列

提供:yonewiki
2022年9月26日 (月) 10:49時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++へ戻る ※このページではC言語にも存在していたという意味で記事タイトルがC 文字列配列になっていますが、<br /> C++でも同様です。C++だけの機能がある場合は明記します。<br /> <br /> == '''文字列配列''' == 文字列そのものが配列なのでした。<br /> char cStr0[]="yonewiki";<br /> ってすると大きさ9で配列…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC 文字列配列になっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

文字列配列

文字列そのものが配列なのでした。
char cStr0[]="yonewiki";
ってすると大きさ9で配列番号0~8までが生成されます。そしてcStr0や&cStr0[0]は文字列配列の先頭アドレスを保持しています。
文字列を配列として扱う場合は、自然と2次元の配列になります。
char cStr[][10]={"yonewiki","mediawiki","google"};
とすると要素数3は自動で生成され、文字数の最大は自分で指定してあげなければなりません。
それぞれの先頭アドレスは、
&cStr[0][0],&cStr[1][0],&cStr[2][0]に格納されています。
cStr[0],cStr[1],cStr[2]もアドレスを指定するという意味では同じです。
&cStr[0],&cStr[1],&cStr[2]としてもアドレスが返ってきますが、
cout関数ではアドレスを出力し、printf関数ではアドレスをいれれば%s書式指定するので文字列が出力されます。

おいおいおい。わけわかんねぇぜ。って思われた人はいるかもしれません。文字列で勉強してきたことを覚えているからなんだと思います。
&cStr0[0]とcStr0って書くとcout関数はアドレスを受け取り、文字列を出力するのでした。&cStr0って書くとアドレスが返ってきました。
cout関数は文字列の&cStr0[0]だと文字列を、文字列配列の&cStr[0]だとアドレス。なんか同じように見えるんすけど…違うのが返ってくるんすね。

#include <iostream>
int main() {
    char cStr0[] = "yonewiki";
    char cStr[][10]={"yonewiki","mediawiki","google"};
    std::cout << "&cStr0[0]    =" << &cStr0[0] << "\n";
    std::cout << "&cStr[0]     =" << &cStr[0] << "\n";
    return 0;
}

出力結果

&cStr0[0]    =yonewiki
&cStr[0]     =002EF984

ってな具合です。文字列の配列になっているのか、ただの文字列なのかで、同じような記述でも意味が異なります。これはややこしい。
文字列配列はcStr[0]や&cStr[0][0]で文字列の出力。&cStr[0]でアドレス。
文字列は  cStr0 や&cStr0[0]で文字列の出力。&cStr0でアドレス。
このような対応になっています。太字部分が同じような書き方ですが、もともとの変数によって意味が違うのです。
変数がどういう文字列変数として定義されたのか、よく理解しないとダメだということを意味しています。

もっと踏み込みます。
charのポインタ型の変数
char *pcStr_0,*pcStr_1,*pcStr_2;
に文字列配列の先頭アドレスを格納させようとすると

    char cStr0[] = "yonewiki";
    char cStr1[] = "mediawiki";
    char cStr2[] = "google";
    char cStr[][10]={"yonewiki","mediawiki","google"};
    char *pcStr_0,*pcStr_1,*pcStr_2;
    pcStr_0=cStr[0];
    pcStr_1=cStr[1];
    pcStr_2=cStr[2];

と記述しないといけませんが、ポインタと文字列は密接な関係にあるのでした。
文字列として宣言したcStr0[]はcStr0や&cStr0[0]として表現できるポインタ変数でもあります。
だとしたら、以下のように記述できても良いのではないか?という発想です。
char *cStr0="yonewiki";
実はできます。これは、配列に似ていますが、"yonewiki"という文字列がメモリ上に置かれて、その先頭アドレスを指し示そうとするものです。
"yonewiki"のようなダブルクォーテーションで囲まれた文字列を文字列リテラルと言います。文字列リテラルがどこに配置されるか、わからないので、残念ながら書き換えることは保障されていません。
書き換えれないことはおいておいて、
更に、
char *cStr0の配列として
char *cStr0[]={"yonewiki","mediawiki","google"};
とできます。これなら文字列の長さを数える必要もないです。
この場合、cStr0[0],cStr0[1],cStr0[2]には、それぞれの文字列リテラルへの先頭アドレスが格納されます。アドレスの先には文字列型のものが入っていると言っています。
配列の中の要素数は配列の全体のcStr0の大きさと配列一つ分のcStr0[0]の大きさを割り算で求めることができます。32bitアドレスなら12byte/4byteで3個ってことです。
sizeof(cStr0)/sizeof(cStr0[0])です。
そうすると

char *cStr0[]={"yonewiki","mediawiki","google"};
for(int i=0;i < sizeof(cStr0)/sizeof(cStr0[0]);i++){
    std::cout << "cStr0[" << i << "]=" << cStr0[i] << "\n";
    std::cout << "cStr0[" << i << "]=" << &cStr0[i] << "\n";//文字列リテラルの先頭アドレスを格納しているアドレス
    std::cout << "cStr0[" << i << "]=" << (void*)*&cStr0[i] << "\n";//文字列リテラルの先頭アドレス
}

とすることで配列の中身を書き出すことも出来ます。
この文字列配列のポインタは、&cStr0[i]が示すようにcStr0[0]=0022FA18,cStr0[1]=0022FA1C,cStr0[2]=0022FA20というアドレス(アドレスは実行する都度変わります。)を持っているだけですから
0022FA18に"yonewiki"という文字列の先頭アドレスを保持しているわけです。この中身のアドレスを見るには(void*)*&cStr0[i]とすれば良いわけです。
そうすると0022FA18の中には→例えば0133E410というアドレス番号が格納されており、0133E410の中身に"yonewiki"の先頭文字の'y'の文字コードが格納されています。
更にこれを

char **ppcStr;// char型のポインタのポインタ
char *cStr0[]={"yonewiki","mediawiki","google"};
ppcStr=cStr0;

とすることもできます。
ん~。このへんから、もう理解できないって人が増えてくる。
char型の文字列が既に配列で先頭アドレスってのを示すことでcout関数とかで出力ができてた。
その先頭アドレスを格納しているアドレス型の変数ってこと。ダメ?。
絵が欲しい?わかった。
C CharMatrixPoiterPointer.png
と上記のような絵を描いてみました。
上側が*cStr[0]~[3]のアドレスが格納される配列です。0022FA18のアドレスから始まるその中に0133E410が格納されています。
絵には少し嘘が入っていて、0133E410はリトルエンディアンという方式が採用されているせいで実際には逆順に10,E4,33,01と格納されてます。
リトルエンディアンの思想が紛らわしいのでとりあえず、順番に書いておきました。
それで、下側の方にあるアドレス0133E410の先頭からyonewikiと順番に格納されています。
そうすると

for(int i=0;i < sizeof(cStr0)/sizeof(cStr0[0]);i++){
    std::cout << "ppcStr[" << i << "]=" << ppcStr[i] << "\n";
}

ってすると文字列が取得できます。 実行結果

ppcStr[0]=yonewiki
ppcStr[1]=mediawiki
ppcStr[2]=google

なんだよppcStrって配列宣言してないのに、配列なのかよ。って思う人もいるかもしれません。
ポインタは配列のように指定することができて、この表記は
ppcStr[i]から*(ppcStr+i)と書き換えることができます。

(void*)ppcStr[i]というアドレスの中に書かれたアドレスを
ppcStr[i]や*(ppcStr+i)で指定しアドレスを指し示す。

これがポインタであり配列なのです。他の型でもこれは同じことです。
ここではiが1増える都度、アドレスの大きさである4Byte分が大きくなって表示されます。
これのアドレスが移動している様(中身)を見たい場合は
(void*)*(ppcStr+i)
とすると良いです。

ここで、まだ理解ができていな人向けに説明しておくと
char **ppcStr;で定義したときはアドレスのアドレスのための変数でありながら、
逆に、これを処理の中に記述すると
**ppcStrはppcStrに格納されているアドレスが指し示す中身の中身という意味になることです。
したがって、**(ppcStr)は'y'を示します。
printf("%c",**ppcStr);
但し、厳密には以下のような記述が良いでしょう。
printf("%c",*(*(ppcStr + 0) + 1));で'o'と確認できます。
なぜならここで3つ定義した文字列リテラルはメモリアドレスの中で連続して配置されているとは限らないからです。
カッコを使って増やすアドレスを増加させる対象をわけておくのがいいでしょう。一番内側のカッコ内が
"yonewiki","mediawiki","google"の3つの要素を移動するためのアドレスを格納するのに必要な4Byteでの移動量を意味しています。
外側がChar型変数の大きさ1Byteでの移動を意味しています。
ポインタのポインタを使った場合でも2つの指定しなければならない値があることで、2次元配列になっていることが実感できたと思います。
興味本位で数字を増やして全部確認していくも良いですが、\0が表示されたらそこで処理を止めないと
未定義の領域をポインタで参照することになり違反となります。
printf("%c",*(*(ppcStr + 1) + 0));なら"mediawiki"の先頭の'm'が表示されます。

より理解を深めるためには、
ポインタと配列を勉強しなおさないとダメですね。またそれは別の項目で記載します。


C++へ戻る