MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C++の基礎 - ポインタのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C++の基礎 - ポインタ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == C++において、ポインタはメモリアドレスを格納する変数である。<br> <br> ポインタを使用することにより、動的なメモリ確保やデータの効率的な操作が可能になるが、<br> 適切に扱わなければメモリリークやダングリングポインタ等の問題を引き起こす危険性がある。<br> <br> ポインタは、C++の基本的かつ重要な機能であり、以下に示すような場面で使用される。<br> * 動的なメモリ確保 * 関数への参照渡し * 配列やデータ構造の操作 * ポリモーフィズムの実現 <br><br> == ポインタとは == ポインタとは、メモリアドレスを格納する変数である。<br> 通常の変数がデータそのものを格納するのに対し、ポインタはデータが格納されているメモリ上の場所 (アドレス) を格納する。<br> <br> ポインタは、以下に示すような特徴を持っている。<br> * メモリアドレスを格納する。 * 間接参照演算子(*)を使って、ポインタが指すメモリの値にアクセスできる。 * アドレス演算子(&)を使って、変数のアドレスを取得できる。 * 動的にメモリを確保・解放することができる。 * nullptrを代入することで、何も指していない状態を表現できる。 <br> <syntaxhighlight lang="c++"> #include<iostream> int main() { // 通常の変数の宣言 int value = 10; // ポインタの宣言と初期化 // valueのアドレスをptrに格納する int* ptr = &value; // ポインタが指す値を表示 std::cout << "valueの値: " << value << std::endl; // 10が表示される std::cout << "valueのアドレス: " << &value << std::endl; // メモリアドレスが表示される std::cout << "ptrの値(アドレス): " << ptr << std::endl; // valueと同じアドレスが表示される std::cout << "ptrが指す値: " << *ptr << std::endl; // 10が表示される // ポインタを通じて値を変更 // ptrが指すメモリの値を20に変更する *ptr = 20; std::cout << "変更後のvalueの値: " << value << std::endl; // 20が表示される // nullptrの使用 // 何も指していないポインタを宣言 int* ptr2 = nullptr; return 0; } </syntaxhighlight> <br><br> == ポインタの使い方 == <syntaxhighlight lang="c++"> // ポインタの宣言には、型名の後にアスタリスク(*)を付ける int* ptr1; // int型へのポインタ double* ptr2; // double型へのポインタ char* ptr3; // char型へのポインタ // ポインタの初期化 int value = 100; int* ptr = &value; // アドレス演算子(&)で変数のアドレスを取得 // nullptrで初期化することもできる // nullptrは何も指していない状態を表す int* ptr4 = nullptr; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // 間接参照演算子(*)を使用して、ポインタが指す値にアクセスできる int value = 10; int* ptr = &value; // ポインタを通じて値を読み取る // *ptrは、ptrが指すメモリの値を表す int readValue = *ptr; // readValueは10 // ポインタを通じて値を変更する // *ptrに代入すると、ptrが指すメモリの値が変更される *ptr = 20; // valueの値が20になる </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // 動的メモリ確保には、new演算子を使用する // メモリの解放には、delete演算子を使用する // int型のメモリをヒープ領域に動的に確保 // new int(10)は、10で初期化されたint型のメモリを確保し、そのアドレスを返す int* ptr = new int(10); // ポインタを使用 std::cout << *ptr << std::endl; // メモリを解放 // 動的に確保したメモリは、必ずdeleteで解放する必要がある delete ptr; // 解放後はnullptrを代入するのが安全 // これにより、ダングリングポインタを防げる ptr = nullptr; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // 配列の動的確保には、new[]演算子を使用する // 配列の解放には、delete[]演算子を使用する // int型配列の要素数10をヒープ領域に動的に確保 int* array = new int[10]; // 配列に値を代入 // ポインタでも添字演算子[]を使用できる for(int i = 0; i < 10; i++) { array[i] = i * 2; } // 配列の値を表示 for(int i = 0; i < 10; i++) { std::cout << array[i] << " "; } std::cout << std::endl; // 配列のメモリを解放(delete[]を使用) // new[]で確保したメモリは、必ずdelete[]で解放する delete[] array; array = nullptr; </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // クラスのメンバにアクセスするには、アロー演算子(->)を使用する class MyClass { public: int value; void print() { std::cout << value << std::endl; } }; // クラスのインスタンスを動的に確保 MyClass* ptr = new MyClass(); // アロー演算子でメンバにアクセス // ptr->valueは、(*ptr).valueと同じ意味 ptr->value = 100; ptr->print(); // (*ptr).valueのように書くこともできるが、->の方が簡潔で読みやすい (*ptr).value = 200; // メモリを解放 delete ptr; </syntaxhighlight> <br><br> == 関数の引数 == 関数に大きなデータを渡す時や、関数内で値を変更したい時にポインタを使用する。<br> 値渡しではデータのコピーが発生するが、ポインタ渡しではアドレスのみを渡すため効率的である。<br> <br> <syntaxhighlight lang="c++"> // 値を変更する関数 // ポインタを引数に取ることで、呼び出し元の変数を直接変更できる void increment(int* ptr) { (*ptr)++; // ポインタが指す値をインクリメント } int main() { int value = 10; // valueのアドレスを渡す // 関数内でvalueの値が変更される increment(&value); std::cout << value << std::endl; // 11が表示される return 0; } </syntaxhighlight> <br><br> == 動的メモリ確保 == 実行時にサイズが決まる配列や、大きなデータ構造を扱う場合にポインタを使用する。<br> スタック領域は限られているため、大きなデータはヒープ領域に動的に確保する必要がある。<br> <br> <syntaxhighlight lang="c++"> int main() { int size; std::cout << "配列のサイズを入力: "; std::cin >> size; // 実行時にサイズが決まる配列を動的に確保 // スタック配列と異なり、実行時に決定したサイズで配列を作成できる int* array = new int[size]; // 配列を使用 for(int i = 0; i < size; i++) { array[i] = i; } // メモリを解放 // 動的に確保したメモリは必ず解放する delete[] array; return 0; } </syntaxhighlight> <br> ==== ポリモーフィズム ==== 基底クラスのポインタを使って、派生クラスのオブジェクトを扱う場合にポインタを使用する。<br> これにより、実行時に適切な関数が呼び出される動的な多態性が実現できる。<br> <br> <syntaxhighlight lang="c++"> class Base { public: virtual void print() { std::cout << "Base" << std::endl; } // 仮想デストラクタを定義することで、適切にメモリが解放される virtual ~Base() {} }; class Derived : public Base { public: // 基底クラスの仮想関数をオーバーライド void print() override { std::cout << "Derived" << std::endl; } }; int main() { // 基底クラスのポインタで派生クラスのオブジェクトを指す Base* ptr = new Derived(); // 実行時に派生クラスのprint関数が呼び出される ptr->print(); // "Derived"と表示される delete ptr; return 0; } </syntaxhighlight> <br><br> == ポインタの注意点 == ==== メモリリーク ==== 動的に確保したメモリを解放しないと、メモリリークが発生する。<br> プログラムが終了するまでメモリが占有され続け、システムのメモリが枯渇する可能性がある。<br> <br> <syntaxhighlight lang="c++"> // メモリリークの例 void memoryLeak() { int* ptr = new int(10); // deleteを忘れている! // 関数終了時にptrは消えるが、確保したメモリは残り続ける } // 正しい使い方 void noMemoryLeak() { int* ptr = new int(10); // 必ずdeleteする // これによりメモリが適切に解放される delete ptr; } </syntaxhighlight> <br> ==== ダングリングポインタ ==== 解放済みのメモリを指すポインタをダングリングポインタと呼ぶ。<br> ダングリングポインタを使用すると、未定義動作を引き起こす。<br> <br> <syntaxhighlight lang="c++"> int* ptr = new int(10); delete ptr; // この時点でptrはダングリングポインタ // ptrはまだアドレスを保持しているが、そのメモリは解放済み // *ptr = 20; // 未定義動作! 絶対にやってはいけない // 解放後はnullptrを代入する // これによりダングリングポインタを防げる ptr = nullptr; // nullptrチェックで安全に使用できる if(ptr != nullptr) { *ptr = 20; } </syntaxhighlight> <br> ==== 二重解放 ==== 同じメモリを2回以上解放すると、プログラムがクラッシュする可能性がある。<br> <br> <syntaxhighlight lang="c++"> int* ptr = new int(10); delete ptr; // delete ptr; // 二重解放! プログラムがクラッシュする可能性 // 解放後にnullptrを代入すれば、二重解放を防げる ptr = nullptr; // nullptrのdeleteは安全(何もしない) delete ptr; </syntaxhighlight> <br> ==== nullポインタの参照 ==== nullptrや未初期化のポインタを参照すると、プログラムがクラッシュする。<br> <br> <syntaxhighlight lang="c++"> int* ptr = nullptr; // *ptr = 10; // クラッシュする! // 使用前にnullptrチェックを行う // これにより安全にポインタを使用できる if(ptr != nullptr) { *ptr = 10; } // または、ポインタを必ず有効な値で初期化する int value = 0; int* ptr2 = &value; // 有効なアドレスで初期化 *ptr2 = 10; // 安全 </syntaxhighlight> <br> ==== 配列のdelete ==== new[]で確保したメモリは、delete[]で解放する必要がある。<br> deleteとdelete[]を混同すると、未定義動作を引き起こす。<br> <br> <syntaxhighlight lang="c++"> // 単一のオブジェクト // newで確保したメモリはdeleteで解放 int* ptr1 = new int(10); delete ptr1; // OK // 配列 // new[]で確保したメモリはdelete[]で解放 int* ptr2 = new int[10]; delete[] ptr2; // OK // 間違った例 // int* ptr3 = new int[10]; // delete ptr3; // 未定義動作! delete[]を使う必要がある </syntaxhighlight> <br><br> == ポインタと配列の違い == ポインタと配列は密接に関連しているが、重要な違いがいくつか存在する。<br> これらの違いを理解することで、ポインタと配列を適切に使い分けることができる。<br> <br> ==== 宣言と初期化 ==== 配列は宣言時にサイズが固定され、連続したメモリ領域が確保される。<br> ポインタは単にアドレスを格納する変数であり、初期化時にメモリを指すように設定する。<br> <br> <syntaxhighlight lang="c++"> // 配列の宣言 // コンパイル時にサイズが決まり、スタック領域に確保される int array[10]; // ポインタの宣言 // ポインタ変数自体はスタックに確保されるが、何も指していない int* ptr; // ポインタを使った動的配列 // 実行時にサイズを決定でき、ヒープ領域に確保される ptr = new int[10]; </syntaxhighlight> <br> ==== メモリの配置 ==== 配列名は、その配列の先頭要素のアドレスを表す定数である。<br> ポインタは変数であり、別のアドレスを代入することができる。<br> <br> <syntaxhighlight lang="c++"> int array[10]; int* ptr = array; // 配列の先頭アドレスをポインタに代入 // 配列名は定数なので、別のアドレスを代入できない // array = ptr; // エラー! 配列名は定数 // ポインタは変数なので、別のアドレスを代入できる int another[10]; ptr = another; // OK </syntaxhighlight> <br> ==== sizeof演算子の結果 ==== sizeof演算子を使った時、配列とポインタでは異なる結果が返される。<br> 配列に対してsizeofを使うと、配列全体のサイズが返される。<br> ポインタに対してsizeofを使うと、ポインタ変数自体のサイズが返される。<br> <br> <syntaxhighlight lang="c++"> int array[10]; int* ptr = array; // 配列のサイズ // int型のサイズ×要素数が返される std::cout << sizeof(array) << std::endl; // 40バイト (int型が4バイトの場合) // ポインタのサイズ // ポインタ変数自体のサイズが返される std::cout << sizeof(ptr) << std::endl; // 8バイト (64ビットシステムの場合) // 配列の要素数を計算 int arraySize = sizeof(array) / sizeof(array[0]); std::cout << arraySize << std::endl; // 10 </syntaxhighlight> <br> ==== ポインタ演算 ==== 配列名もポインタも、ポインタ演算を使用できる。<br> ただし、配列名は定数なので、インクリメントやデクリメントはできない。<br> <br> <syntaxhighlight lang="c++"> int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int* ptr = array; // 配列名を使ったアクセス std::cout << array[0] << std::endl; // 0 std::cout << *(array + 2) << std::endl; // 2 // ポインタを使用したアクセス std::cout << *ptr << std::endl; // 0 std::cout << *(ptr + 2) << std::endl; // 2 // ポインタはインクリメントできる ptr++; // OK 次の要素を指すようになる std::cout << *ptr << std::endl; // 1 // 配列名はインクリメントできない // array++; // エラー! 配列名は定数 </syntaxhighlight> <br> ==== メモリの解放 ==== スタックに確保された配列は、スコープを抜けると自動的に解放される。<br> ポインタで動的に確保したメモリは、明示的に delete または delete[] で解放する必要がある。<br> <br> <syntaxhighlight lang="c++"> void stackArray() { int array[10]; // arrayを使用 // 関数終了時に自動的に解放される } void heapArray() { int* ptr = new int[10]; // ptrを使用 // 明示的に解放する必要がある delete[] ptr; // 解放を忘れるとメモリリークが発生する } </syntaxhighlight> <br> ==== 関数の引数としての配列とポインタ ==== 関数の引数として配列を渡すと、実際にはポインタとして渡される。<br> このため、関数内で配列のサイズを知ることはできない。<br> <br> <syntaxhighlight lang="c++"> // 配列を引数に取る関数 // 実際にはポインタとして渡される void printArray(int arr[], int size) { // sizeof(arr)はポインタのサイズを返すので、要素数は計算できない // そのため、サイズを別の引数で渡す必要がある for(int i = 0; i < size; i++) { std::cout << arr[i] << " "; } std::cout << std::endl; } int main() { int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 配列と、そのサイズを渡す printArray(array, 10); return 0; } </syntaxhighlight> <br> ==== 多次元配列 ==== 配列とポインタでは、多次元配列の扱い方が異なる。<br> <br> <syntaxhighlight lang="c++"> // 2次元配列の宣言 int array2D[3][4]; // ポインタを使った2次元配列 // まず、ポインタの配列を確保 int** ptr2D = new int*[3]; // 次に、各行に配列を確保 for(int i = 0; i < 3; i++) { ptr2D[i] = new int[4]; } // 値の代入はどちらも同じ array2D[1][2] = 10; ptr2D[1][2] = 10; // ポインタで確保した2次元配列の解放 // 各行を解放してから、ポインタの配列を解放 for(int i = 0; i < 3; i++) { delete[] ptr2D[i]; } delete[] ptr2D; </syntaxhighlight> <br><br> == 配列との比較 == 静的配列とポインタによる動的配列には、それぞれ利点がある。<br> <br> 静的配列を使用する方法は、スタック領域に確保されるため、高速にアクセスできる。<br> また、メモリの解放を意識する必要がない。<br> ただし、コンパイル時にサイズが決まっている必要がある。<br> <br> ポインタを使用する方法は、実行時にサイズを決定できる。<br> また、大きなデータをヒープ領域に確保できる。<br> ただし、メモリの解放を明示的に行う必要がある。<br> <br> 静的配列は、サイズが固定で小さいデータに適している。<br> ポインタによる動的配列は、実行時にサイズが決まる場合や、大きなデータを扱う場合に適している。<br> <br> ただし、現代のC++では、std::vector や std::array等のコンテナクラスの使用が推奨される。<br> これらのクラスは、メモリ管理を自動化し、より安全で使いやすいインターフェースを提供する。<br> <br> ==== 例 : 動的配列の使用 ==== <syntaxhighlight lang="c++"> #include <iostream> int main() { int size; std::cout << "要素数を入力してください: "; std::cin >> size; // 動的に配列を確保 // ユーザが入力したサイズで配列を作成できる int* array = new int[size]; // 配列に値を代入 for(int i = 0; i < size; i++) { array[i] = (i + 1) * 10; } // 配列の内容を表示 std::cout << "配列の内容: "; for(int i = 0; i < size; i++) { std::cout << array[i] << " "; } std::cout << std::endl; // 配列の平均値を計算 double sum = 0; for(int i = 0; i < size; i++) { sum += array[i]; } double average = sum / size; std::cout << "平均値: " << average << std::endl; // メモリを解放 // 動的に確保したメモリは必ず解放する delete[] array; array = nullptr; return 0; } </syntaxhighlight> <br><br> __FORCETOC__ [[カテゴリ:C++]]
C++の基礎 - ポインタ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse