C言語ではポインタの話を避けて通れません. ちうわけでちょこっと説明してみました. すべての文責は高橋にあります.
計算機アーキテクチャ的にはええかげんな説明になってますので気をつけましょう.
2022-06-06 追記: 表示の仕組みのせいで一部のコードのインデントがめちゃくちゃになってます.ごめんなさい.
いきなりですが,以下の二つのプログラムの実行結果の違いとその理由を説明できますか?できない人は,それぞれのリンクをたどってふるまいを調べてみませう.
違いがわかる人は,「printf()には&はいらんのにscanf()には必要」なんがなんでかわかりますね.
1 void hoge(int x, int y); 2 3 int main() 4 { 5 int a, b; 6 7 a = 7; b = 3; 8 hoge(a, b); 9 printf("%d %d\n", a, b); 10 } 11 12 void hoge(int x, int y) 13 { 14 y = x + y; 15 printf("%d %d\n", x, y); 16 }
1 void hoge(int x, int *y); 2 3 int main() 4 { 5 int a, b; 6 7 a = 7; b = 3; 8 hoge(a, &b); 9 printf("%d %d\n", a, b); 10 } 11 12 void hoge(int x, int *y) 13 { 14 *y = x + *y; 15 printf("%d %d\n", x, *y); 16 }
関数への値の受け渡しでのポインタの使い方がわかってくると,「ほななんで配列の受け渡しはあんなんでええのん?」という疑問がわいてきます.
配列を使った例として以下のソースを考えましょう.
● プログラム例 fuga1.c
1 void fuga(int x[], int n); 2 3 int main() 4 { 5 int a[10]; 6 int n = 3, i; 7 8 for(i = 0; i < n; i++){ 9 a[i] = 2*i + 1; 10 printf("%d %d\n", i, a[i]); 11 } 12 printf("-----\n"); 13 14 fuga(a, n); 15 16 for(i = 0; i < n; i++){ 17 printf("%d %d\n", i, a[i]); 18 } 19 } 20 21 void fuga(int x[], int n) 22 { 23 int i; 24 25 for(i = 0; i < n; i++) x[i] *= 3; 26 }
このプログラムを実行すると,以下のような結果が得られます.
0 1 1 3 2 5 ----- 0 3 1 9 2 15
関数fuga()によって配列aの中身が変更されていることがわかります.配列ではない変数の場合には,関数の中での値の変更をmain()の側にも反映させるためにはポインタを使う必要がありました.しかしこの例では,ポインタを使っているように見えないのに,関数中での変更が外の世界にも反映されています.一体どうなっているのでしょうか.
実は,
ということがみそになっています.
以下のソースを例にしてこれを説明してみます.これは上記のソースを少し書き換えたものですが,実行すると上記のものと同じ結果となります.
● プログラム例 fuga2.c
1 void fuga(int x[], int n); 2 3 main() 4 { 5 int a[8]; 6 int n = 3, i; 7 8 for(i = 0; i < n; i++){ --> 9 *(a + i) = 2*i + 1; --> 10 printf("%d %d\n", i, *(a + i)); 11 } 12 printf("-----\n"); 13 14 fuga(a, n); 15 16 for(i = 0; i < n; i++){ 17 printf("%d %d\n", i, a[i]); 18 } 19 } 20 21 void fuga(int x[], int n) 22 { 23 int i; 24 25 for(i = 0; i < n; i++) x[i] *= 3; 26 }
この例の5,9,10行目を説明すると,
ということになっています(注:二項演算子 + よりポインタのための演算子 * の方が優先順位が高いので,*a + i と書くと違う結果になります).
C言語のコンパイラは,a[i] というソースを見たら *(a + i) に置き換えて処理しているので,当然 fuga1.c も fuga2.c も同じ結果となるわけです(ちなみに,*(a + i) = *(i + a) ですから,配列の名前と添字を引っくり返して i[a] とか書いても平気やったりします.3[a] とか書いても平気やったりします.気持悪いけど...).
以上のことから,配列データを普通に関数と受け渡しすると関数中での変更が外の世界にも反映される理由がわかります.だって知らん間にポインタ使うてて,値を渡すんやのうて番地渡してんねやからね. fuga2.c をさらに書き換えて以下のようにしてみればはっきりします.
● プログラム例 fuga3.c
1 void fuga(int x[], int n); 2 3 main() 4 { 5 int a[10]; 6 int n = 3, i; 7 8 for(i = 0; i < n; i++){ 9 *(a + i) = 2*i + 1; 10 printf("%d %d\n", i, *(a + i)); 11 } 12 printf("-----\n"); 13 14 fuga(a, n); 15 16 for(i = 0; i < n; i++){ 17 printf("%d %d\n", i, a[i]); 18 } 19 } 20 --> 21 void fuga(int *x, int n) 22 { 23 int i; 24 --> 25 for(i = 0; i < n; i++) *(x + i) *= 3; --> 26 //for(i = 0; i < n; i++) x[i] *= 3; 27 }
1. 関数の宣言(1行目と21行目)で int x[] と書いても int *x と書いても同じ 2. 25行目と26行目も同じこと
工事中.HTML版へどうぞ
以下の表を埋めましょう.
宣言の仕方 | 変数値(注) | アドレスの参照 | |
int型の普通の変数 x | int x; | x | |
int型変数へのポインタ変数 y |
注: ポインタ変数の場合には,「ポインタ変数の指す先の変数値」という意味として考えましょう
int x; scanf("%d", &x); printf("%d", x);
● 関数とポインタ篇
を答えなさい.
#include <stdio.h> void Hoge(int x, int *y) { ほげほげ: 「xの値」と「yが指す変数値」をかけたものを「yが指す変数値」に代入する. } int main() { int a, b; for(a = 0; a < 10; a++){ b = a + 1; ほげほげ: 関数Hoge()を呼び出す.引数は,aとbのアドレス. printf("%d %d\n", a, b); } return 0; }
● 配列とポインタ篇
以下のプログラムを実行したときの結果を答えなさい.
#include <stdio.h> int main() { int x[10], *p, *q, i; for(i = 0; i < 10; i++) x[i] = i; p = x; q = &x[1]; for(i = 0; i < 5; i++){ printf("%d %d %d\n", i, *p, *(q + 2 * i)); p = p + 2; } return 0; }
以下のプログラムを実行したときの結果を答えなさい.
#include <stdio.h> void Print3(int *a) { printf("%d %d %d\n", *a, *(a + 1), *a + 5); } int main() { int x[10], *p, i; for(i = 0; i < 10; i++) x[i] = i; p = &x[6]; Print3(x); Print3(&x[5]); Print3(++p); return 0; }