月曜日, 1月 21, 2008

C++解説3(ポインタの確認)

C言語を勉強している多くの人がポインタで挫折するようですね。
何でポインタが難しいのか考えてみます。

int i;
このような変数宣言があるとiはint型の変数である事が宣言されています。
int型とは一般には32bitで構成される数値を管理する型となりますよね。

ポインタもこれとまったく同じ考え方です。
int *p;
このような変数宣言があるとpはint*型の変数である事が宣言されています。
int*型とは32Bitで構成されるアドレスを管理する型となります。

でも宣言は基本的にこれですべてです。
整理して考えれば難しい話は一つもありません。

void swap( int *p, int *q ) {
  int s;
  s = *p;
  *p = *q;
  *q = s;
}

上の関数は入れ替え(swap)を行っていますよね。
ポインタを使っていますが、多分これを難しいと思う人はいないと思います。
簡単に説明するとC/C++において「*」は演算の意味を持っています。
int型に*と書かれていれば、相手ありきとなりますが掛け算となります。
intポインタ型に*と書かれていれば、そのアドレスが指す値と言う事になります。
そのため、上のswap関数は引数としてもらったアドレスが指す要素の値を
書き換える事ができているのです。
つまり、C言語ではアドレスを渡さないと呼び出された関数側では
値を書き換える事ができないという事になります。
関数で引数として宣言している変数は、呼び出し元とは別の領域が確保されます。
関数側ではその値を書き換える事になります。
よって関数側にアドレスを渡し、引数として受け取った関数は
そのアドレスが指す値を変更する事で値の変更を行えるのです。


もう一つ重要な話にC言語において配列はポインタとなるという話があります。

int i;
この宣言がスタック領域に詰まれると以下のようになるとします

「iの領域」
ここにintで宣言されていれば32Bit格納できる大きさで領域が確保されます。
0 = 00000000 00000000 00000000 00000000
1 = 00000000 00000000 00000000 00000001
ってな感じになりますね。

int array[4];
この宣言が行われれば当然intの箱を4つ作れと宣言されているので
32Bit格納できる大きさの領域を4つ分確保します。
つまり上の「iの領域」が連続して4つ確保される事になります。

ここで問題があります。
「i」と宣言したものは当然「i = 0;」と書かれれば
「i」の領域に代入すればいいのですが
「array = 0;」と書かれた場合は
何処に代入すればいいのか分からないという事になります。
そこで代入する時には修飾子として「array[0] = 0;」
といった感じでindexを指定します。
では「array」とは何を指しているのでしょうか?
C言語では配列の変数名は先頭のアドレスを指している事になっています。
つまり「array == &array[0]」という事になります。

では実際に配列を関数に渡してみます。

#include "stdio.h"

void PrintArray( int *Array, int ArraySize ) {
  int i;
  for( i = 0; i < ArraySize; ++i ) {
    printf("%d,", Array[i] );
  }
}

int main(void) {
  int i, Size, Array[10];

  Size = 10;
  for( i = 0; i < Size; ++i ) {
    Array[i] = i;
  }
  PrintArray( Array, Size );

  return 0;
}

実行結果
0,1,2,3,4,5,6,7,8,9,


同様に文字列を関数で出力してみます

#include "stdio.h"

void PrintArray( char **Array, int ArraySize ) {
  int i;
  for( i = 0; i < ArraySize; ++i ) {
    printf( "%s,", Array[i] );
  }
}

int main(void) {
  int Size;
  char *Array[2] = { "senna", "yuuna" };

  Size = 2;
  PrintArray( Array, Size );

  return 0;
}

実行結果
senna,yuuna,

intの配列と何も変わりませんね。
配列はポインタとなるところはそのままなので、
ポインタの配列でポインタのポインタと
なってダブルポインタという事になります。
特に難しい話ではないと思います。

このダブルポインタというものは混乱する人も多いようですが
C言語は文字列はcharの配列になる。
charの配列と言えば、配列なのでポインタとなる。
ポインタの配列と言えば、ポインタのポインタとなる。
何て順序立てて考えれば、難しい事はないと思います。

文字列の配列を関数に渡す事に成功しました。
と言う事は多次元配列(文字列はcharの配列のため)について渡す事に
成功したという事になります。
であれば、int _2DArray[10][10];で宣言されている配列を渡す事も問題なく
理解できると思います。何ていっときながら失敗しちゃうんですよ!
多分、上記の流れでは以下のプログラムをイメージする事でしょう。

#include "stdio.h"

void PrintArray( int **Array, int _1DArraySize, int _2DArraySize ) {
  int i, j;
  for( i = 0; i < _1DArraySize; ++i ) {
    for( j = 0; j < _2DArraySize; ++j ) {
    printf( "%s,", Array[i][j] );
   }
  }
}

int main(void) {
  int _1DSize, _2DSize;
  int Array[3][3] = { 1, 2, 3, 4, 5, 6 };

  _1DSize = _2DSize = 3;
  PrintArray( Array, _1DSize, _2DSize );

  return 0;
}

コンパイルエラーという結果がでたと思います。
2次元配列は文字列の配列とは異なって、同じような引渡しができません。
このような配列を引数として渡す場合、配列数を解決しておく必要があります。
配列とはポインタになる事は問題ないと思いますが、
2次元配列だからダブルポインタという考え方が間違っている事になります。
配列は2次元だろうが、3次元だろうがポインタです。
もちろん、intポインタを格納する配列を作成する事も可能です。
intポインタとは配列を指すため(まあ配列とは限りませんが)
ポインタの配列となり、先ほどの文字列の配列と同じ扱いになります。
それでは、実際にコンパイルエラーとなったプログラムを
コンパイルエラーとならない状態に修正して実行しましょう。

#include "stdio.h"

void PrintArray( int Array[3][3] ) {
  int i, j;
  for( i = 0; i < 3; ++i ) {
    for( j = 0; j < 3; ++j ) {
      printf( "%d,", Array[i][j] );
    }
  }
}

int main(void) {
  int Array[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  PrintArray( Array );
  return 0;
}

実行結果
1,2,3,4,5,6,7,8,9,


最後にintポインタの配列を格納するプログラムを書いてみます。

#include "stdio.h"

void PrintArray( int **Array, int _1Size, int _2Size, int _3Size ) {
  int i, j, ArraySize[3];

  ArraySize[0] = _1Size;
  ArraySize[1] = _2Size;
  ArraySize[2] = _3Size;

  for( i = 0; i < 3; ++i ) {
    for( j = 0; j < ArraySize[i]; ++j ) {
      printf( "%d,", Array[i][j] );
    }
  }
}

int main(void) {
  int *Array[3];
  int Array0[3] = { 1, 2, 3 };
  int Array1[4] = { 4, 5, 6, 7 };
  int Array2[5] = { 8, 9, 10, 11, 12 };
  int _1Size, _2Size, _3Size;
  _1Size = 3;
  _2Size = 4;
  _3Size = 5;

  Array[0] = Array0;
  Array[1] = Array1;
  Array[2] = Array2;

  PrintArray( Array, _1Size, _2Size, _3Size );

  return 0;
}

実行結果
1,2,3,4,5,6,7,8,9,10,11,12,

0 件のコメント: