ここまでは、ほとんど数学と同じ感覚でプログラムを書いてきた。 ここで、次にポインタ (pointer) を学ぶため、コンピュータのしくみを少しだけ説明する。
コンピュータの世界は 0 と 1 だけからなると言われるが、 その、0 か 1 かの情報を収める単位を 1 ビット (bit) という。 ふだんは 1 ビットずつ扱うことはなく、 何ビットかをまとめた 1 バイト (byte) という単位でデータを扱う。 1 バイトはたいてい 8 ビットである。(例外もあるらしい。)
以下、8 ビットを 1 バイトと仮定する。 1 バイトには、二進法で書いて 0000 0000 から 1111 1111 までの数を収めることができる。 1111 1111 は十進法では 255 である。
いま、8 ビットを 4 ビットずつに分けて書いたのには意味がある。 二進法は表記が長くなりすぎて使いづらい。 4 ビットをまとめると 24 = 16 だから十六進法となる。 十六進法では、0 から 9 までの数字に加えて、十から十五までを表す数字が必要である。 それには、a から f までのアルファベットを使う。
|
|
この表を見ると、たとえば二進の 1011 0110 は十六進では b6, 十進に直すと 11 * 16 + 6 = 182 とわかる。 このあたりの換算は、Windows の電卓を「プログラマー」モードで使うと簡単である。
コンピュータの中には、1 バイトの情報を収めることのできる「メモリ (memory)」 が並んでいる。 それらには、0, 1, 2, ... と、番号がついている。その番号を「アドレス (address)」 という。 「番地」と思うとわかりやすいかもしれない。
1 バイトでは、0 から 255 までの数しか収められないから、実地の計算の役に立たない。 そこで、何バイトかをまとめて、そこに整数を格納するしくみになっている。 現在の多くのパソコンでは 4 バイトを使って整数を格納する。
いま使っている int 型もそうで、mod 232 = 4294967296 で計算をしている。 代入できる値は -231 = -2147483648 から 231 - 1 = 2147483647 までである。
変数名の前に演算子「&」をつけると、 その変数の値ではなく、その変数の収められたアドレスの意味になる。
printf() でアドレスを出力するときは、%p と書く。 この gcc では、十六進で表記される。
演算子 sizeof のうしろに小かっこに入れて型の名を書くと、 その型が何バイトを占めるか、になる。
#include <stdio.h> int main() { int m, n, i; double x, y; int a[10]; printf("int 型は %d バイトです.\n", sizeof(int)); printf("m のアドレスは %p です.\n", &m); printf("n のアドレスは %p です.\n", &n); putchar('\n'); printf("double 型は %d バイトです.\n", sizeof(double)); printf("x のアドレスは %p です.\n", &x); printf("y のアドレスは %p です.\n", &y); putchar('\n'); for (i = 0; i < 10; i++) { printf("a[%d] のアドレスは %p です.\n", i, &a[i]); } }
アドレスがわかるとどんな使い道があるか、はこのあと述べる。
「ポインタ (pointer)」とは、いままで出てきた変数とは異なり、 変数のアドレスを格納する変数である。
ポインタには、どの型の変数のアドレスを格納するかによって、 int 型へのポインタ、double 型へのポインタ、などの区別がある。
変数 p が int 型へのポインタであることの宣言は、 int *p; としておこなう。 int 型へのポインタを二つ以上宣言するときには int *p, *q; のようにする。 ここでは、int 型変数 x, y に加えて、 int 型へのポインタ p, q をも宣言している。
#include <stdio.h> int main() { int x, y; int *p, *q; p = &x; q = &y; printf("x のアドレスは %p です.\n", &x); printf("y のアドレスは %p です.\n", &y); printf("x のアドレスは %p です.\n", p); printf("y のアドレスは %p です.\n", q); }
「p = &x;」の & は、次にくる変数のアドレスを示す演算子であった。 よって、この文が実行されると、変数 x に格納されている値ではなく、 x のアドレスが p に格納される。 慣れるまでは「x の番地が p にはいった」と思うとわかりやすい。 次の「q = &y;」も同様である。
関数 printf() でアドレスを出力させるには %p と書くのだった。 p と &x とは同じだから、 三つめ・四つめの printf() 文もわかるであろう。
アドレスやポインタが何の役に立つのか、の説明をまだしていないが、 とりあえず、次のプログラムを見よ。これも役に立たないプログラムである。
#include <stdio.h> int main() { int x, y; int *p, *q; x = 10; y = 20; printf("(1) x = %d, y = %d.\n", x, y); p = &x; q = &y; *p = 30; *q = *p; printf("(2) x = %d, y = %d.\n", x, y); q = p; *q = 100; printf("(3) x = %d, y = %d.\n", x, y); }
「*p = 30;」が新しい。 この「*」は、ポインタの前につく演算子で、 「*p」は「ポインタ p に格納されているアドレスに割り当てられている変数」 を意味する。 「ポインタ p がさす変数」と言うこともある。 ここでは、p には x のアドレスがはいっているので、 x に 30 が代入される。 次の「*q = *p」では、 y に x の値がコピーされることがわかるだろうか?
その先の「q = p;」について。 ポインタは変数であるから、このように代入が可能である。 これで q は x をさすようになったから、 次の「*q = 100;」では x に 100 が代入される。
※ ここで説明した「*」と、 ポインタの宣言のときの「int *p;」の「*」との間に関連をつけることもできるそうだが、 別々に理解しておけばよいだろう。
※ 「&」と「*」とは互いに逆の演算子である。 よって次のように書けるが、こう書くことはまずない。
#include <stdio.h> int main() { int x; int *p; x = 10; printf("x は %d, %d.\n", x, *&x); p = &x; printf("p は %p, %p.\n", p, &*p); }
※ 「100 番地には何がはいっているだろう?」と思って、 「p = 100; printf("%d\n", *p);」などと書いてはいけない。 ポインタに代入できるのは、とりあえずは、実在する変数のアドレスのみ、 と思っておくのがよい。
関数の自作について不安のある者は 211022.html で復習せよ。
ツルカメ算を解く関数を書いてみよう。 ツルの数とカメの数の二つを返す関数は書けないので、次のように書く。
※ 「ツ」を cu とするのは新日本式ローマ字である。「チチャチュチョ」は ci cya cyu cyo とする。
関数 void curukame(int c1, int c2, int *p, int *q) は、 ツルとカメが合わせて c1 匹、 足の数の合計が c2 本のとき、 ツルの数を *p に、カメの数を *q に入れるものである。
呼び出すときは、curukame(a, b, &x, &y); のようにする。 「合わせて a 匹、 足の数が b 本のとき、 ツルの数を &x “番地”に、 カメの数を &y “番地”に入れてくれ」というのである。 仮に、の話だが、もしも x が 100 番地、y が 104 番地なら、 「100 番地と 104 番地に答えを入れてくれ」と呼び出す。
すると、関数 curukame() の側では、 「100 番地と 104 番地に答えを入れればいいのだな」とわかる仕組みである。
#include <stdio.h> void curukame(int c1, int c2, int *p, int *q); int main() { int a, b, x, y; a = 5; b = 16; printf("ツルカメ合わせて %d 匹、足は %d 本のとき、", a, b); curukame(a, b, &x, &y); printf("ツルは %d 匹、カメは %d 匹です.\n", x, y); } /* 全部で c1 匹、足が c2 本のとき、ツルの数を *p に、カメの数を *q に入れる */ void curukame(int c1, int c2, int *p, int *q) { *p = (4 * c1 - c2) / 2; *q = (c2 - 2 * c1) / 2; }
もう一つの書き方は、「大域変数」を使う方法である。 大域変数とは、関数の外で宣言される変数で、 プログラムのどこからでもアクセスできる。
#include <stdio.h> void curukame(int c1, int c2); int curu, kame; /* 大域変数 */ int main() { int a, b; a = 5; b = 16; printf("ツルカメ合わせて %d 匹、足は %d 本のとき、", a, b); curukame(a, b); printf("ツルは %d 匹、カメは %d 匹です.\n", curu, kame); } /* 全部で c1 匹、足が c2 本のとき、ツルの数を curu に、カメの数を kame に入れる */ void curukame(int c1, int c2) { curu = (4 * c1 - c2) / 2; kame = (c2 - 2 * c1) / 2; }
どちらがよいかは、一概には言えない。
また、この程度の計算なら、独立した関数にするまでもない。これはあくまでも見本である。
int 型変数 x, y の値を入れかえる関数は、 void swap(int x, int y) としては書けない。 void swap(int *p, int *q) として書いて、 呼び出す際に swap(&x, &y) とする。書いてみよ。 (もちろん、main() も書くのである。)