C++ポインタ練習問題集

C++のポインタに関する教科書の内容を踏まえた練習問題を作成しました。基本的な概念から始めて、徐々に応用的な内容へと進んでいきます。これらの問題を解くことで、ポインタの基本概念と実践的な使い方を理解できるようになります。

初級編: ポインタの基礎

問題1: ポインタと変数のアドレス

#include <iostream>
using namespace std;

int main() {
    int a = 100;

    // 以下のコードを完成させて、変数aの値とアドレスを表示してください
    cout << "aの値は" << /* ここを埋める */ << endl;
    cout << "アドレスは" << /* ここを埋める */ << " です。" << endl;

    return 0;
}
解答
#include <iostream>
using namespace std;

int main() {
    int a = 100;

    cout << "aの値は" << a << endl;
    cout << "アドレスは" << std::hex << &a << " です。" << endl;

    return 0;
}

解説: 変数の値は変数名をそのまま使うことで取得できます。アドレスは&演算子を使って取得します。std::hexを使うことで、アドレスを16進数で表示できます。教科書のサンプルコードと同様の出力形式になります。

問題2: ポインタの宣言と初期化

#include <iostream>
using namespace std;

int main() {
    int x = 50;

    // 以下のコードを完成させて、ポインタpをNULLで初期化し、
    // その後xのアドレスを代入してください

    // ここにコードを書く

    cout << "pの値: " << p << endl;
    cout << "*pの値: " << *p << endl;

    return 0;
}
解答
#include <iostream>
using namespace std;

int main() {
    int x = 50;

    int* p = NULL;  // NULLで初期化
    p = &x;         // xのアドレスを代入

    cout << "pの値: " << p << endl;
    cout << "*pの値: " << *p << endl;

    return 0;
}

解説: ポインタ変数は、型名の後に*を付けて宣言します。教科書の説明通り、初期化時にNULLを使用することで、無効なアドレスを指すことを防ぎます。その後、&演算子で変数のアドレスを取得し、ポインタに代入しています。

問題3: ポインタを使った値の変更

#include <iostream>
using namespace std;

int main() {
    int a = 100;
    int* p = &a;

    // ポインタpを使って、変数aの値を200に変更してください

    cout << "aの値: " << a << endl;

    return 0;
}
解答
#include <iostream>
using namespace std;

int main() {
    int a = 100;
    int* p = &a;

    *p = 200;  // ポインタpが指す値(変数a)を200に変更

    cout << "aの値: " << a << endl;

    return 0;
}

解説: ポインタの値を間接参照(デリファレンス)するには*演算子を使います。教科書のサンプル213で示されているように、*p = 200;とすることで、pが指す先の値(この場合は変数a)を200に変更します。

問題4: ポインタの追跡

#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int y = 20;
    int* p1 = &x;
    int* p2 = &y;

    cout << "x = " << x << ", y = " << y << endl;
    cout << "*p1 = " << *p1 << ", *p2 = " << *p2 << endl;

    // 以下のコードを実行すると、各変数はどのような値になるか予測してください
    p1 = p2;
    *p1 = 30;

    cout << "x = " << x << ", y = " << y << endl;
    cout << "*p1 = " << *p1 << ", *p2 = " << *p2 << endl;

    return 0;
}
解答

実行後の値:

解説: p1 = p2;によって、ポインタp1はp2と同じ値(yのアドレス)を持つようになります。つまり、p1もyを指すようになります。その後、*p1 = 30;でp1が指す変数(y)の値を30に変更します。xはどのポインタからも参照されなくなっているため、値は変わりません。

中級編: ポインタの応用

問題5: ポインタを使った値の交換

#include <iostream>
using namespace std;

// 以下の関数を完成させて、2つの整数値を交換するようにしてください
void swap(int* a, int* b) {
    // ここにコードを書く
}

