FukuRiko's WebSite

HOME | はじめに | プロフィール | フリーソフト | シェアウェア | Programming Tips | C言語 | 開発中のソフト
掲示板 | Blog | サイトマップ | 問い合せ | リンク

C言語ポイント学習

C言語初心者向けのページです。
づまづきやすい、「ポインタと配列」についてまとめてみました。
内容は少ないですが随時ふやしていきたいと思っています。
  1. 配列とポインタ(その1)
  2. 配列とポインタ(その2)
  3. 文字列・ポインタと配列
  4. 文字列と2次元配列
  5. 文字列の比較
C言語で文字列の配列を表現するにはどうしたら良いでしょう?
ここでは、下記ようなの名簿を管理するプログラムを想定して
話を進めていきます。

 ・名簿
  ┏━━━━━━━━━━━━━━━━━━━━━┓
  ┃Asano                                     ┃
  ┃Fukushima                                 ┃
  ┃Furuichi                                  ┃
  ┃Hirao                                     ┃
  ┃Nanbo                                     ┃
  ┗━━━━━━━━━━━━━━━━━━━━━┛

 
文字の配列の配列  
上記の名簿(と言っても名前しかないですが。)を配列を使って表現すると
以下のようになります。

   char Meibo[5][10] = { "Asano", "Fukushima", "Furuichi",  "Hirao", "Nanbo" } ;

メモリをイメージ的に表現すると以下のようになります。

     ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓─┐
     ┃A┃s┃a┃n┃o┃\0┃・┃・┃・┃・┃  │
     ┣━╋━╋━╋━╋━╋━╋━╋━╋━╋━┫  │
     ┃F┃u┃k┃u┃s┃h┃i┃m┃a┃\0┃  │
     ┣━╋━╋━╋━╋━╋━╋━╋━╋━╋━┫  │
     ┃F┃u┃r┃u┃i┃c┃h┃i┃\0┃・┃  │ 5 Bytes
     ┣━╋━╋━╋━╋━╋━╋━╋━╋━╋━┫  │
     ┃H┃i┃r┃a┃o┃\0┃・┃・┃・┃・┃  │
     ┣━╋━╋━╋━╋━╋━╋━╋━╋━╋━┫  │
     ┃N┃a┃n┃b┃o┃\0┃・┃・┃・┃・┃  │
     ┗━┻━┻━┻━┻━┻━┻━┻━┻━┻━┛─┘
     └─────── 10 Bytes ───────┘      (・の部分は不定値)

  C言語での2次元配列は [行の数][列の数] で定義します。
つまり、char Meibo[5][10] と定義すると 5行×10列=50バイトの
2次元配列が用意されるわけです。

名簿を表示する時は各行の先頭アドレスを示せば良いので
単純に書くと

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃  printf("%s\n",&Meibo[0][0]);   /* Asano を表示       */   ┃
  ┃  printf("%s\n",&Meibo[1][0]);   /* Fukushima を表示   */   ┃
  ┃  printf("%s\n",&Meibo[2][0]);   /* Furuichi を表示    */   ┃
  ┃  printf("%s\n",&Meibo[3][0]);   /* Hirao を表示       */   ┃
  ┃  printf("%s\n",&Meibo[4][0]);   /* Nanbo を表示       */   ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

となります。


 
ポインタ変数の配列  
今度は、ポインタ変数の配列を使って名簿を表現してみましょう。
下記のようになります。

   char* Meibo[5] = { "Asano", "Fukushima", "Furuichi",  "Hirao",  "Nanbo" } ;

2次元配列のときと同じように見えますが、ちょっと(かなり)意味が違ってきます。
この例の場合、

   char* Meibo[5]

の部分で ポインタ変数(char*) を5個用意して、文字列の先頭ポインタを代入するからです。
図にすると下記のようになります。

