[C++] ポインタの配列を初期化する方法

C++でポインタの配列を初期化する方法はいくつかあります。

まず、静的配列として初期化する場合、要素数を指定してポインタを格納する配列を宣言し、各要素にアドレスを代入します。

例えば、int* arr[3] = {&a, &b, &c};のように、整数型変数a, b, cのアドレスを配列に格納します。

動的配列として初期化する場合は、new演算子を使ってメモリを確保し、ポインタを格納します。

例えば、int** arr = new int*[3];でポインタの配列を作成し、各要素にnewで確保したメモリのアドレスを代入します。

これにより、柔軟にメモリを管理できます。

この記事でわかること
  • 静的および動的なポインタの配列の初期化方法
  • ポインタの配列を用いた要素へのアクセスと操作方法
  • 動的メモリ管理におけるnewとdeleteの使い方
  • ポインタの配列を活用した文字列や多次元配列の実装方法
  • 関数ポインタの配列を用いた柔軟な関数呼び出しの実現方法

目次から探す

静的配列としてのポインタの配列の初期化

静的配列の宣言方法

C++において、ポインタの配列を静的に宣言する方法は、通常の配列の宣言と似ています。

以下に基本的な宣言方法を示します。

#include <iostream>
// int型のポインタを格納する配列を宣言
int* ptrArray[5];

この例では、ptrArrayは5つのint型ポインタを格納できる配列として宣言されています。

初期化時の要素の指定

ポインタの配列を初期化する際には、各要素に適切なアドレスを割り当てる必要があります。

以下の例では、静的配列の各要素にint型変数のアドレスを割り当てています。

#include <iostream>
int main() {
    int a = 10;
    int b = 20;
    int c = 30;
    // ポインタの配列を初期化
    int* ptrArray[3] = { &a, &b, &c };
    // 各要素の値を出力
    for (int i = 0; i < 3; ++i) {
        std::cout << "ptrArray[" << i << "]が指す値: " << *ptrArray[i] << std::endl;
    }
    return 0;
}
ptrArray[0]が指す値: 10
ptrArray[1]が指す値: 20
ptrArray[2]が指す値: 30

このコードでは、ptrArrayの各要素がそれぞれa, b, cのアドレスを指しており、ループを通じて各ポインタが指す値を出力しています。

初期化の例と注意点

ポインタの配列を初期化する際には、以下の点に注意が必要です。

  • 初期化されていないポインタ: 配列の要素として使用するポインタは、必ず有効なアドレスを指すように初期化する必要があります。

未初期化のポインタを使用すると、未定義の動作を引き起こす可能性があります。

  • 配列のサイズ: 静的配列のサイズはコンパイル時に決定されるため、サイズを超える要素を追加することはできません。

以下に、初期化の注意点を含む例を示します。

#include <iostream>
int main() {
    int x = 5;
    int y = 15;
    // 初期化されていないポインタを含む配列
    int* ptrArray[3] = { &x, &y, nullptr };
    // 各要素の値を出力
    for (int i = 0; i < 3; ++i) {
        if (ptrArray[i] != nullptr) {
            std::cout << "ptrArray[" << i << "]が指す値: " << *ptrArray[i] << std::endl;
        } else {
            std::cout << "ptrArray[" << i << "]はnullptrを指しています" << std::endl;
        }
    }
    return 0;
}
ptrArray[0]が指す値: 5
ptrArray[1]が指す値: 15
ptrArray[2]はnullptrを指しています

この例では、ptrArray[2]nullptrで初期化されているため、アクセス時に特別な処理を行っています。

未初期化のポインタを使用しないように注意しましょう。

動的配列としてのポインタの配列の初期化

動的メモリ確保の基本

C++では、動的メモリ確保を行うことで、実行時に必要なメモリを柔軟に管理することができます。

動的メモリ確保は、new演算子を使用して行い、確保したメモリはdelete演算子を用いて解放します。

これにより、プログラムの実行中に必要なメモリを効率的に利用できます。

new演算子を用いた初期化

動的配列としてポインタの配列を初期化するには、まずポインタの配列自体を動的に確保し、その後、各要素に対しても動的メモリを確保します。

以下にその例を示します。

#include <iostream>
int main() {
    // ポインタの配列を動的に確保
    int** ptrArray = new int*[3];
    // 各ポインタに動的メモリを割り当て
    for (int i = 0; i < 3; ++i) {
        ptrArray[i] = new int(i * 10); // 0, 10, 20で初期化
    }
    // 各要素の値を出力
    for (int i = 0; i < 3; ++i) {
        std::cout << "ptrArray[" << i << "]が指す値: " << *ptrArray[i] << std::endl;
    }
    // メモリの解放
    for (int i = 0; i < 3; ++i) {
        delete ptrArray[i];
    }
    delete[] ptrArray;
    return 0;
}
ptrArray[0]が指す値: 0
ptrArray[1]が指す値: 10
ptrArray[2]が指す値: 20

