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; }
まとめと振り返り
[編集]ポインターと参照の総まとめ
[編集]- ポインターと参照の違い
- 適切な使用シーンの理解
よくあるミスとその対策
[編集]- ポインターの初期化忘れ
- メモリリークの防止
- 参照の誤用によるバグ