ポインタと参照の基礎

ポインタの基本演算子

C++におけるポインタには2つの主要な演算子があります:

*(間接参照演算子):ポインタに適用すると、そのポインタが指すアドレスに格納されている値を取得します。

&(アドレス演算子):変数に適用すると、その変数のメモリアドレスを取得します。

ポインタ自体も変数であるため、ポインタのアドレスを取得することも可能です。このアドレスは二重ポインタに格納されますが、多次元のケースは複雑になるため、通常のプログラミングではあまり頻繁には遭遇しません。

ポインタの宣言

ポインタを宣言する際、任意の型を指定できます。アスタリスク(*)の両側のスペースは任意ですが、各ポインタ変数名にはアスタリスクが必要です。

int *ptr_num;
char* ptr_char;
double*ptr_double;

最初の宣言は、*ptr_numの型がintであることを示しています。間接参照演算子はポインタに使用されるため、変数ptr_num自体はポインタでなければなりません。口頭や文書では、「ptr_numはint型を指す」と表現します。

ポインタ宣言の実例

以下の例では、メモリアドレスが100から始まるものとします(便宜上、10進数で表記していますが、実際のC++プログラムでアドレス値を出力すると16進数で表示されます)。

// メモリアドレス: |value1 100|value2 104|ptr1 108|ptr2 112|
int value1 = 123;
double value2 = 3.14;
int *ptr1 = &value1;
double* ptr2 = &value2;
cout << value1 << " " << value2 << " " << ptr1 << " " << ptr2 << endl;

このコードは以下を出力します:
123 3.14 100 104

配列とポインタの関係

int data_array[10];

上記の宣言では、data_array[4]は整数型(int)ですが、data_array自体の型は何でしょうか?また、それは何を表しているのでしょうか?

ほとんどの場合、配列名(上記のdata_array)の値はポインタ定数であり、配列の最初の要素のアドレスを表します。上記の例では、data_arrayの型は「intへの定数ポインタ」です。配列が他の型の場合、配列名の型は「その型への定数ポインタ」となります。

この値はポインタ変数ではなくポインタ定数であることに注意してください。完全な配列要素の開始位置を示すポインタは、簡単には変更できません。配列全体を簡単に移動させることはできないためです。

配列の添字参照式

*(data_array + i) = data_array[i]

これら2つの式、間接参照と添字参照は等価です。実際、プログラム文で記述する際には、演算子の優先順位に若干の違いがあります。

int numbers[10];
int *ptr_num = numbers + 2;

以下のptr_numを含む各式について、numbersを使用した等価な式を考えてみましょう:

  • ptr_num:明らかに、numbers+2&numbers[2]が等価です。
  • *ptr_num:間接参照演算子はポインタが指す位置にアクセスするため、答えはnumbers[2]です。*(numbers+2)も等価です。
  • ptr_num[0]ptr_numが配列ではないのでこの操作はできないと考えるかもしれませんが、それは他のプログラミング言語の思考惯性です。C++の添字参照は間接参照式と同じです。この場合、等価な式は*(ptr_num+(0))となり、0と括弧を除くと前の式と同じ結果になります:numbers[2]
  • ptr_num+6ptr_numnumbers[2]を指している場合、この加算演算によって生成されるポインタは、numbers[2]から6つ先の整数位置にある要素を指します。等価な式はnumbers + 8または&numbers[8]です。
  • *ptr_num+6:注意!ここでの2つの演算子のうち、間接参照が先に実行されます!間接参照の結果が6に加算されるため、この式はnumbers[2]+6と等価です。
  • *(ptr_num+6):上記の例から、この式の値はnumbers[8]であることがわかります。
  • &ptr_num:この式は合法でコンパイルを通過しますが、その結果はnumbersとは関係ありません。コンパイラがptr_numnumbersに対してどのメモリ位置に配置するかは予測できません。
  • ptr_num[-1]:「負の添字はエラーになる」という一般的な考えはここでは直接適用できません。まず間接参照式に変換する必要があります:*(numbers + 2 - 1)。これにより、答えはnumbers[1]となります。
  • ptr_num[9]:元の配列numbersは10個の要素しかないため、範囲外エラーが発生します。
  • 2[numbers]:驚くかもしれませんが、この式は合法で、その値はnumbers[2]です!ここでの加法は交換法則を満たすため、a+i = i+aが成り立ちます。

ポインタへのポインタ

int value = 12, *ptr_value = &value, **ptr_ptr = &ptr_value;

上記のコードでは、ptr_ptrの型は明らかにポインタです。

しかし、ptr_valueは「intへのポインタ」であるため、ptr_ptrは「intへのポインタ」へのポインタです。

正確には、ここでのptr_ptrはポインタへのポインタであり、このような定義は合法です。ポインタへのポインタも間接参照*操作を行うことができます。

***ptr_ptrの場合、*演算子は右から左への結合性を持つため、この式は*(*(*ptr_ptr))と等価です。

以下の各行では、式とそれと等価な意味を持つ式を示しています。

  • value:12と等価です。
  • ptr_value&valueと等価です。
  • *ptr_valuevalue、12と等価です。
  • ptr_ptr&ptr_valueと等価です。
  • ***ptr_ptr*ptr_valuevalue、12と等価です。

すでに*(data_array + i) = data_array[i]という記法が導入されていることに注意してください。ここでのdata_arrayは配列の最初の要素を指すポインタで、int*/int[]型であり、iは位置添字でint型です。

両者は加法だけでなく減法も行うことができますが、配列の場合、「加減法」が表す配列添字の移動が範囲外にならないように注意する必要があります。

注意:C++の基本型はコンピュータ内で実際に占めるビット数が異なりますが、ポインタの「加減法」が表すアドレスのメモリ内での移動は、ポインタが指すデータ型のビット数を考慮して計算されます。

long long big_array[10], *ptr_big = big_array+1; // big_array[1]
char chars[5], *ptr_char = chars+2; // chars[2]

タグ: C++ ポインタ メモリ管理 配列 参照

6月9日 00:28 投稿