C++プログラマへの道 #2 - ポインタ & 参照

Last Edited: 8/29/2024

このブログ記事は、C++におけるポインタや参照変数について紹介します。

C++ References

ポインタ

ポインタの動作はほとんどCと同じで、詳細は "Cプログラマへの道 #5 - ポインタ" や "Cプログラマへの道 #7 - ポインタ 続"の記事で説明 されています。ポインタに慣れていない場合は、ぜひチェックしてみてください。C++におけるポインタの取り扱いで 特に注目すべき違いは、voidポインタの扱い方です。Cではvoidポインタに対して弱い型付けが行われ、型キャスト が不要ですが、C++ではポインタの型キャストが必要です。

void *ptr;
// 以下はCでは問題ありませんが、C++ではエラーになります
int *i = ptr; 
int *j = malloc(sizeof(int));
 
// C++では以下のようにする必要があります
int *i = (int *) ptr;
int *j = (int *) malloc(sizeof(int));

このように明示的な型キャストはCとC++の両方でコンパイル可能なので、冗長に感じるかもしれませんが、常に 型キャストをするべきかもしれません。

参照

Cでは、引数を参照渡しで渡すためにポインタを多用してきました。たとえば、次のようにswap関数を作成します。

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

このような簡単なプログラムでは、参照渡しにポインタを使うのは簡単ですが、複雑になるとすぐに混乱してしまいます。 これを解決するために、C++では参照変数が導入されました。参照変数はポインタを作成せずに変数のエイリアス(言い換え) として機能し、参照渡しが可能です。

void swap(int &x, int &y) {
    int temp = x;
    x = y;
    y = x;
}
 
int main () {
 
    int x = 10;
    int y = 20;
 
    swap(x, y);
 
    cout << "x: " << x << ", y: " << y << endl;
    // => x: 20, y: 10
 
    // 参照変数の初期化
    int &rX = x;
    rX += 2; // xにも影響を与えます
    cout << "x: " << x << ", rX: " << rX << endl;
    // => x: 22, rX: 22
 
    return 0;
}

参照変数は&を使って宣言され、ポインタとは異なり初期化が必要です。単なるエイリアスであるため、 デリファレンスが不要であり、上記のようにswap関数の定義を簡素化できます。ポインタと同様に、 参照変数を関数から返すこともでき、以下のmax関数のようなシナリオで有用です。

int &max(int array[], int length) {
    int max_idx = 0;
    for (int i = 1; i < length; i++) {
        if (array[i] > array[max_idx]) {
            max_idx = i;
        }
    }
    return array[max_idx];
}
 
int _max(int array[], int length) {
    int max_idx = 0;
    for (int i = 1; i < length; i++) {
        if (array[i] > array[max_idx]) {
            max_idx = i;
        }
    }
    return array[max_idx];
}
 
int main () {
    int array[3] = {3, 100, 3};
    
    int &max_val = max(array, 3); // maxによって返される参照変数
    int _max_val = _max(array, 3); // _maxによって返される値
 
    _max_val++;
    cout << "array[1]: " << array[1] << "_max_val" << _max_val << endl;
    // => array[1]: 100, _max_val: 101
 
    max_val++;
    cout << "array[1]: " << array[1] << "max_val" << max_val << endl;
    // => array[1]: 101, _max_val: 101
 
    return 0;
}

max関数はarray[1]への参照変数を返すのに対し、_max関数は配列の最大値を返します。そのため、 max_valがインクリメントされたときにのみarray[1]もインクリメントされます。

動的メモリ割り当て

ポインタを使用するもう一つの方法として、malloccallocreallocなどを使った動的メモリ割り当てがあります。C++では、 以下の新しい構文を導入することで、これらに近いことをより簡単に実現しています:

// C/C++
int *j = (int *) malloc(5 * sizeof(int));
free(j);
 
// C++
int *j = new int[5];
delete[] j;

newmallocに、deletefreeに対応していますが、いくつか重要な違いがあります。mallocは指定したバイト数の メモリをヒープに割り当て、失敗した場合はNULLを返しますが、newはコンパイラに必要なメモリ容量を計算させ、 ヒープ領域(free store)にメモリを割り当て、失敗した場合にはエラーを投げます(NULLを返しません)。

さらに、newdeleteはオブジェクトの初期化と破棄を、それぞれコンストラクタとデストラクタを使って行います (コンストラクタとデストラクタについては次回の記事で説明します)。また、newdeleteを使って単一のintも初期化できます:

int *j = new int(5); // 5で初期化され、配列ではありません
delete j;

クイズ

この記事では、学習した内容を確認するためのクイズを設けます。記事のメイン部分を読んだ後に、ぜひ自分で問題を解いてみることを強くお勧めします。各問題をクリックすると答えが表示されます。

リソース