各文字列の先頭アドレスは 1000,2000,3000,4000,5000と仮定しています。
(実際はこんなに離散してアドレスが割り振られることはないんですねどね!)


             char* の配列     アドレス
            ┏━━━━━┓     1000
            ┣          ┫       ┏━┳━┳━┳━┳━┳━┓
   Meibo[0] ┣ 1000 ┫  ←   ┃A ┃s ┃a ┃n ┃o ┃\0┃
            ┣          ┫       ┗━┻━┻━┻━┻━┻━┛
            ┣━━━━━┫     2000
            ┣          ┫       ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓
   Meibo[1] ┣ 2000 ┫  ←   ┃F ┃u ┃k ┃u ┃s ┃h ┃i ┃m ┃a ┃\0┃
            ┣          ┫       ┗━┻━┻━┻━┻━┻━┻━┻━┻━┻━┛
            ┣━━━━━┫     3000
            ┣          ┫       ┏━┳━┳━┳━┳━┳━┳━┳━┳━┓
   Meibo[2] ┣ 3000 ┫  ←   ┃F ┃u ┃r ┃u ┃i ┃c ┃h ┃i ┃\0┃
            ┣          ┫       ┗━┻━┻━┻━┻━┻━┻━┻━┻━┛
            ┣━━━━━┫     4000
            ┣          ┫       ┏━┳━┳━┳━┳━┳━┓
   Meibo[3] ┣ 4000 ┫  ←   ┃H ┃i ┃r ┃a ┃o ┃\0┃
            ┣          ┫       ┗━┻━┻━┻━┻━┻━┛
            ┣━━━━━┫     5000
            ┣          ┫       ┏━┳━┳━┳━┳━┳━┓
   Meibo[4] ┣ 5000 ┫ ←    ┃N ┃a ┃n ┃b ┃o ┃\0┃
            ┣          ┫       ┗━┻━┻━┻━┻━┻━┛
            ┗━━━━━┛     


表示を行うときは、すでに Meibo[n]の中に文字列の先頭アドレスが入っているので

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃  printf("%s\n",Meibo[0]);   /* Asano を表示       */   ┃
  ┃  printf("%s\n",Meibo[1]);   /* Fukushima を表示   */   ┃
  ┃  printf("%s\n",Meibo[2]);   /* Furuichi を表示    */   ┃
  ┃  printf("%s\n",Meibo[3]);   /* Hirao を表示       */   ┃
  ┃  printf("%s\n",Meibo[4]);   /* Nanbo を表示       */   ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

とすれば、名簿が表示されます。
説明を省いていましたが、OSが32ビットの場合ポインタ変数のサイズ
も32ビット(4バイト)になります。

ポインタ変数へのポインタ  
先ほどの例の

   char* Meibo[5];

の部分はポインタ変数の配列を表現していますよね?
ポインタ変数の配列のアドレスを何かしらの変数に格納する場合は

  char** ptr = &Meibo[0];

となります。
char* のアドレスを格納するわけですから char** となるのです。
(char のアドレスを格納したときは char* でしたね!)
メモリイメージは下記のようになります。
  ※Meibo[0] の先頭アドレスを100と仮定しています。


     char** ptr        アドレス   char* の配列     アドレス
   ┏━━━━━┓    ┌─ 100 ┏━━━━━┓     1000
   ┣          ┫    │       ┣          ┫       ┏━┳━┳━┳━┳━┳━┓
   ┣  100  ┫←─┘       ┣ 1000 ┫  ←   ┃A ┃s ┃a ┃n ┃o ┃\0┃
   ┣          ┫             ┣          ┫       ┗━┻━┻━┻━┻━┻━┛
   ┗━━━━━┛         104 ┣━━━━━┫     2000
                              ┣          ┫       ┏━┳━┳━┳━┳━┳━┳━┳━┳━┳━┓
                              ┣ 2000 ┫  ←   ┃F ┃u ┃k ┃u ┃s ┃h ┃i ┃m ┃a ┃\0┃
                              ┣          ┫       ┗━┻━┻━┻━┻━┻━┻━┻━┻━┻━┛
                          108 ┣━━━━━┫     3000
                              ┣          ┫       ┏━┳━┳━┳━┳━┳━┳━┳━┳━┓
                              ┣ 3000 ┫  ←   ┃F ┃u ┃r ┃u ┃i ┃c ┃h ┃i ┃\0┃
                              ┣          ┫       ┗━┻━┻━┻━┻━┻━┻━┻━┻━┛
                          112 ┣━━━━━┫     4000
                              ┣          ┫       ┏━┳━┳━┳━┳━┳━┓
                              ┣ 4000 ┫  ←   ┃H ┃i ┃r ┃a ┃o ┃\0┃
                              ┣          ┫       ┗━┻━┻━┻━┻━┻━┛
                          116 ┣━━━━━┫     5000
                              ┣          ┫       ┏━┳━┳━┳━┳━┳━┓
                              ┣ 5000 ┫ ←    ┃N ┃a ┃n ┃b ┃o ┃\0┃
                              ┣          ┫       ┗━┻━┻━┻━┻━┻━┛
                              ┗━━━━━┛ 