このコードでは、ptrArrayは3つのint型ポインタを格納する配列として動的に確保され、各ポインタはそれぞれ0, 10, 20で初期化されています。

delete演算子によるメモリ解放

動的に確保したメモリは、使用後に必ず解放する必要があります。

解放を怠ると、メモリリークが発生し、プログラムのメモリ使用量が増加し続ける可能性があります。

delete演算子を使用して、個々のポインタが指すメモリを解放し、最後にポインタの配列自体をdelete[]で解放します。

以下に、メモリ解放の手順を示します。

#include <iostream>
int main() {
    // ポインタの配列を動的に確保
    int** ptrArray = new int*[3];
    // 各ポインタに動的メモリを割り当て
    for (int i = 0; i < 3; ++i) {
        ptrArray[i] = new int(i * 10);
    }
    // メモリの解放
    for (int i = 0; i < 3; ++i) {
        delete ptrArray[i]; // 各ポインタが指すメモリを解放
    }
    delete[] ptrArray; // ポインタの配列自体を解放
    return 0;
}

この例では、deleteを用いて各ポインタが指すメモリを解放し、delete[]を用いてポインタの配列自体を解放しています。

これにより、メモリリークを防ぐことができます。

ポインタの配列の操作

要素へのアクセス方法

ポインタの配列における要素へのアクセスは、通常の配列と同様にインデックスを使用して行います。

ポインタの配列では、各要素がポインタであるため、間接参照演算子*を用いて実際の値にアクセスします。

#include <iostream>
int main() {
    int a = 5;
    int b = 10;
    int c = 15;
    // ポインタの配列を初期化
    int* ptrArray[3] = { &a, &b, &c };
    // 各要素へのアクセスと出力
    for (int i = 0; i < 3; ++i) {
        std::cout << "ptrArray[" << i << "]が指す値: " << *ptrArray[i] << std::endl;
    }
    return 0;
}
ptrArray[0]が指す値: 5
ptrArray[1]が指す値: 10
ptrArray[2]が指す値: 15

このコードでは、ptrArrayの各要素に対して間接参照を行い、実際の値を出力しています。

ポインタの配列のサイズ変更

静的配列のサイズはコンパイル時に決定されるため、直接変更することはできません。

しかし、動的配列を使用することで、サイズを変更することが可能です。

動的配列のサイズを変更するには、新しいサイズの配列を確保し、既存の要素をコピーしてから古い配列を解放します。

#include <iostream>
int main() {
    // 初期のポインタの配列を動的に確保
    int** ptrArray = new int*[2];
    ptrArray[0] = new int(10);
    ptrArray[1] = new int(20);
    // 新しいサイズの配列を確保
    int** newPtrArray = new int*[3];
    for (int i = 0; i < 2; ++i) {
        newPtrArray[i] = ptrArray[i]; // 既存の要素をコピー
    }
    newPtrArray[2] = new int(30); // 新しい要素を追加
    // 古い配列を解放
    delete[] ptrArray;
    // 新しい配列の要素を出力
    for (int i = 0; i < 3; ++i) {
        std::cout << "newPtrArray[" << i << "]が指す値: " << *newPtrArray[i] << std::endl;
    }
    // メモリの解放
    for (int i = 0; i < 3; ++i) {
        delete newPtrArray[i];
    }
    delete[] newPtrArray;
    return 0;
}
newPtrArray[0]が指す値: 10
newPtrArray[1]が指す値: 20
newPtrArray[2]が指す値: 30

このコードでは、動的に確保した配列のサイズを変更し、新しい要素を追加しています。

ポインタの配列のコピー

ポインタの配列をコピーする際には、各ポインタが指すメモリの内容を新しいメモリ領域にコピーする必要があります。

単純にポインタをコピーするだけでは、同じメモリ領域を指すことになり、意図しない動作を引き起こす可能性があります。

#include <iostream>
#include <cstring> // memcpyを使用するために必要
int main() {
    // 元のポインタの配列を動的に確保
    int** originalArray = new int*[2];
    originalArray[0] = new int(100);
    originalArray[1] = new int(200);
    // コピー先の配列を動的に確保
    int** copiedArray = new int*[2];
    for (int i = 0; i < 2; ++i) {
        copiedArray[i] = new int(*originalArray[i]); // メモリの内容をコピー
    }
    // コピーされた配列の要素を出力
    for (int i = 0; i < 2; ++i) {
        std::cout << "copiedArray[" << i << "]が指す値: " << *copiedArray[i] << std::endl;
    }
    // メモリの解放
    for (int i = 0; i < 2; ++i) {
        delete originalArray[i];
        delete copiedArray[i];
    }
    delete[] originalArray;
    delete[] copiedArray;
    return 0;
}
copiedArray[0]が指す値: 100
copiedArray[1]が指す値: 200

このコードでは、originalArrayの各要素が指すメモリの内容をcopiedArrayにコピーしています。

