C++
ポインタ
メモリ管理
配列とポインタ
More than 1 year has passed since last update.


アドレス

・変数の値はコンピュータのメモリに保存される。

アドレスは値が格納されているメモリの位置を表す。


変数のアドレスの取得

・変数からは 1.アドレス(address) と 2.実体、値(content, value) を取り出すことができる。

・アドレスを取得するには、アドレス演算子である&を使う。

int num = -99;

// 実体の出力
cout << num << endl; // -99
// アドレスの出力
cout << &num << endl; // 0x00F4


ポインタ変数

ポインタ変数、またはポインタとは、アドレスを保持する変数のこと。

・ ポインタ変数を使うことは、メモリのアドレスを使って別のデータにアクセスする方法の一つ

・ポインタ(変数)の操作は、単なる値の操作よりも、低レベルな操作。

・プログラマは、ポインタ(変数)に格納したいアドレスを見つけて、それを正しく扱わなければならない。


宣言

int *intptr:

・「intptrはint型のアドレスを持つことが出来る」という意味。

※宣言の時、スペースは重要ではない

int * intptr;  // 上と同じ

int* intptr; // 上と同じ


ポインタ変数にアドレスを代入

int *intptr;

intptr = &num;

・ポインタintptrが、変数numのアドレスを指すようになる

例)

int x = 25;  // int変数

int *ptr; // ポインタ変数(int型へのポインタ)

ptr = &x; // ptrにxのアドレスを代入
cout << "x の値は " << x << endl;
cout << "x のアドレスは " << &x << endl;
cout << "x のアドレスは " << ptr << endl;

結果)

x の値は 25

x のアドレスは 0x7e00
x のアドレスは 0x7e00


間接参照

・間接参照演算子(*)は、ポインタを間接参照(デリファレンス)する。

・ポインタが指している値にアクセスできるようになる

int x = 25;

int *intptr = &x;
cout << *intptr << endl; // 25
cout << intptr << endl; // 0x7e00

例)

int x = 25;  // int型の変数

int *ptr; // int型へのポインタ

ptr = &x; // xのアドレスをポインタに代入

cout << x << endl;
cout << *ptr << endl;
cout << &x << endl;
cout << ptr << endl;

// ptrが指す場所に100を代入してみる。
// これは実際にxに100を割り当てる。(デリファレンス)
*ptr = 100;

cout << "値を変更した結果" << endl;
cout << x << endl;
cout << *ptr << endl;
cout << &x << endl;
cout << ptr << endl;

結果)

25

25
0x7e00
0x7e00
値を変更した結果
100
100
0x7e00
0x7e00


関数に情報を渡す


値渡し

・普通の方法。関数内で引数に渡された値の変更ができない。


参照渡し

参照(リファレンス)とは、ある値に対し別名をつける機能。

例)

int jellyDonuts;

getOrder(jellyDonuts);

void getOrder(int &donuts)
{
cout << "何個のドーナツがほしいですか?";
cin >> donuts;
}

・ 関数getOrderの引数donutsに「&」を付けると、呼び出し元の変数jellyDonutsの別名となる。(束縛するとも言う。)

※ 値渡しの場合、呼び出された関数からは呼び出し元の変数は見えない(関数内で値を変更しても、元の値は変化しない)が、別名であればアクセスすることができる


ポインタ渡し

例)

int number = 5;

showValue(&number);

void showValue(int *value)
{
cout << value << endl; // numberのアドレスが表示される
}

・ 関数showValueの引数valueに「*」を付けると、呼び出し元の変数numberを指すポインタ(変数)となる。

・C++は自動的に引数valueに変数numberアドレスを代入する。

※ 値渡しの場合、呼び出された関数からは呼び出し元の変数は見えない(関数内で値を変更しても、元の値は変化しない)が、ポインタであればアクセスすることができる

cf. 値渡し、ポインタ渡し、参照渡しの違い


ポインタ演算


配列とポインタの関係

・配列名を添え字なしで使うと、配列の先頭の要素を指すポインタとなる。


宣言と代入

int vals[] = { 4, 7, 11 };

int *valptr;
valptr = vals; // 明示的に代入している。

・配列名vals配列の先頭の要素のアドレスを持つポインタ定数となっている。


値の取得


vals[i]*(valptr + i) と同じ

// 同じ値が表示される。

cout << vals[1]; // 7が表示される。
cout << *(valptr+1); // 7が表示される。
// 同じ値が表示される。
cout << vals[2]; // 11が表示される。
cout << *(valptr+2); // 11が表示される。


アドレスの取得


&vals[i]valptr + iと同じ

// 同じアドレスが表示される。

cout << &vals[1];
cout << valptr+1;
// 同じアドレスが表示される。
cout << &vals[2];
cout << valptr+2;

valptr + 1とは、(valptrのアドレス) + (1 × intのサイズ)

・つまり、先頭の要素からプラスした場所のアドレス。

*(valptr+1)は、valptr+1の間接参照(デリファレンス)。

例)

static constexpr int NUM_COINS = 5;

double coins[NUM_COINS] = { 0.05, 0.1, 0.25, 0.5, 1.0 };
double *doublePtr; // double型へのポインタ
int count;