char** ptr を使って名簿を表示するには

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃  ptr = &Meibo[0];           /*  Meibo[0]の先頭アドレスを代入 */    ┃
  ┃                                                                    ┃
  ┃  printf("%s\n",*(ptr+0));   /* Asano を表示       */               ┃
  ┃  printf("%s\n",*(ptr+1));   /* Fukushima を表示   */               ┃
  ┃  printf("%s\n",*(ptr+2));   /* Furuichi を表示    */               ┃
  ┃  printf("%s\n",*(ptr+3));   /* Hirao を表示       */               ┃
  ┃  printf("%s\n",*(ptr+4));   /* Nanbo を表示       */               ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

と書くことができます。
なぜ?このプログラムで今までと同じ様に表示されるかというと

  ptr に 100(番地)が入っていますよね?
*ptr とすると100番地の内容つまり、1000 が取得できるわけです。
1000は文字列 "Asano" の先頭アドレスなので "Asano" と表示されます。
つぎに、
 ptr + 1 = 104 (char* のサイズ分増減する)
*(ptr+1) とすると104番地の内容つまり、"Fukushima" の先頭アドレス 2000 が
取得できるわけです!


ここでちょっとした、実験です。*(ptr+n)の部分を以下のように
*(ptr)+n と変更するとどうなるでしょう?

   実験)*(ptr+n) を *(ptr)+n に変更すると。
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃  printf("%s\n",*(ptr)+0);                              ┃
  ┃  printf("%s\n",*(ptr)+1);                              ┃
  ┃  printf("%s\n",*(ptr)+2);                              ┃
  ┃  printf("%s\n",*(ptr)+3);                              ┃
  ┃  printf("%s\n",*(ptr)+4);                              ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

表示結果は以下のようになります。

   実験)表示結果
  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃  Asano                                                 ┃
  ┃  sano                                                  ┃
  ┃  ano                                                   ┃
  ┃  no                                                    ┃
  ┃  o                                                     ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

あはは、変な表示結果になりましたね!
どううして、このような結果になる、仮の番地で考えてみると簡単です。

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃  *(ptr) は 1000 番地 なので                            ┃
  ┃                                                        ┃
  ┃  *(ptr) + 1 = 1001番地                                 ┃
  ┃     ↑                                                 ┃
  ┃     └──── *(ptr)が先に展開される。                ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

*(ptr+1) のときは ptr+1 が先に展開されてましたが *(ptr)+1 のときは
*ptr が先に展開されるから結果のように1文字ごとにずれて文字がでて
きたのです。

  ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃ *ptr + n  ← このような書き方は演算の優先順位が分からない!    ┃
  ┃    ↓                                                          ┃
  ┃ *(ptr + n)                                                     ┃
  ┃    又は                                                        ┃
  ┃ *(ptr) + n        ※ 用途に応じて明示的に括弧をつけること。    ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

このように、C言語では括弧の優先順位で結果ががらりと変わってしまう
場合があるので注意が必要です。優先順位がどちらになるか分からない場合
は明示的に()をつけて無用な混乱を避けましょう!


どうして?「ポインタ変数のポインタ」のような面倒なものを
覚えなければいけないのでしょうか?

データを受け渡す場合を考えてみましょう。
今回の例では、名簿という大きなデータ(と言っても 数十バイトのものですが・・・)
を受け渡さなければいけませんが、ポインタ変数のポインタを使えば
受け渡すデータのサイズは4バイトで済みます。
このように、C言語ではポインタを使ってデータの受け渡しを
高速に処理するとこができるのです。