これにより、コピー先の配列が独立したメモリ領域を持つことが保証されます。

応用例

文字列の配列としての利用

ポインタの配列は、文字列の配列として利用することができます。

C++では、文字列はchar型の配列として扱われるため、char*型のポインタを使用して文字列を指すことができます。

#include <iostream>
int main() {
    // 文字列の配列を初期化
    const char* stringArray[] = { "こんにちは", "世界", "C++" };
    // 各文字列を出力
    for (int i = 0; i < 3; ++i) {
        std::cout << "stringArray[" << i << "]: " << stringArray[i] << std::endl;
    }
    return 0;
}
stringArray[0]: こんにちは
stringArray[1]: 世界
stringArray[2]: C++

このコードでは、stringArrayは3つの文字列を指すポインタの配列として初期化され、各文字列が出力されています。

多次元配列の実装

ポインタの配列を用いることで、多次元配列を動的に実装することができます。

以下の例では、2次元配列を動的に確保し、各要素に値を設定しています。

#include <iostream>
int main() {
    // 2次元配列を動的に確保
    int rows = 2;
    int cols = 3;
    int** matrix = new int*[rows];
    for (int i = 0; i < rows; ++i) {
        matrix[i] = new int[cols];
    }
    // 配列に値を設定
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            matrix[i][j] = i * cols + j;
        }
    }
    // 配列の内容を出力
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }
    // メモリの解放
    for (int i = 0; i < rows; ++i) {
        delete[] matrix[i];
    }
    delete[] matrix;
    return 0;
}
0 1 2 
3 4 5

このコードでは、2次元配列matrixが動的に確保され、各要素に値が設定されています。

最後に、メモリが適切に解放されています。

関数ポインタの配列

関数ポインタの配列を使用することで、異なる関数を動的に呼び出すことができます。

以下の例では、異なる計算を行う関数を関数ポインタの配列に格納し、動的に呼び出しています。

#include <iostream>
// 加算を行う関数
int add(int a, int b) {
    return a + b;
}
// 減算を行う関数
int subtract(int a, int b) {
    return a - b;
}
int main() {
    // 関数ポインタの配列を初期化
    int (*operationArray[2])(int, int) = { add, subtract };
    int x = 10;
    int y = 5;
    // 各関数を呼び出し
    std::cout << "加算: " << operationArray[0](x, y) << std::endl;
    std::cout << "減算: " << operationArray[1](x, y) << std::endl;
    return 0;
}
加算: 15
減算: 5

このコードでは、operationArrayaddsubtractの関数ポインタが格納され、動的に関数が呼び出されています。

これにより、柔軟な関数の選択と実行が可能になります。

よくある質問

ポインタの配列と配列のポインタの違いは?

ポインタの配列と配列のポインタは、似た名前ですが異なる概念です。

  • ポインタの配列: これは、ポインタを要素とする配列です。

例えば、int* ptrArray[5];は、5つのint型ポインタを格納する配列です。

各要素は異なるメモリ位置を指すことができます。

  • 配列のポインタ: これは、配列全体を指すポインタです。

例えば、int (*ptr)[5];は、5つのint型要素を持つ配列を指すポインタです。

このポインタは、配列全体を一つの単位として扱います。

メモリリークを防ぐにはどうすればいい?

メモリリークを防ぐためには、動的に確保したメモリを使用後に必ず解放することが重要です。

以下の点に注意してください。

  • deleteとdelete[]の使用: newで確保したメモリはdeleteで、new[]で確保したメモリはdelete[]で解放します。

例:int* ptr = new int; delete ptr; または int* arr = new int[10]; delete[] arr;

  • スマートポインタの利用: C++11以降では、std::unique_ptrstd::shared_ptrといったスマートポインタを使用することで、自動的にメモリを管理し、メモリリークを防ぐことができます。

ポインタの配列を使うメリットは何ですか?

ポインタの配列を使用することにはいくつかのメリットがあります。

  • 柔軟性: ポインタの配列を使用することで、異なる型やサイズのデータを指すことができ、柔軟なデータ構造を構築できます。
  • 動的メモリ管理: 動的にメモリを確保することで、実行時に必要なメモリを効率的に管理できます。

これにより、プログラムのメモリ使用量を最適化できます。

  • 多次元配列の実装: ポインタの配列を用いることで、多次元配列を動的に実装することが可能です。

これにより、サイズが不定のデータを扱うことが容易になります。

まとめ

この記事では、C++におけるポインタの配列の初期化方法や操作方法について詳しく解説しました。

静的配列と動的配列の違いや、それぞれの初期化方法、さらにポインタの配列を用いた応用例についても触れています。

これらの知識を活用することで、より柔軟で効率的なプログラムを作成することが可能になります。

ぜひ、実際のプログラミングにおいてポインタの配列を活用し、コードの効率化や最適化に挑戦してみてください。

  • URLをコピーしました!
目次から探す