// doublePtrに配列coinsのアドレスを代入
doublePtr = coins;

cout << "配列coinsの値です。\n";
for (count = 0; count < NUM_COINS; count++)
cout << doublePtr[count] << " ";

cout << "これも配列coinsの値です。\n";
for (count = 0; count < NUM_COINS; count++)
cout << *(coins + count) << " ";


動的メモリ割り当て


動的メモリ割り当てとは?

プログラム実行中に、変数のための場所を割り当てることができる。

・コンピュータは新しく割り当てられた変数のアドレスを返す。

・メモリを割り当てるには、new演算子を使う。

double *dptr;

dptr = new double;

new はメモリのアドレスを返す


動的配列

newを、配列のサイズを決めるために使うこともできる。

double *arrayPtr;

const int SIZE = 25;
arrayPtr = new double[SIZE];

・配列にアクセスするために、ポインタ演算で見た方法を使うことができる。

for (i = 0; i < SIZE; i++)

arrayPtr[i] = i * i;

or

for (i = 0; i < SIZE; i++)

*(arrayPtr + i) = i * i;

・割り当てるのに十分なメモリがないとき、プログラムは終了する。

※ 静的配列と動的配列

1. 静的配列: コンパイル時に大きさが決まる。

2. 動的配列: 実行時に大きさが決まる。

・静的配列にせよ動的配列にせよ、メモリを確保する手段に過ぎない。

例)

#include <new>

int main(){
int arr[5] = {};//自動変数領域にint型で要素数5つ分の連続した領域を確保し、0初期化
arr[0] = 2;//ポインタ演算で0番目の要素にアクセスし2を代入
int* d_arr = nullptr;
try{
d_arr = new int[5];//int型で要素数5つ分の連続した領域を動的に確保、失敗したら例外が飛ぶ
d_arr[0] = 2;//ポインタ演算で0番目の要素にアクセスし2を代入
delete[] d_arr;
}catch(...){
delete[] d_arr;
}
if(int* d_arr2 = new (std::nothrow) int[5]){//int型で要素数5つ分の連続した領域を動的に確保、失敗したらNULLポインタが帰る
d_arr[0] = 2;//ポインタ演算で0番目の要素にアクセスし2を代入
delete[] d_arr;
}
}


動的なメモリの解放

・動的なメモリを開放するにはdeleteを使う。

・動的な配列を開放するには、[]を使う。

deleteを使うのは、動的メモリに対してのみ。

例)

# 売り上げの平均値を持つ配列を動的に割り当てる

double *sales, total = 0.0, average;

int numDays, count; // 売り上げ日数となる変数。

// 売り上げ日数を得る。
cout << "何日間の売り上げを調べますか? ";
cin >> numDays;

// 売り上げ日数を確保できるような十分大きな配列を、動的に割り当てる。
sales = new double[numDays];

// 毎日の売り上げを得る。
cout << "売り上げを入力してください。\n";
for (count = 0; count < numDays; count++)
{
cout << (count + 1) << "日目" << " * ";
cin >> sales[count];
}

// 売り上げの合計を計算する。
for (count = 0; count < numDays; count++)
{
total +- sales[count];
}

// 1日の売り上げの平均を計算する。
average = total / numDays;

// 結果を表示する。
cout << fixed << showpoint << setprecision(2);
cout << "\n\n合計: $" << total << endl;
cout << "売り上げ平均: $" << average << endl;

// 動的に割り当てられたメモリを開放する。
delete [] sales;
sales = nullptr; // salesをNULLにする。

結果)

何日間の売り上げを調べますか? 5[入力]

売り上げを入力してください。
1日目: 898.63[入力]
2日目: 652.32[入力]
3日目: 741.85[入力]
4日目: 852.96[入力]
5日目: 921.37[入力]

合計: $4067.13
売り上げ平均: $813.43

※ ポインタをdeleteした後、sales = nullptrのようにnullptrを代入するのはベストプラクティスの一つ。まず、プログラムが、不本意に解放されたメモリの領域にアクセスするのを防ぐことができる。次に、そのポインタに対しdeleteがもう一度間違って呼ばれた場合、エラーが起こるのを防ぐdelete演算子は、どのデータも指さないNULLポインタに対して使われたときは、何も影響が出ないようになっている。

・もっと詳しく → C++11スマートポインタ入門


関数からポインタを返す

・ポインタは、関数の返り値にもなる。

int * newNum():

・関数は、その関数内にあるローカル変数へのポインタを返してはいけない。(その関数のスコープで確保された領域を指すポインタを、返さなければよい。)

・関数は以下を指すポインタのみを返すべき。

 ・引数として関数に渡された値

 ・動的に割り当てられたメモリ

 ・static変数へのポインタ

例)

int *getRandomNumbers(int num)

{
int *array; // 数を持つ配列

// numが0かマイナスだったらnullを返す。
if (num <= 0)
return NULL;

// 配列を動的に割り当てる。
array = new int[num];

// srandにtime(0)という値を渡して、乱数を生成する。
srand(time(0));

// 乱数を使って配列に値を入れる。
for (int count = 0; count < num; count++)
array[count] = rand();

// 配列へのポインタを返す。
return array;