ポインタ(2)
[PDF版]
[関連課題]
ポインタについてのおさらい
ポインタとは、「なにかをポイント(指し示す)するもの」という意味です。
つまり、どこにあるかを示すもので、その実体はアドレスである、というは前
回説明しました。計算機のメモリというのは、順番に番号がついていて(アド
レスがついていて)、そこに変数や配列(そして、プログラムも)が格納され
ています。アドレスとはメモリ中のどこにあるかを示す番号で、それがポイン
タなのです。このアドレスを扱えるのが、C言語で効率的なプログラムが書け
る秘訣です。
ポインタ変数とはポインタが格納されている変数で、そのポインタが指すデー
タのデータ型に*をつけて宣言します。例えば、整数intのデータを指し示す
ポインタ変数は、以下のように宣言します。
このポインタで指し示す整数(このポインタは整数を指すと宣言しているので
すから、さしている先の値は整数です)を得るには、*演算を使います。
掛け算*と違うことを注意してください。この*はポインタにつけたときに参照
の意味になります。なので、
は、ポインタの指す先の値を
意味します。変数のアドレス、つまり、変数へのポインタを得るには&演算子
を使います。例えば、ポインタ変数pに変数yへのポインタを格納するには、
以下のようにします。
*演算子は、代入側に使うとポインタで指されているところに値を格納すると
いう意味になります。
pは、yへのポインタなので、pの指されている先、つまり、yに123が格納されることになります。
ポインタについての演算
配列の配列名は、配列の先頭のアドレスの定数であると、前回の最後に説明し
ました。例えば、
ではAは、100個の整数が格納されているメモリの(先頭の)アドレスを示し
ます。なので、Aは実はintへのポインタなのです。ですから、pに代入できま
す。
で、配列の3番目の要素を参照するには、A[2]と書きました。同じように、p
にAが代入されているのですから、
とすれば、z = A[2]; と同じ意味になります。
ポインタには整数を足すことができます。1を足すと、ポインタがさしている
データの次の要素をさすポインタの値になります。アドレスでいうと、今の場
合、ポインタは整数をさしているので、次の整数のデータ、すなわち4(32
ビット)を足した値になるわけです。アドレスで考えると、アドレスが+1では
なくて、指しているデータ型のバイト分だけ加算されることを注意してくださ
い。
そこで、*演算子を使ってデータを参照すると、次のデータを参照することに
なります。p[2]はpの2個先(3個目)のデータを参照することなので、実は、
p+2の値を参照することと同じです。なので、上の文は、以下のように書くこ
ともできます。
もちろん、pとAは同じであれば、A[2]も*(A+2)と書くことと同じです。この演算の例として、文字列の小文字を大文字に変換する例を考えて見ましょう。
void toupper(char s[])
{
int i;
for(i = 0; s[i] != ‘\0’; i++)
if(s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 'a' + 'A';
}
|
上が、配列で書いたもので、下がポインタで書いた例です。
void toupper(char s[]){
char *p;
for(p = s; *p != ‘\0’; p++)
if(*p >= 'a' && *p <= 'z') *p = *p -'a'+'A';
}
|
文字列は文字の配列ですから、文字へのポインタ変数pを加算しながら、次の
文字に文字列の最後の'\0'まで、アクセスしています。
では、引き算はどうでしょうか。これは、負の値を足す場合も同じですが、ポ
インタがさしているデータの前(つまり、アドレスが小さいほう)のデータへ
のポインタとなります。(配列で書いた例と比較してみてください)
ポインタ同士の引き算もできます。その場合には指しているデータの単位で何個はなれているかを計算します。
int *p,*q;
…
p = &A[2];
q = &A[10];
i = q - p;
|
&演算子は変数以外でも、何か値を参照する式の前につけることで、その参照
するところへのポインタを得ることができます。なお、例では配列の要素A[2]
へのポインタとA[10]へのポインタを引き算するとi = 8になります。
配列のパラメータとポインター変数
配列のパラメータの宣言は、foo(int a[])とかけると以前説明しました。例え
ば、配列の引数を持つ関数の定義は、以下のようになります。
void foo(int a[])
{
… 関数定義の本体 …
}
|
この関数を呼び出すときには、
とします。実は、関数パラメータの定義は、以下のようにポインタでもいいのです。
void foo(int *a)
{
… 関数定義の本体 …
}
|
Aは、配列Aへのポインタであると説明しました。つまり、Aは整数のポイン
タなので、その引数を参照する関数のパラメータの宣言は整数のポインターと
して宣言してもいいのです。ポインタと宣言しても、関数の本体ではa[i]と配
列と同じように扱うことができるのは、前に説明したとおりです。
ポインタとデータ型、ポインタ配列
intやdouble, char, floatなどはどのような種類のデータかということを示す
もので、データ型(data type)と呼びます。
特に、このような基本的な数値
に関するデータ型を基本データ型(basic data type)といいます。変数や配
列の宣言は、以下のようなものでした。
または、
ポインタもデータ型の一つです。つまり、int *は
「整数型データへのポインタ」というデータ型です。なので、ポインタ変数、整数へのポインタの変数は、
int *p; と宣言するわけです。これを配列に適用すると、整数へのポインタ
を格納している配列というのは、以下のように宣言することができます。
ここで、3番目のポインタで指される配列の4番目の要素は、
で参照されます。参照の書き方だけを見ると2次元配列と同じですが、APの
3番目のポインタを取り出して、そのポインタで指されている配列(メモリ領
域)の4番目を参照するという意味になることを注意してください。それに対
し、2次元配列int A[10][10]では、A[2][3]はAから始まる領域の2*10+3番
目の要素を参照しています。
文字列とは、文字の配列であると説明してきました。しかし、実際はメモリ上
にある文字の並びなのです。したがって、文字列というデータ型は文字(の並
び)へのポインタとして扱われることがあります。例えば、文字列の配列は、
と宣言されます。このようにすることによって、格納されてい
る文字列の順番を入れ替えたりするときには、ポインタだけを入れ替えればい
いので、便利です。
ポインタのデータ型は、指し示すデータ型の後に*をつけたものになります。
したがって、整数へのポインタへのポインタのデータ型を持つ変数は、
と宣言できます。