第 6 回 ポインタ

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

6-1. ポインタ

C 言語では、変数の値の格納しているメモリの番地を取り扱うことができます。 変数 x に対して、その変数の値のメモリの番地は &x で表します。 また、この番地を変数に代入して処理することもできます。 このメモリの番地を扱う変数を ポインタ と言います。 但し、変数の番地を扱う場合、その変数の型は常に意識します。 int 型の変数の番地を扱う変数の型は int* で表します。 int *y; と宣言すると y は int の変数の番地を取り扱うこと ができます。 x が int 型だった場合、 y = &xで x の番地を代入できま す。 また、 *y とするとこれは y に格納されているメモリの番地に入っている値 を表します。 x=1 とすると *y は 1 になります。 さらに、 *y=2 とすると、 x は 2 になります。 このメモリの番地やポインタには様々な利用法があります。

なお、上記の y の変数型は int* だと考えることができますが、一方で、C言 語では、複数のポインタを宣言する場合に、次の様にしないといけません。


int *y, *z;

これを int* y,z と宣言してしまうと、 z は int* 型ではなく、 int 型で定 義されてしまいます。 このように空白の位置や文法的なくせにより、ポインタ宣言は int* yと書かれたり、int *y と書かれたりします。 本講義では int* で宣言をすることと、複数のポインタを一度に宣言せず、 一行に一つだけ宣言するということを推奨します。

関数とのデータのやりとり

C 言語では関数の中で宣言された変数はローカル変数なので、 何の工夫もなく別の関数からその変数の値を変更することはできません。 一つの変数の値だけなら、 a=f(a) のように関数の戻り値を代 入することで f から a の値を変更できますが、二つ以上の変数に対しては行 えません。 例えば、変数 a と変数 b の値を交換することを考えます。 関数さえ呼び出さなければ次のようにすればできます。


int a=4,b=1;
int c;

c=a;
a=b;
b=c;

この処理はしばしば出てくるので、これを関数としたいところです。 ところが次のようにしても思うように動きません。


#include <stdio.h>
void swap(int x, int y){
  int z;
  z=x;
  x=y;
  y=z;
  return;
}
int main(void){
  int a,b;
  a=2; b=3;
  swap(a,b);
  printf("a=%d, b=%d\n",a,b);
  return 0;
}

関数の引数の宣言での x, y はローカル変数です。 従って、呼び出す側の変数と値は同じですが、既に別の変数ですので、代入し ても呼出側の変数の値は変わりません。

そこで、ポインタを使います。 呼び出し側の変数の番地をサブルーチンに渡し、関数では、その番地に値を書 き込むのです。 次のようにすると思った通りの動きになります。


#include <stdio.h>
void swap(int* x, int* y){
  int z;
  z=*x;
  *x=*y;
  *y=z;
  return;
}
int main(void){
  int a,b;
  a=2; b=3;
  swap(&a,&b);
  printf("a=%d, b=%d\n",a,b);
  return 0;
}

なお、C++ 言語では 他の関数の中で呼び出す側の変数の使用を許可することができま す。 これを、変数呼び出し(C++ 用語では参照呼び出し)と呼びます。 関数定義の際、引数の定義で変数の前に & を付けると参照呼び出しであ ることが指定できます。 次のプログラムは C 言語ではコンパイルできませんが、 C++ ではコンパイル でき、予想通りの動作をします。


#include <stdio.h>
void swap(int& a, int& b){
  int c;
  c=a;
  a=b;
  b=c;
  return;
}
int main(void){
  int a=3,b=4;
  printf("%d, %d\n",a,b);
  swap(a,b);
  printf("%d, %d\n",a,b);
  return 0;
}

配列の要素を指す

C 言語での配列名は実はポインタです。 配列 a の 0 番目の要素が格納されているメモリ上の番地が a になっていま す。つまり、 &a[0] と a は等しいです。ですから、a[0] と *a は等し いです。


#include <stdio.h>
int main(void){
  double a[]={1.0,2.0,3.0,-1.0};
  printf("&a[0]=%d, a=%d\n",(int)&a[0],(int)a);
  printf("a[0]=%f, *a=%f\n",a[0],*a);
  return 0;
}

では a[1] と a の関係はどうでしょうか? double のように一文字では表せないようなデータは、複数の番地に渡って格 納されているため、 a の隣の番地はまだ a[0] の内容の続きが入っています。 sizeof 演算子を使うと、その型が何バイトの記憶領域を使うかが 分かります。 筆者の環境では sizeof (double) の値は 8 になっています。 つまり、一つの double 型の変数を格納するのに 8 バイトの記憶領域が必要 になってきます。 ですから、 a[1] の番地は a[0] の番地から 8 バイト先の位置に記憶されて います。 結局、 C 言語の配列というのは次のようにして計算された値になります。

a[i] = * ( a + i*sizeof(a[0]))

ところが、ポインタの計算では、何の型のポインタかが明らかなため、 +1 を すると、次の要素を指すように値が増えます。つまり、その型のサイズ分だけ 増えます。 結局 a[1]=*(a+1) となります。


#include <stdio.h>
int main(void){
  double a[]={1.0,2.0,3.0,-1.0};
  printf("&a[1]=%d, a=%d, sizeof double = %d\n",
	 (int)&a[1],(int)a,sizeof (double));
  printf("a + 1 * sizeof a[0] = %d, a + 1 =%d \n",
	 (int)a + 1*sizeof a[0], (int)(a+1)); 
  printf("a[1]=%f, *(a+1)=%f\n",a[1],*(a+1));
  return 0;
}

これを使うと、配列の値を次々指し示す場合に ++ などのインクリメントが使えま す。 またループ変数を無くすこともできます。


#include <stdio.h>
int main(void){
  double a[]={1.0,2.0,3.0,-1.0};
  double* x;
  for(x=a; *x != -1; x++){
    printf("%f\n",*x);
  }
  return 0;
}

6-2. 演習問題

演習6-1

  1. 変数のポインタを渡すと1.0 ふやす関数void inc(double * x) を作りなさい。
  2. main 関数で double a=0.1 に対して、「inc を 呼び値を表示 する」を3 回行いなさい。

演習6-2

ポインタを使って、文字配列 x[] の内容を文字配列 y[] にコピーするプログ ラムを書きなさい。 但し、下の例において、これ以上変数の宣言をしないこと。


#include <stdio.h>
int main(void){
  char x[]="abcdef";
  char y[50]="xxxxxxxxxxxx";
  char* p;
  char* q;

  /* p と q を使って x の内容を y にコピーする */
  printf("%s をコピーした結果 %s となる\n",x,y);
  return 0;
}

演習6-3

文字配列の内容を別の文字配列にコピーする関数を書き、 x[] の内容を y[] にコピーしなさい。 但し、下の例において、これ以上変数の宣言をしないこと。


#include <stdio.h>
void copy(char* a, char* b){
  /* a の内容を b にコピーする */
}
int main(void){
  char x[]="abcdef";
  char y[50]="xxxxxxxxxxxx";
  copy(x,y);
  printf("%s をコピーした結果 %s となる\n",x,y);
  return 0;
}

坂本直志 <[email protected]>
東京電機大学工学部情報通信工学科