16
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-07-04

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

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

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

#基本

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

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

pointer.initialization.nullptr
int *p = nullptr;

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

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

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

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

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

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 と代入されます。

initialization_vs_assignment
int initializedVariable = 24;

int notInitializedVariable;
notInitializedVariable = 24;

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

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

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

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

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

pointer.declaration.bad
int* p = nullptr;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 のメンバ関数をアクセスしています。

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

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

#基本の斜め上を行く

##const とポインタ

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

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

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

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

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

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

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

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

###const なポインタ

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

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

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

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

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 型の p1constints なので、int 型オブジェクトを指す const なポインタとなります。

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

##配列とポインタ

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

###ポインタの配列

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

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

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

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

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

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

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

#続く!

16
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?