C++/ポインターと参照
< C++
ポインターの基礎
編集ポインターとは
編集メモリとアドレスの概念
編集プログラムが実行される際、変数はメモリ上の特定の位置に格納されます。この位置を「アドレス」と呼びます。
#include <iostream> auto main() -> int { int a{10}; std::cout << "a の値: " << a << std::endl; //=> a の値: 10 std::cout << "a のアドレス: " << &a << std::endl; //=> a のアドレス: 0x7ffde7b0fa64 return 0; }
ポインターの宣言と初期化
編集ポインターは特定のデータ型のアドレスを保持する変数です。
int a{10}; int* p = &a; // p は a のアドレスを保持するポインター
nullptr
編集ポインターがどの変数も指していない場合、nullptr
で初期化します。
int* p = nullptr;
ポインターの操作
編集アドレス演算子(&)と間接演算子(*)
編集アドレス演算子 &
は変数のアドレスを取得し、間接演算子 *
はポインターが指すアドレスの値を取得します。
int a{10}; int* p = &a; std::cout << "p が指す値: " << *p << std::endl; // 10
ポインターによる変数の参照と変更
編集ポインターを使って変数の値を変更できます。
int a{10}; int* p = &a; *p = 20; std::cout << "a の新しい値: " << a << std::endl; // 20
ポインターの加減算
編集ポインターを使って配列の要素にアクセスできます。
int arr[3] = {10, 20, 30}; int* p = arr; std::cout << "配列の2番目の要素: " << *(p + 1) << std::endl; // 20
ポインターの例と演習問題
編集基本的なポインターの使用例
編集以下のコードはポインターの基本的な操作を示します。
#include <iostream> auto main() -> int { int a{5} int* p = &a; std::cout << "a の値: " << a << ", a のアドレス: " << p << std::endl; *p = 10; std::cout << "a の新しい値: " << a << std::endl; return 0; }
演習問題
編集- 変数
b
のポインターを宣言し、b
の値を10から20に変更してみてください。 - ポインターを使って配列
arr
の全要素を表示してください。
配列とポインター
編集配列とポインターの関係
編集配列の先頭要素のアドレス
編集配列名は配列の先頭要素のアドレスを指します。
int arr[3] = {1, 2, 3}; int* p = arr; std::cout << "配列の先頭要素: " << *p << std::endl; // 1
ポインターを使った配列の操作
編集ポインターを使って配列要素にアクセスできます。
int arr[3] = {1, 2, 3}; int* p = arr; for (int i = 0; i < 3; i++) { std::cout << "arr[" << i << "] = " << *(p + i) << std::endl; }
ポインターの配列
編集ポインターの配列の宣言と使用方法
編集ポインターの配列を宣言することで、複数のポインターを管理できます。
int a = 10, b = 20, c = 30; int* arr[3] = {&a, &b, &c}; for (int i = 0; i < 3; i++) { std::cout << "arr[" << i << "] が指す値: " << *arr[i] << std::endl; }
多次元配列とポインター
編集多次元配列の操作にもポインターを使えます。
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}}; int (*p)[3] = arr; std::cout << "2行目3列目の値: " << p[1][2] << std::endl; // 6
配列とポインターの例と演習問題
編集配列操作の具体例
編集#include <iostream> auto main() -> int { int arr[5] = {1, 2, 3, 4, 5}; int* p = arr; for (int i = 0; i < 5; i++) { std::cout << "arr[" << i << "] = " << *(p + i) << std::endl; } return 0; }
演習問題
編集- 配列
arr
の全要素をポインターを使って2倍にしてください。 - 2次元配列
matrix
の要素をポインターを使って表示してください。
関数とポインター
編集ポインターを引数とする関数
編集関数へのポインターの渡し方
編集void increment(int* p) { (*p)++; } auto main() -> int { int a{10}; increment(&a); std::cout << "a の値: " << a << std::endl; // 11 return 0; }
ポインターを使った関数の戻り値
編集int* add(int* a, int* b) { int* result = new int; *result = *a + *b; return result; } auto main() -> int { int x{5}, y = 10; int* sum = add(&x, &y); std::cout << "x + y = " << *sum << std::endl; // 15 delete sum; // 動的に確保したメモリの解放 return 0; }
関数ポインター
編集関数ポインターの宣言と使用
編集int add(int a, int b) { return a + b; } auto main() -> int { int (*funcPtr)(int, int) = add; std::cout << "10 + 5 = " << funcPtr(10, 5) << std::endl; // 15 return 0; }
コールバック関数の実装
編集void callbackExample(void (*callback)()) { std::cout << "Callback function execution:" << std::endl; callback(); } void myCallback() { std::cout << "Hello from callback!" << std::endl; } auto main() -> int { callbackExample(myCallback); return 0; }
関数とポインターの例と演習問題
編集関数ポインターを使ったプログラム例
編集#include <iostream> void greet() { std::cout << "Hello, World!" << std::endl; } void farewell() { std::cout << "Goodbye, World!" << std::endl; } auto main() -> int { void (*messageFunc)(); messageFunc = greet; messageFunc(); // Hello, World! messageFunc = farewell; messageFunc(); // Goodbye, World! return 0; }
演習問題
編集- 数値の配列を引数に取り、その平均を計算する関数を作成してください。関数はポインターを使って配列を受け取ります。
- 関数ポインターを使って、複数の数学関数(加算、減算、乗算、除算)を実行するプログラムを作成してください。
参照の基礎
編集参照とは
編集参照の宣言と初期化
編集int a{10}; int& ref = a; std:: cout << "ref の値: " << ref << std::endl; // 10
参照とポインターの違い
編集参照は初期化時に変数を設定し、その後変更できません。
int a{10}; int& ref = a; ref = 20; // a が 20 に変更される std::cout << "a の新しい値: " << a << std::endl; // 20
参照の使用方法
編集関数引数としての参照
編集void increment(int& ref) { ref++; } auto main() -> int { int a{10}; increment(a); std::cout << "a の値: " << a << std::endl; // 11 return 0; }
参照のリターン
編集int& larger(int& a, int& b) { return (a > b) ? a : b; } auto main() -> int { int x = 5, y = 10; int& largerValue = larger(x, y); largerValue = 20; std::cout << "x = " << x << ", y = " << y << std::endl; // x = 5, y = 20 return 0; }
参照の例と演習問題
編集基本的な参照の使用例
編集#include <iostream> auto main() -> int { int a{5} int& ref = a; std::cout << "a の値: " << a << ", ref の値: " << ref << std::endl; ref = 10; std::cout << "a の新しい値: " << a << std::endl; return 0; }
演習問題
編集- 2つの整数を引数に取り、その値を交換する関数を参照を使って作成してください。
- 参照を使って、配列の要素を2倍にする関数を作成してください。
高度なポインターの概念
編集動的メモリ管理
編集newとdelete
編集int* p = new int(10); std::cout << "動的に確保された整数の値: " << *p << std::endl; // 10 delete p;
メモリリークとその防止方法
編集動的に確保したメモリを正しく解放しないとメモリリークが発生します。
auto main() -> int { int* p = new int(10); // delete p; // これを忘れるとメモリリークが発生します return 0; }
スマートポインター
編集unique_ptr, shared_ptr, weak_ptrの使い方
編集#include <iostream> #include <memory> void useUniquePtr() { std::unique_ptr<int> p = std::make_unique<int>(10); std::cout << "*p = " << *p << std::endl; } void useSharedPtr() { std::shared_ptr<int> p1 = std::make_shared<int>(20); std::cout << "*p1 = " << *p1 << ", p1.use_count() = " << p1.use_count() << std::endl; std::shared_ptr<int> p2 = p1; std::cout << "*p1 = " << *p1 << ", p1.use_count() = " << p1.use_count() << std::endl; p2 = nullptr; std::cout << "*p1 = " << *p1 << ", p1.use_count() = " << p1.use_count() << std::endl; } void useWeakPtr() { std::shared_ptr<int> sp = std::make_shared<int>(30); std::cout << "*sp = " << *sp << ", sp.use_count() = " << sp.use_count() << std::endl; std::weak_ptr<int> wp = sp; std::cout << "*sp = " << *sp << ", sp.use_count() = " << sp.use_count() << std::endl; std::cout << "wp.expired() = " << wp.expired() << std::endl; sp = nullptr; std::cout << "wp.expired() = " << wp.expired() << std::endl; } auto main() -> int { useUniquePtr(); useSharedPtr(); useWeakPtr(); return 0; }
この例では、C++11で導入された3種類のスマートポインタ(unique_ptr
、shared_ptr
、weak_ptr
)の使用方法を示しています。
useUniquePtr()
関数:std::make_unique<int>(10)
を使ってunique_ptr
オブジェクトp
を作成し、値を10に初期化しています。unique_ptr
は単一のオブジェクトを(スタック上のではなく)ヒープ上に所有し、そのオブジェクトが無効になると自動的に削除されます。
useSharedPtr()
関数:std::make_shared<int>(20)
を使ってshared_ptr
オブジェクトp1
を作成し、値を20に初期化しています。shared_ptr
は複数のポインタが同じオブジェクトを共有できます。p2 = p1
でp2
が同じオブジェクトを参照するようになり、use_count()
が2になります。p2 = nullptr
でp2
が参照を解放しますが、p1
はまだオブジェクトを所有しているため、use_count()
が1になります。shard_ptr
は単一のオブジェクトを(スタック上のではなく)ヒープ上に所有し、そのオブジェクトへのリファレンスカウントが0になると自動的に削除されます。
useWeakPtr()
関数:std::make_shared<int>(30)
を使ってshared_ptr
オブジェクトsp
を作成し、値を30に初期化しています。weak_ptr
オブジェクトwp
はsp
が所有するオブジェクトへの弱い参照を作成します。weak_ptr
はオブジェクトを所有せず、shared_ptr
の存在を監視するためだけに使用されます。wp
はuse_count()
に影響を与えません。
高度なポインターの例と演習問題
編集スマートポインターを使ったプログラム例
編集#include <iostream> #include <memory> struct Node { int data; std::unique_ptr<Node> next; Node(int val) : data(val), next(nullptr) {} }; auto main() -> int { auto head = std::make_unique<Node>(1); head->next = std::make_unique<Node>(2); head->next->next = std::make_unique<Node>(3); Node* current = head.get(); while (current) { std::cout << "Node の値: " << current->data << std::endl; current = current->next.get(); } return 0; }
演習問題
編集shared_ptr
を使って簡単なグラフ構造を実装してください。unique_ptr
とweak_ptr
を使ってサイクルを持つデータ構造(例えば、循環リスト)を管理してください。
総合演習
編集複合的な問題を解く
編集ポインターと参照を組み合わせた問題
編集#include <iostream> void swap(int& a, int& b) { int temp = a; a = b; b = temp; } auto main() -> int { int x = 5, y = 10; swap(x, y); std::cout << "x = " << x << ", y = " << y << std::endl; // x = 10, y = 5 return 0; }
実用的なプログラムの例
編集#include <iostream> void reverseArray(int* arr, int size) { int* start = arr; int* end = arr + size - 1; while (start < end) { int temp = *start; *start = *end; *end = temp; start++; end--; } } auto main() -> int { int arr[5] = {1, 2, 3, 4, 5}; reverseArray(arr, 5); for (int i = 0; i < 5; i++) { std::cout << arr[i] << " "; } std::cout << std::endl; return 0; }
まとめと振り返り
編集ポインターと参照の総まとめ
編集- ポインターと参照の違い
- 適切な使用シーンの理解
よくあるミスとその対策
編集- ポインターの初期化忘れ
- メモリリークの防止
- 参照の誤用によるバグ