C++: ポインタについて

中村一義の「銀河鉄道より」の最初が完全にベートーヴェンの交響曲第7番なんですが…

#概要
簡単に C++ でのポインタについて書きます。
C++11 を標準としています。
ここにある情報は、内容が正しくても表現が間違っている可能性があります。気付き次第指摘願います。

#ポインタとは何か
ポインタは英語で pointer、「指指す者」という意味です。
オブジェクト A へのポインタとは、メモリ上に A が存在している領域を値とするオブジェクトです。
簡単に言うと、オブジェクト A へのポインタは、A を指し示すモノです。

#基本

##オブジェクトを指さないポインタの初期化

下のコードは、`p` という名の、`int` 型オブジェクトを指すポインタを初期化します。`p` はオブジェクトに指すように指定したくないので、`nullptr` と初期化します。

```cpp:pointer.initialization.nullptr
int *p = nullptr;
```

下のコードは、3 行すべてが `int` 型オブジェクトを指すポインタを初期化します。3 つのポインタはどれもオブジェクトを指していません。

```cpp:pointer.initialization.null_alternatives
int *p0 = nullptr;
int *p1 = 0;
int *p2 = NULL;
```

`nullptr` も `NULL` も実際は `0` なのです。与えられたポインタが何らかのオブジェクトを指しているか否かを確かめたい時に、`if` 文を使えば簡単に書けます。

下のコードでは、`p` を `if` 文の条件としています。ポインタの値は数字なので、ポインタである `p` が何も指さず、値が `0` であれば、`if` 文の条件としてブーリアン値 `false` に変換されます。逆に、`p` が何かを指していた場合、値は `0` 以外の数字です。`0` でない整数をブーリアン値に変換すれば、`true` となります。

```cpp:pointer.check_pointer_pointing
// p というポインタが既に宣言されています。
if (p) {
    // p は何らかのオブジェクトを指している様です。
    std::cout << "p points to something!" << std::endl;
} else {
    // p は何のオブジェクトも指していない様です。
    std::cout << "p points to nothing." << std::endl;
}
```

##オブジェクトを指すポインタの初期化

下のコードは、`int` 型変数 `x` を `12` と初期化し、`int` 型オブジェクトを指すポインタ `p` を、`x` を指すように初期化します。コード実行後、`p` の値は `x` がメモリに保存されている位置です。

```cpp:pointer.point_to
int x = 12;
int *p = &x;
```

`&x` のうちの `&` は**アドレス演算子**といい、オブジェクトがメモリにある位置を返します。
ここでは、`x` がメモリに保存されている位置を返します。

ここで 3 つ注意すべき点があります。

* * *

**注意** アドレス演算子としての `&` は、参照を宣言する時に使う `&` とは違います。簡単に説明すると、**「あれはあれ、これはこれ」**です。ポインタを宣言する時に `*` を使っていますが、これは後に紹介する間接演算子としての `*` とも、乗算演算子の `*` ともまた違います。減算演算子としての `-` と、負数演算子としての `-` が違うのと同じ訳です。見た目は同じだけど、意味が違うのです。

* * *

**注意** ポインタは**初期化してから使うこと**が常識です。
初期化せずにポインタが指すメモリ領域をアクセスしようとすると、undefined、すなわち「未定義」となり、コードが正しく動作しなくなります。
そのうえ、コンパイラは一連のエラーが、「あるポインタが初期化せずに使われた」という原因により引き起こされたことに気づけない場合があるので、デバッグが難しくなります。
しかし、時代は先進し、最近のコンパイラには初期化されていないポインタがあるか否かを確認し警告を出すフラグがあるようです。

* * *

**注意** C++ において、初期化（initialization）と代入（assignment）は違います。
変数 A を値 V に初期化するということは、A を宣言する文と同じ文で V を A の値とすることです。
代入はいつでもどこでも変数の値を書き換えれば済むことです。

