はじめに
ポインタの基本から、関数へのポインタ渡しまでをなるべく行間を作らず解説します。
ポインタとは
まず下のコードをご覧ください。
# include <stdio.h>
int main(){
int i;
i = 1;
printf("%d\n", i);
}
実行結果
1
このコードでは、まずint iで整数型変数iが生成され、メモリ領域が割かれます。そしてi=1でそのメモリ領域に1というデータが保存されます。
ポインタとは、iのような変数が記憶されるメモリ領域の住所のことです。ポインタを得るには変数の前に&を付けて&iとします
# include <stdio.h>
int main(){
int i = 1;
printf("%p\n", &i); //ポインタの指定変換文字はp
}
実行結果
0061FF2C
ポインタ変数
上の実行結果の0061FF2Cを保持する変数が必要になるときもあるでしょう。そのような変数は、ポインタを保持する変数なのでポインタ変数と呼ばれます。ポインタ変数の型は変数の型*で宣言します。例えばint型の変数へのポインタ変数はint*で、char型の変数へのポインタ変数はchar*で宣言します。
# include <stdio.h>
int main(){
int i = 1;
int* pointer; //int型のポインタを宣言
pointer = &i;
printf("%p\n", &i);
printf("%p\n", pointer);
}
実行結果
0061FF28
0061FF28
何故ポインタ変数の宣言で、元の変数の型を指定する必要があるのでしょうか?それはメモリの仕組みと関係しています。メモリには、1バイトずつ、固有の住所が割り当てられています。それが上の0061FF28などの16進整数です。int型の変数は4バイトのデータをもつのでメモリ領域は0061FF28, 0061FF29, 0061FF2A, 0061FF2Bの4つです。しかし、4つも住所を扱うのは大変ですから、「0061FF28から連続した4バイト分の領域」として扱います。バイト数は変数の型によって違いますから、ポインタ変数を宣言する際に、変数の型の情報が必要だということです。
なお、上のコードの
int* pointer;
pointer = &i;
は
int* pointer = &i;
と一行に書き換えることができます。
ポインタ変数の前に*を付けると、ポインタ変数から元の変数にアクセスすることができます。
# include <stdio.h>
int main(){
int i = 1;
int* pointer = &i;
printf("%d\n", *pointer);
}
実行結果
1
実は*pointerはiとまったく同じように扱えます。*pointerに値を代入すると、iに代入したことになります。
# include <stdio.h>
int main(){
int i = 1;
int* pointer = &i;
*pointer = 2;
printf("%d\n", i);
}
実行結果
2
ポインタ演算
ポインタに整数を足し引きすることができます。
int型のポインタの場合、ポインタに1を足すと4バイト分だけポインタが移動します。これは、int型が4バイト分のメモリ領域をもつからです。
# include <stdio.h>
int main(){
int i = 1;
int* pointer = &i;
printf("%p\n", pointer);
pointer++; //ポインタを移動する
printf("%p\n", pointer);
}
実行結果
0061FF28
0061FF2C
16進数なので0061FF28+4=0061FF2Cとなります。
配列とポインタ
配列の要素は連続したメモリ領域に記憶されており、ポインタを用いて配列を操作することができます。要素がn個ある配列i[n]のiは、実は先頭の配列要素i[0]へのポインタとなっています。
# include <stdio.h>
int main(){
int i[3]={1, 2, 3};
printf("%p\n", i);
printf("%d\n", *i);
}
実行結果
0061FF24
1
ここで、0061FF24は配列の先頭要素である1へのポインタです。
iのポインタ演算も行うことができます。
# include <stdio.h>
int main(){
int i[3]={1, 2, 3};
printf("%d\n", *i);
printf("%d\n", *(i+1)); //i+1は配列2番目要素を指すポインタ。*演算子で値にアクセス
printf("%d\n", *(i+2));
}
実行結果
1
2
3
ただしiはポインタ変数でないことに注意が必要です。ポインタ変数はポインタを保持する変数であるので、保持している値(=ポインタ)を変えることができます。一方でiはポインタ変数でないので、ポインタを変えてしまうような操作、例えばi++などはできません。
上の例をみると、i[k]と*(i+k)は同じ意味をもつことがわかります。実はi[k]よりも、*(i+k)のほうがより本質的な記述であり、i[k]は便宜的な記法にすぎません。このことをi[k]は*(k+1)のシンタックスシュガーであるといいます。
関数へのポインタ渡し
これまでに紹介したポインタの原理を使うと関数から複数の返り値を受け取ることができます。
# include <stdio.h>
//2つの引数をそれぞれ0, 1にする関数
void zero_one(int* l, int* m){ //ポインタ変数l, mがポインタを受け取る
*l=0; //*演算子で値にアクセス
*m=1;
}
int main(){
int i=2, j=3;
zero_one(&i, &j); //ポインタを渡す
printf("%d/t%d\n", i, j);
}
実行結果
0 1
関数に配列を渡すこともできます
# include <stdio.h>
//配列の最初の要素を0に、2番目の要素を1にする関数
void zero_one(int* p){ //ポインタ変数pがポインタを受け取る
p[0] = 0; //*p=0と同値
p[1] = 1; //*(p+1)=1と同値
}
int main(){
int i[2] = {2, 3};
zero_one(i); //ポインタを渡す。配列iはポインタになってる!
printf("%d\t%d\n", i[0], i[1]);
}
実行結果
0 1
おわりに
間違った記述があればご指摘お願いいたします。