C++: ポインタ演算

最終更新:2026-08-31

レッスン23ではポインタ演算の基本(p + np - q)を学びました。

でもポインタにはまだ重要な内容があります —— ポインタの比較は?void* ポインタは?ポインタのポインタは?


1. ポインタの比較

(1) 1.1 ポインタの比較は可能?

はい! ただし配列内のポインタ同士のみ意味を持ちます。

▶ サンプル 2:基本的なプログラミング練習(難易度 ⭐)

CPP
#include <iostream>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* p1 = &arr[0];
    int* p2 = &arr[3];
    
    if (p1 < p2) { // ✅ p1はp2より前
        std::cout << "p1はp2より前" << std::endl;
    }
    
    if (p2 - p1 == 3) { // ✅ ポインタの差は3
        std::cout << "p1とp2の間は3要素" << std::endl;
    }
    
    return 0;
}
▶ 試してみよう
💡 ヒント: ポインタの比較はアドレスの比較(配列の順序)です。


(2) 1.2 ポインタの等価比較

CPP
#include <iostream>

int main() {
    int x = 5;
    int* p1 = &x;
    int* p2 = &x;
    
    if (p1 == p2) { // ✅ 同じポインタ
        std::cout << "p1とp2は同じ" << std::endl;
    }
    
    int y = 10;
    int* p3 = &x;
    int* p4 = &y;
    
    if (p3 != p4) { // ✅ 異なるポインタ
        std::cout << "p3とp4は異なる" << std::endl;
    }
    
    return 0;
}


2. void* ポインタ

(1) 2.1 void* ポインタとは?

void*汎用ポインタ —— 型を持たないアドレスを格納できます。

CPP
#include <iostream>

int main() {
    int x = 5;
    double y = 3.14;
    char c = 'A';
    
    void* p1 = &x; // ✅ void* はintを指せる
    void* p2 = &y; // ✅ void* はdoubleを指せる
    void* p3 = &c; // ✅ void* はcharを指せる
    
    return 0;
}
💡 ヒント:

  • void*間接参照できない(コンパイラが何バイト読むかわからない)
  • void*ポインタ演算できない

(2) 2.2 void* の使い道

使い道:汎用関数を書く(例:std::memcpyvoid* を使う)

CPP
#include <iostream>
#include <cstring>