下のコードでは、`initializedVariable` は宣言と同時に `24` に初期化されます。
`notInitializedVariable` は初期化されず、宣言文の次の文で `24` と代入されます。

```cpp:initialization_vs_assignment
int initializedVariable = 24;

int notInitializedVariable;
notInitializedVariable = 24;
```

* * *

##ポインタの宣言文について

同じ型の変数とその型のオブジェクトを指すポインタの宣言文はまとめて一行で書くことができます。

下のコードは、`int` 型変数 `i0` を `12` と初期化し、`int` 型オブジェクトを指すポインタ `p0` を `i0`　を指すように初期化します。

```cpp:pointer.declaration.mixed
int i0 = 12, *p0 = &i0;
```

下のコードは、`int` 型変数へのポインタ `p` を初期化します。`*` が識別子（変数の名前、つまり `p`）から離れていますが、コードとしては動作します。

```cpp:pointer.declaration.bad
int* p = nullptr;
```

まるで `int*` というポインタを型にしたようなこの書き方はあまりおすすめ出来ません。何故なら、こんな書き方も出来ますから。

```cpp:pointer.declaration.really_bad
int* p0 = nullptr, i0 = 12, *p1 = &i0;
```

`p0` と `p1` は `int` 型オブジェクトを指すポインタで、`i0` は `int` 型変数です。一見、`i0` がまるで `int*` 型変数のように見えます。

このような書き方は忌み嫌われるべきであると、僕は個人的に思います。

##ポインタへのポインタ

ポインタは立派なオブジェクトです。よって、ポインタを指すポインタもあり得ます。

「本当に使うの？」どうでしょうかね。

下のコードでは、`int` 型変数 `x`、`int` 型オブジェクトを指すポインタ `p`、`int` 型オブジェクトを指すポインタを指すポインタ `pp` があります。

```cpp:pointer.double_pointer
int x = 1;
int *p = &x;
int **pp = &p;
```

ちなみに、ポインタへの参照も定義出来ます。

下のコードでは、`r` は `int` 型オブジェクトを指すポインタ `p` への参照に初期化されます。

```cpp:pointer.reference_to_pointer
int *p = nullptr, *&r = p;
```

##ポインタが指すオブジェクトへのアクセス

オブジェクトを指すことがポインタの存在意義です。**間接演算子** `*` を使う事によって、ポインタが指すオブジェクトをアクセスできます。

下のコードの 1 行目では、`int` 型オブジェクトを指すポインタ `p` は `int` 型変数 `x` を指します。

```cpp:pointer.dereferencing
int x = 12, *p = &x;
*p++;
int a = *p + 3;
```

2 行目では、`p` が指すオブジェクトの値に 1 を足します。`*p` の `*` は**間接演算子**といい、ポインタが指すオブジェクトへの**参照**を返します。ここでは、`p` が指すオブジェクト、つまり `x` への参照を返します。

間接演算子については、参照を返すということが大事です。もし `*p` が返すのは左辺値である参照ではなく、ただの右辺値であれば、`*p++` のコードは `x` の値に何も影響を与えません。

**直接メンバ演算子** `.` を使ってオブジェクトのメンバにアクセス出来ます。**間接メンバ演算子** `->` を使えば、ポインタが指すオブジェクトのメンバにアクセス出来ます。

下のコードは `Programmer` 型の変数 `greekfellows` を指すポインタ `employee` を初期化します。そして、間接メンバ演算子 `->` を使って `employee` が指す `greekfellows` のメンバ関数をアクセスしています。

```cpp:pointer.arrow_operator
// Programmer 型のオブジェクトは quitJob() というメンバ関数があるとします。
Programmer greekfellows, *employee = greekfellows;
employee->quitJob(); // (*employee).quitJob(); と同じ
```

`employee->` と `(*employee).` は全く同じ意味です。`*employee.` と書くとエラーなので注意（`*` の優先順位が `.` より低いので）。

#基本の斜め上を行く

##`const` とポインタ

