このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
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 言語の配列というのは次のようにして計算された値になります。
ところが、ポインタの計算では、何の型のポインタかが明らかなため、 +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;
}
このように、ポインタと関数と配列は密接な関係にあります。 特に、配列を関数に渡す時は、配列名である配列の先頭番地を指す変数を渡 すことで、配列を操作できます。 さらに、糖衣構文で仮引数宣言でポインタ型の代わりに、配列の表現も許さ れています。 しかし、関数の中では実体はポインタとして扱われ、配列の性質は失います。
#include <stdio.h>
void a(int *y){
printf("%ld\n",sizeof(y));
}
void b(int y[]){ // 糖衣構文
printf("%ld\n",sizeof(y));
}
int main(void){
int x[]={1,2,3};
printf("%ld\n",sizeof(x)); // 配列のバイト数
a(x); // ポインタ変数のサイズ(OSが32bitか64bitか)
b(x); // ポインタ変数のサイズ(OSが32bitか64bitか)
return 0;
}
複数の文字列を扱う方法ですが、次のように、ポインタの配列として扱いま す。 つまり、ダブルクォーテーションマークによる文字列表現は、C言語として は文字型の番地として扱われます。 したがって、違う長さの文字でも扱えます。
#include <stdio.h>
void a(char** y){
for(; *y!=NULL; y++){
printf("%s\n",*y);
}
}
void b(char* y[]){ //糖衣構文
for(; *y!=NULL; y++){
printf("%s\n",*y);
}
}
int main(void){
char* x[]={"a", "bc", "def", NULL};
char** p;
for(p=x; *p!=NULL; p++){
printf("%s\n",*p);
}
a(x);
b(x);
return 0;
}
二次元配列に関しては、列数を定めなければなりません。 さらに、ポインタを作る時は列数を指定した宣言をするため、難解な構文にな ります。
#include <stdio.h>
void a(int (*y)[2]){
int j;
for(; (*y)[0]!=0; y++){
for(j=0;j<2;j++){
printf("%d ",(*y)[j]);
}
printf("\n");
}
}
void b(int *y){
int j;
for(j=0;j<2;j++){
printf("%d ",y[j]);
}
printf("\n");
}
int main(void){
int x[][2]={{1,2},{3,4},{5,6},{7,8},{0}};
int i,j;
int (*p)[2]; // 二次元配列のポインタ
for(i=0;x[i][0]!=0;i++){
for(j=0;j<2;j++){
printf("%d ",x[i][j]);
}
printf("\n");
}
for(p=x; (*p)[0]!=0; p++){
for(j=0;j<2;j++){
printf("%d ",(*p)[j]);
}
printf("\n");
}
a(x);
for(i=0;x[i][0]!=0;i++){
b(x[i]); //内側の配列をポインタで渡す
}
return 0;
}
void inc(double * x)
を作りなさい。
double a=0.1
に対して、「inc を 呼び値を表示
する」を3 回行いなさい。
ポインタを使って、文字配列 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;
}
文字配列の内容を別の文字配列にコピーする関数を書き、 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;
}