int main() {
    int arr1[5] = {1, 2, 3, 4, 5};
    int arr2[5];
    
    // std::memcpy は void* で任意の型のポインタを受け取る
    std::memcpy(arr2, arr1, 5 * sizeof(int));
    
    for (int i = 0; i < 5; i++) {
        std::cout << arr2[i] << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

他の使い道:動的メモリ確保(C言語の mallocvoid* を返す)


(3) 2.3 void* から値を取り出す

void* から値を取り出すには、型付きポインタにキャストします。

CPP
#include <iostream>

int main() {
    int x = 5;
    void* p = &x; // void* にintのアドレスを格納
    
    // ❌ 間接参照できない:void* は型が不明
    // std::cout << *p << std::endl;
    
    // ✅ int* にキャスト
    int* intPtr = static_cast<int*>(p);
    std::cout << *intPtr << std::endl; // 出力: 5
    
    return 0;
}
💡 ヒント: static_cast は C++ の型キャスト演算子で、C言語の (int*)p より安全です。



3. ポインタのポインタ

(1) 3.1 ポインタのポインタとは?

ポインタのポインタはポインタのアドレスを格納するポインタです。

CPP
#include <iostream>

int main() {
    int x = 5;
    int* p = &x; // pはxを指す
    int** pp = &p; // ppはpを指す(ポインタのポインタ)
    
    std::cout << "x = " << x << std::endl;
    std::cout << "*p = " << *p << std::endl; // xと同じ
    std::cout << "**pp = " << **pp << std::endl; // xと同じ
    
    return 0;
}

出力:

TEXT 📖 参照専用
x = 5
*p = 5
**pp = 5

(2) 3.2 ポインタのポインタの宣言

宣言 意味
int x; xint
int* p; pint* 型(intへのポインタ)
int** pp; ppint** 型(int*へのポインタ)
int*** ppp; pppint*** 型(int**へのポインタ)
💡 ヒント: 右から左へ読むとわかりやすいです:

  • int* p;p is a pointer to int
  • int** pp;pp is a pointer to pointer to int

(3) 3.3 ポインタのポインタの使い道

使い道1:動的2次元配列

CPP
// 3×4の行列
int** matrix = new int*[3]; // matrixはポインタのポインタ
for (int i = 0; i < 3; i++) {
    matrix[i] = new int[4];
}

使い道2:関数でポインタ自体を変更(ポインタを渡して変更)

CPP
#include <iostream>

void allocateInt(int** ptr) {
    *ptr = new int(5); // *ptr(元のポインタ)を変更
}

int main() {
    int* p = nullptr;
    allocateInt(&p); // pのアドレスを渡す
    std::cout << *p << std::endl; // 出力: 5
    delete p; // 解放
    return 0;
}


4. ポインタと const の関係

(1) 4.1 まとめ

宣言 名前 ポインタの変更 指す値の変更
int* p; 通常のポインタ ✅ 可能 ✅ 可能
const int* p; const int へのポインタ ✅ 可能 ❌ 不可
int* const p = &x; const ポインタ(定数ポインタ) ❌ 不可 ✅ 可能
const int* const p = &x; const への const ポインタ ❌ 不可 ❌ 不可
💡 ヒント: 右から左へ読む:

  • const int* p;p is a pointer to const int
  • int* const p = &x;p is a const pointer to int

(2) 4.2 例:const int*(読み取り専用)

CPP
#include <iostream>

void printArray(const int* arr, int size) {
    for (int i = 0; i < size; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    
    // ❌ 修正不可:arr[i] = 0; // コンパイルエラー
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printArray(arr, 5);
    return 0;
}
💡 ヒント: const int* arr読み取り専用関数の引数型として推奨 —— 「読むだけ、修正しない」を明示。



5. 実践例:ポインタで文字列関数を実装

▶ サンプル 1:自作 strcpy(難易度 ⭐⭐)

CPP
#include <iostream>

void myStrcpy(char* dest, const char* src) {
    while (*src != '\0') {
        *dest = *src; // 文字をコピー
        dest++; // 次へ進む
        src++;
    }
    *dest = '\0'; // 終端文字を追加
}

int main() {
    char src[] = "Hello";
    char dest[20];
    
    myStrcpy(dest, src);
    std::cout << "src = " << src << std::endl;
    std::cout << "dest = " << dest << std::endl;
    
    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
src = Hello
dest = Hello
💡 ヒント: このプログラムはポインタ参照渡しの両方を使い、ポインタの強力さを理解できます。


▶ サンプル 3:ポインタの比較(難易度 ⭐⭐)

CPP
#include <iostream>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* start = arr;
    int* end = arr + 5;

    // ポインタ比較で反復
    int* p = start;
    while (p < end) {
        std::cout << *p << " ";
        p++;
    }
    std::cout << std::endl;

    // ポインタ減算で位置を求める
    int* mid = arr + 2;
    int index = mid - arr;
    std::cout << "中央要素のインデックス: " << index << std::endl;

    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
10 20 30 40 50
中央要素のインデックス: 2

❓ よくある質問

Q ポインタのポインタはいつ使う?
A 実務では以下のような場面で使います:

  1. 動的2次元配列(new int*[3]
  2. 関数でポインタ自体を変更(void foo(int** p)p を変更)

それ以上のポインタ(int*** など)はめったに使わない


📖 まとめ

  • ポインタの比較は可能(配列内のみ)
  • void* は汎用ポインタ、間接参照不可、ポインタ演算不可
  • ポインタのポインタはポインタのアドレスを格納(int**
  • ポインタ + const は読み取り専用、推奨は const int*(読み取り専用)
  • ポインタで文字列関数を実装し、ポインタの強力さを理解

📝 練習問題

  1. 初級(難易度 ⭐): int 変数 x = 5int* ポインタ px を指し、int** ポインタ ppp を指してください。x*p**pp を出力して確認。

  2. 中級(難易度 ⭐⭐): 関数 void swap(int** a, int** b) を書いてください。ポインタ自体を交換。

    TEXT 📖 参照専用
    交換前:p1はxを指し、p2はyを指す
    swap(&p1, &p2)後:p1はyを指し、p2はxを指す
    
  3. 上級(難易度 ⭐⭐⭐): void* とポインタ演算を使って、自作 myMemcpy 関数を実装:

    • 関数シグネチャ:void myMemcpy(void* dest, const void* src, size_t n)
    • 機能:src から n バイトを dest にコピー
    • ヒント:バイト単位でコピー(char* ポインタを使う)


6. 🚀 次のステップ

ポインタの応用を理解したら、次は 参照(Reference)(レッスン25) —— ポインタの「安全版」、C++独自の機能を学ぼう!

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%