`const` キーワードを使う事で、変数を定数として定義出来ます。

下のコードの 1 行目では、`int` 型変数 `c` を `12` と初期化し、`c` に再度値を代入することを許しません。よって、2 行目はエラーとなります。

```cpp:pointer.const_variable
const int c = 12;
c = 13; // error: const なオブジェクトに再度値を代入することは出来ません。
```

これが理解できる前提で話を進めていきます。`const` キーワードによって再度値を代入されることを許されなくされたオブジェクトを、**「**`const` **な」**オブジェクトと呼ぶことにします。

`const` なオブジェクトは**必ず初期化されなければなりません。**ポインタもオブジェクトですから、`const` なポインタも同じです。

###`const` オブジェクトを指すポインタ

下のコードでは、`const` な `int` 型変数 `x` は初期化され、`const` な `int` 型オブジェクトを指すポインタ `p` は `x` を指すように初期化されます。よって、3 行目はエラーとなります。

```cpp:pointer.pointer_to_const
const int c = 10;
const int *p = &c;
*p = 11; // error: ポインタ越しでも、const なオブジェクトに再度値を代入することは出来ません。
```

###`const` なポインタ

下のコードでは、`int` 型変数 `x` と `y` は初期化され、`int` 型オブジェクトを指す `const` なポインタ `p` は `x` を指すように初期化されます。3 行目ではあくまで `p` が指すオブジェクトの値を変えているので問題ありませんが、4 行目では `p` が指すオブジェクトを変えている、すなわち `p` の値を変えているので、エラーとなります。

```cpp:pointer.const_pointer
int x = 10, y = 12;
int *const p = &x;
*p++;
p = &y; // error: const なオブジェクトに再度値を代入することは出来ません。ポインタもオブジェクトです。
```

###`const` オブジェクトを指す `const` なポインタ

下のコードでは、`const` な `int` 型変数 `c` と `d` は初期化され、`const` な `int` 型オブジェクトを指す `const` なポインタ `p` は `c` を指すように初期化されます。よって、3 行目も 4 行目もエラーとなります。もっとも、エラーでコンパイラの作業を止める 3 行目が先にある限り、4 行目が実行されることもありませんが。

```cpp:pointer.const_pointer_to_const
const int c = 18, d = 9;
const int *const p = &c;
*p++;
p = &d;
```

##`typedef` とポインタ

`typedef` を使うと、型指定子に別の名前を与えることが出来ます。もちろん、ポインタ型の指定子に名前を与えることも出来ます。

下のコードでは、`int` 型オブジェクトを指すポインタ型指定子を `ints` と名付けています。よって、`ints` 型である `p0` は、`int` 型オブジェクトを指すポインタとなります。
`const ints` 型の `p1` は `const` な `ints` なので、`int` 型オブジェクトを指す `const` なポインタとなります。

```cpp:pointer.typedef
typedef int *ints;
int x = 9;
ints p0 = &x;
const ints p1 = &x;
```

##配列とポインタ

C++ での配列とポインタは限りなく近いモノです。よく知っておかないと自分の書くコードが滅茶苦茶になります（※体験談です）。

###ポインタの配列

下のコードでは、`int` 型オブジェクトを指すポインタを格納する配列 `p` が宣言されます。格納されたポインタは全て（計 2 つ） `nullptr` に初期化されています。

```cpp:pointer.array_of_pointers
int *p[2] {nullptr, nullptr};
```

###配列を指すポインタ

下のコードでは、`int` 型オブジェクトを格納する配列を指すポインタ `q` が宣言されます。`q` は `nullptr` に初期化されています。

```cpp:pointer.pointer_to_array
int (*q)[2] = nullptr;
```

###配列を指すポインタへの参照、ポインタの配列への参照

```cpp:pointer.reference_to_pointer_to_array
int array[2]{0, 1}, *parray[2] {nullptr, nullptr};
int *(&ref)[2] = array;
int &(*ref)[2] = &array;
```

#続く！