指针加减运算是按所指向类型的大小进行字节偏移,而非简单算术运算;仅支持指向完整对象类型的指针(如int),void、函数指针、不完整类型指针等不支持;减法结果为ptrdiff_t类型,表示元素个数,且两指针必须指向同一数组或其尾后位置才合法。

指针加减运算的本质是按类型偏移
指针的 + 和 - 不是简单算术加减,而是基于所指向类型的大小做字节偏移。比如 int* 加 1,实际地址增加 sizeof(int) 字节(通常是 4),而不是 1 字节。
常见错误:把指针当普通整数用,比如对 char* 和 double* 都加 1 却期望得到相同字节增量——结果完全不同。
-
int arr[5] = {1,2,3,4,5}; int* p = arr;→p + 2指向arr[2],地址值比p多2 * sizeof(int) - 只允许对指向同一数组(或紧邻的尾后位置)的指针做减法:
p - q才有定义;跨数组、跨变量、空指针相减都是未定义行为 - 加减常量时,常量类型自动转为
ptrdiff_t(有符号整型),但别手动传unsigned值,可能触发隐式转换警告或截断
哪些指针支持加减?哪些不支持?
只有指向对象类型的指针(T*,其中 T 是完整类型)才支持加减。函数指针、void*、不完整类型指针(如前置声明的 struct S*)都不行。
典型报错:error: invalid operands to binary + (have 'void*' and 'int') —— 这是因为 void* 在 C++ 中不允许算术运算(C 里允许,但 C++ 明确禁止)。
- 想对
void*偏移?先转成带类型的指针:static_cast<char>(ptr) + offset</char> - 函数指针不能
+1,哪怕它在内存中连续存放——语言标准不保证函数对象可寻址或可偏移 - 指向
std::vector<bool>::reference</bool>这类代理类型的指针?别试,它根本不是真指针,连&都取不到合法地址
指针减法的结果类型和安全边界
p - q 的结果类型是 ptrdiff_t,定义在 <cstddef></cstddef> 中,是有符号整型,能表示任意两个指针间的最大差值。但它不等于字节数,而是“元素个数”。
容易被忽略的安全前提:两个指针必须指向同一数组(或同一对象的子对象),否则行为未定义——编译器不会报错,但优化后可能产生不可预测结果(比如 GCC 的 UBSan 能捕获部分情况)。
-
int a[10], b[10]; int* pa = a, *pb = b; pa - pb→ 未定义,即使a和b内存相邻 -
int* p = &a[0]; p - &a[0]→ 合法,结果为 0;&a[10] - p→ 合法(尾后指针),结果为 10 - 用
std::distance(p, q)替代手写减法?它底层仍是q - p,所以同样受上述限制约束
迭代器场景下指针加减的替代方案
现代 C++ 中,直接操作原始指针加减越来越少见,尤其在容器遍历中。但理解它对读懂底层代码(如 std::vector::data()、std::string::c_str())仍关键。
真实项目里更常遇到的是:把指针运算逻辑迁移到迭代器上,而迭代器重载了 +/-,行为与原生指针一致——但前提是它是随机访问迭代器(vector、deque、array 支持,list、forward_list 不支持)。
-
std::vector<int> v = {1,2,3,4,5}; auto it = v.begin(); *(it + 3)</int>→ 等价于v[3],底层调用operator+做偏移 -
std::list<int>::iterator lit = ...; lit + 2</int>→ 编译失败:没有匹配的operator+,因为list只提供双向迭代器 - 用
std::advance(it, n)?它对随机访问迭代器是 O(1),对其他类型是 O(n),但无法返回新迭代器——这点和it + n有本质区别
最易被绕过的细节:指针加减依赖类型大小,而类型大小受对齐、ABI、编译器选项影响。比如 #pragma pack(1) 改变结构体布局后,struct S* 的 +1 偏移量就变了——调试时若只看地址差值,很容易误判元素个数。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