int main() {
    int x = 5, y = 10;
    cout << "交換前: x = " << x << ", y = " << y << endl;

    swap(&x, &y);

    cout << "交換後: x = " << x << ", y = " << y << endl;
    return 0;
}
解答
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

解説: 教科書のサンプル214で示されている通り、ポインタを使うことで関数の外にある変数の値を変更することができます。一時変数tempを使用して、ポインタが指す2つの値を交換しています。ポインタを引数として受け取ることで、呼び出し元の変数の値を直接変更できるのがポインタの重要な用途の一つです。

問題6: 複数のポインタ

#include <iostream>
using namespace std;

int main() {
    int value = 100;
    int* p1 = &value;
    int* p2 = p1;

    // 以下のコードを実行すると、valueの値はいくつになるか予測してください
    *p1 = 200;
    *p2 = *p2 + 50;

    cout << "valueの値: " << value << endl;

    return 0;
}
解答

valueの値は250になります。

解説: p1とp2は同じアドレス(valueのアドレス)を指しています。まず*p1 = 200;でvalueの値を200に変更し、次に*p2 = *p2 + 50;で現在のvalueの値(200)に50を加えて250にしています。これは教科書で説明されているように、ポインタが指す値を変更すると、元の変数の値も変更される例です。

問題7: NULL安全なポインタ処理

#include <iostream>
using namespace std;

// 以下の関数を完成させて、ポインタがNULLでない場合のみ値を2倍にする
// NULLの場合はエラーメッセージを表示する
void double_value(int* ptr) {
    // ここにコードを書く
}

int main() {
    int a = 10;
    int* p1 = &a;
    int* p2 = NULL;

    double_value(p1);
    cout << "a = " << a << endl;

    double_value(p2);

    return 0;
}
解答
void double_value(int* ptr) {
    if (ptr != NULL) { // C++11以降は if (ptr != nullptr) が推奨される
        *ptr = *ptr * 2;
    } else {
        cout << "エラー: NULLポインタです" << endl;
    }
}

解説: ポインタを使う前に、NULLでないことを確認するのは重要な習慣です。NULLポインタを間接参照(*ptrを使用)しようとすると、プログラムがクラッシュする可能性があります。教科書でNULLによる初期化の重要性が説明されていることからも、この安全チェックの必要性が理解できます。

上級編: 実践的なポインタの使用

問題8: 配列とポインタ

#include <iostream>
using namespace std;

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};
    int* p = numbers;  // 配列名は先頭要素のアドレス

    // ポインタ演算を使って配列の全要素を表示するコードを書いてください
    // for文とポインタ演算の両方を使うこと

    return 0;
}
解答
#include <iostream>
using namespace std;

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};
    int* p = numbers;  // 配列名は先頭要素のアドレス

    for (int i = 0; i < 5; i++) {
        cout << "*(p + " << i << ") = " << *(p + i) << endl;
        // または cout << "p[" << i << "] = " << p[i] << endl; でも同じ意味
    }

    return 0;
}

解説: ポインタ演算を使うと、配列の要素にアクセスできます。*(p + i)は「pからi個の要素分だけ進んだ位置の値」を表します。配列名(この場合はnumbers)は配列の先頭要素のアドレスとして機能するため、ポインタに代入できます。

問題9: 関数に配列を渡す

#include <iostream>
using namespace std;

// 以下の関数を完成させて、整数配列の合計を計算するようにしてください
int sum_array(int* arr, int size) {
    // ここにコードを書く
}

int main() {
    int values[5] = {1, 2, 3, 4, 5};
    int total = sum_array(values, 5);

    cout << "配列の合計: " << total << endl;

    return 0;
}
解答
int sum_array(int* arr, int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];  // または sum += *(arr + i);
    }
    return sum;
}

解説: C++では配列を関数に渡すと、実際にはポインタとして渡されます。そのため、関数内で配列のサイズ情報が失われるので、サイズを別の引数として渡す必要があります。関数内では、ポインタを配列のように使うことができます(arr[i])。また、ポインタ演算(*(arr + i))を使うこともできます。

