今週はC言語のもっともC言語らしいところである,ポインタ変数について取り上げます.
ポインタの前に,前提知識として アドレスについて.
プログラム中の変数や配列などの値は,コンピュータのメインメモリ(Random Access Memory,RAM,ラムと読む)上に記憶されている.
このメモリには,場所を表す「アドレス」という連続した通し番号(整数の値)がついており,変数ごとのアドレスがOS(Operating System, ここではWindows)により管理されている.
たとえば,C言語プログラム中で int a;
と整数の変数を1つ定義すると,整数の値1個を格納する場所が確保され,a という名前を使ってこの場所に値を書き込んだり参照したりすることができる.
下の図ではアドレスは int型の変数 a が,4 バイト分のメモリを割り当てられていることを示す.
機械語では、このアドレスを直接用いて、プログラミングするが、C言語では変数名aによりプログラミングできる。
では,ある変数に割り当てられた場所(メモリのアドレス,上の図では0x0000E008)を調べるには,どうしたらよいか.
また,アドレスを知ることができるという事にどのような利点があるのか.
C言語では,変数の名前の直前に「&」を付けると,変数のアドレスを取り出すことができる.
#include <stdio.h>
void main()
{
int a = 10;
printf("aの 値 は,%d です¥n", a); /* 普通に値を表示 */
printf("aのアドレスは,%p です¥n", &a); /* %p はアドレスを16進数8桁で表示する */
}
この逆,ある「アドレス」に格納されている変数の「値」を調べるには,どうしたらよいだろうか.
変数の置かれているメモリの位置(アドレス)を格納するための変数のことを,ポインタ変数と呼ぶ.
「アドレス」自身はもちろん数値で,演習で用いているPC(32bitCPUと,32bitOSであるWindows XP)では,0 から 232 までの値である.16進数で表すと,0x00000000から,0xFFFFFFFFまでである
参考:1 bit(ビット)= 0 か 1 の値をとる最小単位で,電気信号では電圧の有無に対応.
1 byte(バイト)= 8 bit,28 であるから,0から 255 までの値.
int型は 4 byteのメモリを占有.char型は 1 byte, float,double型はそれぞれ 4 byte, 8 byte.
「ポインタ変数」と「(intなど)普通の(整数型)変数」は,数値を格納する点では同じであるが,その数値の解釈に差異がある
int a=10, b;
これは普通の整数型変数a,bの宣言.
int *p;
ポインタ変数pの宣言.ポインタの指す型を指定し,変数名の前に*(アスタリスク)をつける.
p = &a;
アドレスを格納するポインタ変数pに,int型変数aのアドレスを代入.(数値の代入ではない!)
aのアドレスを取り出すには,変数名の前に&をつける.
b = *p;
ポインタ変数に格納されているアドレスの示す先に格納されている値を取り出すには,ポインタ変数の前に*をつける.
ここではポインタ変数 p の指す変数(ここでは変数 a )の値を,普通の整数型変数 b に代入.b に10が代入される.
#include <stdio.h>
void main()
{
int a; /* 普通の変数の宣言 */
int *p; /* ポインタ変数の宣言 */
a = 3;
printf("aのアドレスは,%p です¥n", &a);
p = &a;
printf("ポインタ変数pの値(=aのアドレス)は,%p です¥n", p);
printf("ポインタ変数pの指している値は,%d です¥n", *p);
p++;
printf("ポインタ変数を一つ増やすと,%p です¥n", p);
}
C言語において配列は,メモリの連続した区間を使うという約束になっている.
このことから,ポインタへの整数の足し算,ポインタ同士の引き算をすることが可能である.ここではポインタを使って,配列の要素を扱ってみよう.
例:
#include <stdio.h>
void main()
{
int a[5] = {10, 20, 30, 40, 50}; /* 配列 */
int b;
int *p; /* ポインタ変数 */
p = &a[0]; /* 配列の先頭のアドレスを代入する */
printf("配列 a の先頭のアドレスは,%p です¥n", p);
printf("はじめに:p の指している値は,%d です¥n", *p);
p += 2; /* ポインタ変数に2を足すと,pは配列の先頭から2つ先を指す=a[2]のアドレス! */
printf("ここでは:p の指している値は,%d です¥n", *p);
b = *(p-1); /* ポインタの指しているアドレスのひとつ前隣の値をbに代入 */
printf("b の中身は,%d です¥n", b);
}
このように,ポインタ変数に足し算,引き算をすることにより,配列の要素に順次アクセスできる.
ポインタに1を足したときに何バイト先に進むかはポインタの指す変数の型により異なるが,ポインタ変数を宣言する時の型指定から自動的に計算される.
#include <stdio.h>
void main()
{
int e[3];
double f[3];
char g[3];
printf("int配列 :%p %p %p¥n", &e[0], &e[1], &e[2]);
printf("double配列 :%p %p %p¥n", &f[0], &f[1], &f[2]);
printf("char配列 :%p %p %p¥n", &g[0], &g[1], &g[2]);
}
実行結果の例(アドレスの値はプログラム実行時にOSが決めるため,必ずしも値がこうなるとは限らない)
0xbffffab4 0xbffffab8 0xbffffabc 0xbffffac0 0xbffffac8 0xbffffad0 0xbffffad8 0xbffffad9 0xbffffada
この結果から,int, double, char, 配列などの各型がメモリ上でどれくらいの大きさかがわかる.
(1)int a, float b, double c, int dに数字をキーボードから入力し,それぞれの値とアドレスを表示するプログラムを作成せよ.
(2)変数a,bに数字をキーボードから入力し,aのアドレスとaにbを代入した時のアドレスが同じになることを以下のプログラムを完成させて確かめよ.
#include<stdio.h>
void main()
{
int a, b;
int *pa;
printf("a = "); scanf("%d", &a);
printf("b = "); scanf("%d", &b);
pa = ???;
printf("ポインタ変数paの値(=aのアドレス)は,???です¥n", ???);
printf("ポインタ変数paの指している値は,%dです¥n", ???);
a = b;
printf("aにbを代入したときのポインタ変数paの値(=aのアドレス)は,???です¥n", ???);
printf("aにbを代入したときのポインタ変数paの指している値は,%dです¥n", ???);
}
(3)整数型配列に初期値として,(10,20,30,40,50)を設定したとき,全ての要素の値を表示するプログラムをポインタを用いて作成せよ.
ヒント:
#include <stdio.h>
void main()
{
int a[5] = {10, 20, 30, 40, 50};
int *p;
int i;
p = ???;
for(i=0; i<5; i++) {
printf(" 配列の %d 番目は???です¥n", ?????? );
}
}
(4)円の半径を渡すと,面積を戻す関数を作成して,面積の値を表示するプログラムを作成せよ.面積は実数であることに注意.
(5)実数xの値を入力し,y=5x5-12x4+3x+1を求める関数を作成して,答えを表示するプログラムを作成せよ.
ただし,xn(nは自然数)の値も,関数を自ら定義して計算すること(pow関数使用不可).
授業終了時までに完成したプログラムをoh-meijiシステムを使って提出すること.
(提出期限を厳守し,提出用の回に提出しないと採点を行わない)