Comments
Description
Transcript
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となる。 【発展課題】おやすみ。