問題10: 複数の値を返す関数

#include <iostream>
using namespace std;

// 以下の関数を完成させて、整数の最大値と最小値を求めるようにしてください
// 結果はポインタを通じて返します
void find_min_max(int* arr, int size, int* min, int* max) {
    // ここにコードを書く
}

int main() {
    int numbers[7] = {4, 7, 2, 8, 1, 3, 5};
    int min_val, max_val;

    find_min_max(numbers, 7, &min_val, &max_val);

    cout << "最小値: " << min_val << endl;
    cout << "最大値: " << max_val << endl;

    return 0;
}
解答
void find_min_max(int* arr, int size, int* min, int* max) {
    if (size <= 0) return; // 配列が空の場合は何もしない

    *min = arr[0];  // 最初の要素で初期化
    *max = arr[0];  // 最初の要素で初期化

    for (int i = 1; i < size; i++) {
        if (arr[i] < *min) {
            *min = arr[i];
        }
        if (arr[i] > *max) {
            *max = arr[i];
        }
    }
}

解説: 教科書のswap関数の例と同様に、ポインタを使うことで関数から複数の値を「返す」ことができます。この関数では、minとmaxのポインタを通じて最小値と最大値を書き込み、呼び出し元で参照できるようにしています。これは、C++で複数の値を返す一般的な方法の一つです。

総合問題: ポインタの理解度チェック

問題11: ポインタクイズ

以下のコードを実行すると、何が表示されるか予想してください。

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int* p = &a;
    int** pp = &p;  // ポインタへのポインタ

    cout << "a = " << a << endl;
    cout << "*p = " << *p << endl;
    cout << "**pp = " << **pp << endl;

    **pp = 20;

    cout << "a = " << a << endl;

    return 0;
}
解答

出力:

a = 10
*p = 10
**pp = 10
a = 20

解説: ppはポインタpのアドレスを持つポインタ(ポインタへのポインタ)です。*pppを指し、**pppが指すaの値(10)を取得します。**pp = 20;aの値を20に変更します。ポインタのレベルが増えても、最終的には元の変数の値を変更できることを示しています。

問題12: 完成させるべきコード

以下のプログラムには、複数の【???】がある箇所があります。正しいコードを入れて、プログラムが期待通りに動作するようにしてください。

#include <iostream>
using namespace std;

void increment(【???】) {
    【???】 += 1;
}

int main() {
    int num = 5;
    cout << "関数呼び出し前: num = " << num << endl;

    increment(【???】);

    cout << "関数呼び出し後: num = " << num << endl;  // numが6になっていることを確認

    return 0;
}
解答
#include <iostream>
using namespace std;

// 関数の引数としてint型へのポインタを受け取る
void increment(int* ptr) {
    // ポインタが指す先の値をインクリメントする
    *ptr += 1;
}

int main() {
    int num = 5;
    cout << "関数呼び出し前: num = " << num << endl;

    // 関数に変数のアドレスを渡す
    increment(&num);

    cout << "関数呼び出し後: num = " << num << endl;  // numが6になっていることを確認

    return 0;
}

解説: 関数から呼び出し元の変数の値を変更するには、ポインタを使用します。関数の引数として整数ポインタを宣言し (int* ptr)、関数内でそのポインタが指す値を変更します (*ptr += 1;)。呼び出し側では、変数のアドレスを渡す必要があります (&num)。これにより、numの値は5から6に増加します。

まとめ

これらの問題を通じて、C++のポインタの基本的な概念と使い方を学びました。

ポインタの重要ポイント

  1. ポインタの基本

  2. NULL安全性

  3. 関数とポインタ

  4. 配列とポインタ

これらの概念をマスターすることで、C++のメモリ管理と効率的なプログラミングの基礎を身につけることができます。ポインタは最初は難しく感じるかもしれませんが、練習を重ねることで理解が深まります。