Lesson 7 ポインタ

1
Lesson 7 ポインタ
C言語には、ポインタとよばれる変数が用意されている
ポインタを使うことで、ある変数が格納されているメモリ上の
アドレスを参照できる。ポインタを理解し、その使い方を覚えよう
前回までに演習した「関数」は、複数の値を受け取って一定の計算と処理を行った後、一つの値を返すも
のであった。これは数学で扱う通常の関数(sinθなど)と同じである。いま、 二つの整数a,bを受け取
り、その値を入れ替える関数
void swap(int a, int b);
を作りたい。 例えばa=1, b=4であったとすると、swap(a,b)を呼び出した後は、a=4, b=1となるようにした
い。 実は、これまでに覚えた関数の機能だけでは、このような単純な処理も実現不可能なのである。
今回は、ポインタという機能を使って、上記の関数を実現する方法を学ぶ。
6-1 swap(int a, int b)の失敗例
プログラム上で、二つの変数aとbの値を入れ替えるには、適当なもう一つの変数(ここではtempとする)
を用意して、
temp=a; a=b; b=temp;
という三つの代入を実行すればよい。しかし、次のプログラムのswap(int a, int b)は、ねらい通りに動か
ない。
failure.c
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
#include <stdio.h>
void swap( int, int );
main()
{
int a=1, b=4;
printf("a=%d, b=%d\n",a,b);
swap(a,b);
printf("a=%d, b=%d\n",a,b);
}
void swap(int num1, int num2){
int temp;
temp = num1;
num1 = num2;
num2 = temp;
}
実行結果:
[nagahiro@Tacoma]: ./a.out
a=1, b=4
a=1, b=4
[nagahiro@Tacoma]:
2
このプログラムが成功しない理由が分かるだろうか? main関数内の10行目で、swap(a, b)が呼び出されて
いる。これによって15行目に処理が飛び、
num1=a; num2=b;
という代入が行われる。しかしnum1, num2とa, bはそれぞれ別のローカル変数なので、関数swap内でnum1,
num2 をどう変更しても、呼び出された元のa, bは変化しないのである。
関数 swap(a, b) の内部からmain関数内で宣言されている変数int aとint bにアクセスすることができれ
ば、上の問題は解決するのである。そのためには変数の「アドレス」を操作する機能を使えるようにならな
ければならない。
6-1 変数とアドレス
変数に格納される値は、コンピュータ上の「メ
モリ」と呼ばれる領域に記憶されている。例え
ば、
int a, b=4;
という変数宣言を行った場合、メモリ上では右の
図のような形で値が保存される。
値が格納されている領域は、家のようなもので
ある。プログラマーがa, bと名付けた変数名は
∼メモリ∼
住所(アドレス)
$%%%
$%%(
変数名
#$%&'&('%)(
!
*+,-./012
3456789:
(
"
-;<=/
$%%>
$%$)
は、「山本」とか「田中」と、家に掲げられた表札
と同じとおもってほしい。家の建っている位置が住所によって示されるように、変数がメモリ上のどの場所
に記憶されているかを示すのが「アドレス」である。アドレス演算子「&」を使って、アドレスの値を得る
ことができる。上の例で言えば
&a
&b /* =1000 */
/* =1004 */
となっている。
ポインタ変数
アドレスは単なる数字であるが、「変数に格納されている値」と「変数のアドレス」はそれぞれ別物であ
る。
a=1012;
という代入はいつでも可能であるけれど、
&a=1012;
という記述によって、アドレスの値を変更することはできない。「変数に格納されている値」と「変数のア
ドレス」は明確に区別しなければならない。そのため、C言語にはアドレスの値だけを格納するための変数
が用意されていて、それをポインタ変数と呼ぶ。ポインタ変数は変数名の直前にアスタリスク「*」をつけて
次のように宣言する。
int *p_1; /*「int型の変数のアドレス」を格納するポインタ変数 p_1 の宣言*/
double *p_2; /*「double型の変数のアドレス」を格納するポインタ変数 p_2 の宣言*/
これは次のようにして使う。今、
int c=5, *p; /*int型の変数cとポインタ変数pの宣言*/
p=&c; /*ポインタ変数pにcのアドレスの値を代入*/
printf("c=%d, *p=%d\n", c, *p);
3
としたする。二行目で、ポインタ変数pに、cのアドレスを代入している。すると三行目のprintfによって表
示される*pの値が 5 になるのである。ここがポインタの一番重要な点である。pにくっついている「*」は間
接参照演算子といい
pがポインタ変数であるとき、*pは、メモリ上のアドレスpの中に記憶されている値
である。上の場合、2行目のp=&c;によって、pにcのアドレスが代入されているから、*pはcの値そのものに
なるのである。
【Problem】ポインタ変数を使ったアドレスの扱いを理解するために、次のプログラムを実行し、動作を
確認せよ。
sample13.c
1: #include <stdio.h>
2:
3: main(void){
4:
int a=3;
5:
int *p;
6:
7: p=&a; /*ポインタ変数pにaのアドレスを代入*/
8:
printf("a=%d, p=%d, *p=%d\n", a, p, *p);
9: 10: *p=5; /* (*p)に5を代入すると、aの値も5に変更される*/
11: printf("a=%d, p=%d, *p=%d\n", a, p, *p);
12:
13: }
実行結果
[nagahiro@Tacoma]: ./a.out
a=3, p=-1073743144, *p=3
a=5, p=-1073743144, *p=5
[nagahiro@Tacoma]:
sample13.cの解説
4行目: int型の変数aを宣言し、それに適当な数字を代入している。
5行目: int型のポインタ変数pを宣言。
7行目: aのアドレスをポインタ変数pに代入。(←重要!)
8行目: この時点で、a, p, *pの値をそれぞれ表示。aの値は3。pの値はaのアドレスであり、一見意
味不明な数が出力されている。*pの値は、アドレスpの中に記憶されている値であるから、3である。
10行目: 「アドレスpの中に5を代入せよ」という意味になる。(←重要!)
11行目: 10行目で*p=5; としたので、aの値も5に変更されている。
このように、ポインタ変数pに変数aのアドレスを代入しておけば、pを介してaの値を変更できるのである。
よってポインタを使えば、関数swap(int a, int b);を実現できる。failure.cの10行目で、関数swap()に、
変数aとbのアドレスを渡してあげればよい。つまり10行目を
swap(&a, &b);
4
としてやればよい。これに伴って関数swap()は
void swap( int *num1, int *num2 ){
int temp;
temp = *num1;
*num1 = *num2;
*num2 = temp;
}
とポインタ変数を引数とする形に変更する。
【提出課題1】failure.cの関数swap()を、ポインタを利用する形に変更し、ねらい通りに動作するかどうか
確認せよ。
【提出課題2】 前問で作成したswap()関数を参考にして、三つの変数の値を小さい順に入れ替える関数
numeric_order_swap( int *num1, int *num2, int *num3)
を作成せよ。たとえば
int a=4, b=3, c=1;
numeric_order_swap( &a, &b, &c );
とするとaの値は1,bの値は3、cの値は4となる。
【発展課題】おやすみ。