ポインタ演算
[第]23[課では]ポインタ[算術の基本]について学びました(
p + n、p - q)。[しかし]ポインタ[の算術にはまだ多くの細かい点がある]――[例えば]ポインタ[で大小を比較できるのか]?
void*ポインタ[とは何か]?ポインタ[の]ポインタ[とはまた何か]?
1. ポインタ[比較]
(1) 1.1 ポインタ[大きさを比較できるのか]?
[可能]! [ただし、][同じ]配列[を指す]ポインタ[同士の比較にのみ意味がある]。
▶ サンプル 2: [基礎プログラミングの実践] (難易度 ⭐)
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0];
int* p2 = &arr[3];
if (p1 < p2) { // ✅ p1 's p2
std::cout << "p1 p2" << std::endl;
}
if (p2 - p1 == 3) { // ✅ pointer 3
std::cout << "p1 p2 3" << std::endl;
}
return 0;
}
💡 [要点]: ポインタ[が比較するのは]アドレス[の値]([つまり、要素が]配列[内の位置])です。
(2) 1.2 ポインタ[等価比較]
#include iostream
int main() {
int x = 5;
int* p1 = &x;
int* p2 = &x;
if (p1 == p2) { // ✅ pointer
std::cout << "p1 p2" << std::endl;
}
int y = 10;
int* p3 = &x;
int* p4 = &y;
if (p3 != p4) { // ✅ pointer's
std::cout << "p3 p4" << std::endl;
}
return 0;
}
2. void* ポインタ
(1) 2.1 void* ポインタとは何か?
void* [はい][汎用]ポインタ——[任意の]型の[データ]を[指すことができる]。
#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* [の用途]
[用途1]:[汎用]関数([後で学ぶ] std::memcpy [つまり] void*)
#include iostream
#include cstring
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
// std::memcpy Using void* 'spointer
std::memcpy(arr2, arr1, 5 * sizeof(int));
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
return 0;
}
[用途2]:動的メモリ[割り当て]([後述] malloc [戻る] void*)
(3) 2.3 void* [の変換]
[使用する] void* [が指す]値は、[必ず具体的な]型[の]ポインタに変換する必要があります。
#include iostream
int main() {
int x = 5;
void* p = &x; // void* int
// ❌:reference void*
// std::cout << *p << std::endl;
// ✅: int*
int* intPtr = static_cast<int*>(p);
std::cout << *intPtr << std::endl; // output 5
return 0;
}
💡 [ヒント]: static_cast [は] C++ [の] 型[変換]演算子([後で学ぶ])であり、C [言語の] (int*)p よりも [安全] です。
3. [多重]ポインタ(ポインタ[の]ポインタ)
(1) 3.1 ポインタとは何か?
ポインタ[の]ポインタ[とは][ポインタアドレスを]格納[する]ポインタ([マトリョーシカ])。
#include iostream
int main() {
int x = 5;
int* p = &x; // p x
int** pp = &p; // pp p(pointer'spointer)
std::cout << "x = " << x << std::endl;
std::cout << "*p = " << *p << std::endl; //: x
std::cout << "pp = " << pp << std::endl; //: x
return 0;
}
出力:
x = 5
*p = 5
**pp = 5
(2) 3.2 [多階層]ポインタ[の]宣言
| [声明] | [意味] |
|---|---|
int x; |
x [はい] int |
int* p; |
p [はい] int*([指向] int [的]pointer) |
int** pp; |
pp [はい] int**([指向] int* [的]pointer) |
int*** ppp; |
ppp [はい] int*([指向] int [的]pointer) |
💡 [読み方]: [右から左へ読む]。
int* p;→pはintへのポインタであるint** pp;→ppは、intへのポインタへのポインタである
(3) 3.3 [多階層]ポインタ[の用途]
[用途1]:[動的]2次元配列([後述])
// points 3×4
int** matrix = new int*[3]; // matrix pointer'spointer
for (int i = 0; i < 3; i++) {
matrix[i] = new int[4];
}
[用途2]:function[修正が必要]pointer([あまり見られない]、[知っておけばよい])
#include iostream
void allocateInt(int** ptr) {
*ptr = new int(5); // *ptr()
}
int main() {
int* p = nullptr;
allocateInt(&p); // ...
std::cout << *p << std::endl; // output 5
delete p; // ...
return 0;
}
4. ポインタ[と] const [の組み合わせ]
(1) 4.1 [4つの組み合わせ]
| [宣言] | [意味] | [変更可能か]ポインタ | [参照先を変更可能か]値 |
|---|---|---|---|
int* p; |
[普通]ポインタ | ✅ [可能] | ✅ [可能] |
const int* p; |
[指向] const int [の]ポインタ | ✅ [可能] | ❌ [不可能] |
int* const p = &x; |
const ポインタ([初期化必須]) | ❌ [不可] | ✅ [可] |
const int* const p = &x; |
[指向] const [の] const ポインタ | ❌ [できない] | ❌ [できない] |
💡 [記憶のコツ]: [右から左へ読む]。
const int* p;→pはconst intへのポインタであるint* const p = &x;→pは、intを指すconstポインタである
(2) 4.2 例:const int*([常用])
#include iostream
void printArray(const int* arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// ❌ modify:arr[i] = 0; // ...
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printArray(arr, 5);
return 0;
}
💡 [要点]: const int* arr [は][最もよく使われる]関数の[引数]型——[「読み取り専用」で、[変更しない]ことを示す]。
5. 演習:[使用]ポインタ[実装]文字列[コピー]
▶ サンプル 1: [手書き] strcpy (難易度 ⭐⭐)
#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;
}
出力:
src = Hello
dest = Hello
💡 [ヒント]: [この]プログラム[では]ポインタ[演算][と][参照の解除]が[使われており]、[ポインタを]理解するための[絶好の練習]となります。
❓ よくある質問
Q:[なぜ] void [は参照にできない]のか?* A:[なぜなら]
void[はサイズのない]型であり、コンパイラは[1] [バイト]、4[バイト]、[あるいは]8[バイト]のどれを読み取るべきか[分からない]からである。[具体的な]型[の]ポインタ([例:]
int*、double*)に変換しなければ、コンパイラは[ストライドを]認識できません。
Q:[多階層]ポインタ[にはどのような用途がある]? A:[実際の]プロジェクト[において]、
int[最もよく見られるのは]: > 1. [動的な]2次元配列([例]new int*[3]) > 2. 関数で[ポインタ]自体を[変更する必要がある]場合([例]void foo(int p)pが[どこを指しているか]を[変更できる])[2段階以上ある]ポインタ(
int***[など])[推奨されません]——[理解するのが難しすぎる]。
Q:const int [と] int const [は同じですか]? A:[同じです]![以下の2つの記述は同等です]: > > const int p; // [推奨] > int const p; // [同等]ですが、[あまり使われません] > > > [しかし]
int* const p[同じではない]——[これは]constポインタ([参照先を変更できない])である。
📖 まとめ
- ポインタ[大小を比較できる]([同じ]配列[を指している場合にのみ意味がある])
void*[汎用]ポインタ、[参照の解除は不可]、[ポインタ演算は不可]- [多級]ポインタ[は]ポインタ[の]ポインタ(
int**) - ポインタ + const [4つの組み合わせがある]、[最もよく使われるのは]
const int*([読み取り専用]) - [用途]ポインタ[実装]文字列[コピーとは何かを理解するための]ポインタ[の絶好の練習]
📝 練習問題
-
初心者(難易度 ⭐): [1つ宣言]
int変数x = 5、[さらに1つ宣言]int*ポインタp[が指す先]x、[さらに1つ宣言]intポインタpp[が]p[を指す]。出力x、*p、pp、[それらが等しいことを検証する]。 -
中級(難易度 ⭐⭐): [1つの]関数
void swap(int a, int b)を書き、[2つの]ポインタ[の指す先を]入れ替える。
x,p2 y
swap(&p1, &p2):p1 y,p2 x
- 上級(難易度 ⭐⭐⭐):
[用]
void*[和]ポインタ[算術],[手書きで]myMemcpy関数:
-
関数[プロトタイプ]:
void myMemcpy(void* dest, const void* src, size_t n) -
[機能]:
srcからnの[バイト数]をdestに[コピー] -
[ヒント]:[バイト単位でコピー](
char*を使用して [ポインタ] [算術演算] を行う) -
ポインタ[整数の加算・減算]:[ポインタ先]type[サイズオフセット]
-
ポインタ[相減]:[要素数の差]
-
ポインタ[比較]:[順序の判定に使用可能]
-
void* ポインタ:[なし]型、[参照型および算術演算]不可
-
関数ポインタ:[関数の]エントリ[の]アドレスを格納
6. 🚀 次は
[ポインタ[の中級編]を学びました]。[次は] 参照(Reference)([第]25[課])を学びます――ポインタの「[安全なバージョン]」、C++の[重要な機能]です!



