ポインタ(2)

[PDF版] [関連課題]

ポインタについてのおさらい

ポインタとは、「なにかをポイント(指し示す)するもの」という意味です。 つまり、どこにあるかを示すもので、その実体はアドレスである、というは前 回説明しました。計算機のメモリというのは、順番に番号がついていて(アド レスがついていて)、そこに変数や配列(そして、プログラムも)が格納され ています。アドレスとはメモリ中のどこにあるかを示す番号で、それがポイン タなのです。このアドレスを扱えるのが、C言語で効率的なプログラムが書け る秘訣です。

ポインタ変数とはポインタが格納されている変数で、そのポインタが指すデー タのデータ型に*をつけて宣言します。例えば、整数intのデータを指し示す ポインタ変数は、以下のように宣言します。

int *p;
このポインタで指し示す整数(このポインタは整数を指すと宣言しているので すから、さしている先の値は整数です)を得るには、*演算を使います。
x = *p;
掛け算*と違うことを注意してください。この*はポインタにつけたときに参照 の意味になります。なので、
*(ポインタの値)
は、ポインタの指す先の値を 意味します。変数のアドレス、つまり、変数へのポインタを得るには&演算子 を使います。例えば、ポインタ変数pに変数yへのポインタを格納するには、 以下のようにします。
int y; 
… 
p = &y;
*演算子は、代入側に使うとポインタで指されているところに値を格納すると いう意味になります。
*p = 123;
pは、yへのポインタなので、pの指されている先、つまり、yに123が格納されることになります。

ポインタについての演算

配列の配列名は、配列の先頭のアドレスの定数であると、前回の最後に説明し ました。例えば、

int A[100];
ではAは、100個の整数が格納されているメモリの(先頭の)アドレスを示し ます。なので、Aは実はintへのポインタなのです。ですから、pに代入できま す。
p = A;
で、配列の3番目の要素を参照するには、A[2]と書きました。同じように、p にAが代入されているのですから、
z = p[2];
とすれば、z = A[2]; と同じ意味になります。

ポインタには整数を足すことができます。1を足すと、ポインタがさしている データの次の要素をさすポインタの値になります。アドレスでいうと、今の場 合、ポインタは整数をさしているので、次の整数のデータ、すなわち4(32 ビット)を足した値になるわけです。アドレスで考えると、アドレスが+1では なくて、指しているデータ型のバイト分だけ加算されることを注意してくださ い。

そこで、*演算子を使ってデータを参照すると、次のデータを参照することに なります。p[2]はpの2個先(3個目)のデータを参照することなので、実は、 p+2の値を参照することと同じです。なので、上の文は、以下のように書くこ ともできます。

z = *(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[]) 
{ 
    … 関数定義の本体 …
}
この関数を呼び出すときには、
int A[100];  
… 
foo(A);
とします。実は、関数パラメータの定義は、以下のようにポインタでもいいのです。
 void foo(int *a) 
{ 
   … 関数定義の本体 …
}
Aは、配列Aへのポインタであると説明しました。つまり、Aは整数のポイン タなので、その引数を参照する関数のパラメータの宣言は整数のポインターと して宣言してもいいのです。ポインタと宣言しても、関数の本体ではa[i]と配 列と同じように扱うことができるのは、前に説明したとおりです。

ポインタとデータ型、ポインタ配列

intやdouble, char, floatなどはどのような種類のデータかということを示す もので、データ型(data type)と呼びます。 特に、このような基本的な数値 に関するデータ型を基本データ型(basic data type)といいます。変数や配 列の宣言は、以下のようなものでした。

  データ型 変数名、変数名、…; 
または、 
データ型 配列名[配列サイズ][…];

ポインタもデータ型の一つです。つまり、int *は 「整数型データへのポインタ」というデータ型です。なので、ポインタ変数、整数へのポインタの変数は、 int *p; と宣言するわけです。これを配列に適用すると、整数へのポインタ を格納している配列というのは、以下のように宣言することができます。

int *AP[100];
ここで、3番目のポインタで指される配列の4番目の要素は、
AP[2][3]
で参照されます。参照の書き方だけを見ると2次元配列と同じですが、APの 3番目のポインタを取り出して、そのポインタで指されている配列(メモリ領 域)の4番目を参照するという意味になることを注意してください。それに対 し、2次元配列int A[10][10]では、A[2][3]はAから始まる領域の2*10+3番 目の要素を参照しています。

文字列とは、文字の配列であると説明してきました。しかし、実際はメモリ上 にある文字の並びなのです。したがって、文字列というデータ型は文字(の並 び)へのポインタとして扱われることがあります。例えば、文字列の配列は、

char *s[…]; 
と宣言されます。このようにすることによって、格納されてい る文字列の順番を入れ替えたりするときには、ポインタだけを入れ替えればい いので、便利です。

ポインタのデータ型は、指し示すデータ型の後に*をつけたものになります。 したがって、整数へのポインタへのポインタのデータ型を持つ変数は、

int **pp;
と宣言できます。