ポインターの基礎

編集

ポインターとは

編集

メモリとアドレスの概念

編集

プログラムが実行される際、変数はメモリ上の特定の位置に格納されます。この位置を「アドレス」と呼びます。

#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;
}

演習問題

編集
  1. 変数 b のポインターを宣言し、b の値を10から20に変更してみてください。
  2. ポインターを使って配列 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;
}

演習問題

編集
  1. 配列 arr の全要素をポインターを使って2倍にしてください。
  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;
}

演習問題

編集
  1. 数値の配列を引数に取り、その平均を計算する関数を作成してください。関数はポインターを使って配列を受け取ります。
  2. 関数ポインターを使って、複数の数学関数(加算、減算、乗算、除算)を実行するプログラムを作成してください。

参照の基礎

編集

参照とは

編集

参照の宣言と初期化

編集
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;
}

演習問題

編集
  1. 2つの整数を引数に取り、その値を交換する関数を参照を使って作成してください。
  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_ptrshared_ptrweak_ptr)の使用方法を示しています。

  1. useUniquePtr()関数:
    std::make_unique<int>(10)を使ってunique_ptrオブジェクトpを作成し、値を10に初期化しています。
    unique_ptrは単一のオブジェクトを(スタック上のではなく)ヒープ上に所有し、そのオブジェクトが無効になると自動的に削除されます。
  2. useSharedPtr()関数:
    std::make_shared<int>(20)を使ってshared_ptrオブジェクトp1を作成し、値を20に初期化しています。
    shared_ptrは複数のポインタが同じオブジェクトを共有できます。
    p2 = p1p2が同じオブジェクトを参照するようになり、use_count()が2になります。
    p2 = nullptrp2が参照を解放しますが、p1はまだオブジェクトを所有しているため、use_count()が1になります。
    shard_ptrは単一のオブジェクトを(スタック上のではなく)ヒープ上に所有し、そのオブジェクトへのリファレンスカウントが0になると自動的に削除されます。
  3. useWeakPtr()関数:
    std::make_shared<int>(30)を使ってshared_ptrオブジェクトspを作成し、値を30に初期化しています。
    weak_ptrオブジェクトwpspが所有するオブジェクトへの弱い参照を作成します。
    weak_ptrはオブジェクトを所有せず、shared_ptrの存在を監視するためだけに使用されます。
    wpuse_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;
}

演習問題

編集
  1. shared_ptr を使って簡単なグラフ構造を実装してください。
  2. unique_ptrweak_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;
}

まとめと振り返り

編集

ポインターと参照の総まとめ

編集
  • ポインターと参照の違い
  • 適切な使用シーンの理解

よくあるミスとその対策

編集
  • ポインターの初期化忘れ
  • メモリリークの防止
  • 参照の誤用によるバグ