ptr + i 访问数组元素最直接,因数组名即首地址且指针加法按类型大小缩放偏移;*(ptr + i)、ptr[i]、*(arr + i) 等价;勿误用字节偏移如(char*)ptr + i,须确保i在[0, size)范围内。

用 ptr + i 访问数组元素是最直接的方式
数组名本质就是首元素地址,int arr[5] 中 arr 的类型是 int*。对指针做加法时,编译器自动按所指类型的大小缩放偏移量:比如 int* 加 1,实际地址增加 sizeof(int) 字节(通常是 4)。所以 *(ptr + i) 等价于 ptr[i],也等价于 *(arr + i)。
常见错误是误以为 ptr + i 是“字节偏移”,写成 (char*)ptr + i 再强转回 int* —— 这会导致越界或读错数据,除非你真在做底层字节操作。
- 确保
i在合法范围内:0 ,否则行为未定义 - 不要对空指针或野指针做
+ i操作,即使i == 0也不安全 - 用
std::vector时,&vec[0] + i是合法的(前提是!vec.empty()),但vec.data() + i更清晰
offsetof 不适用于普通数组元素访问
offsetof 是为结构体成员设计的宏,用于计算成员相对于结构体起始地址的字节偏移。它不能用在数组上——offsetof(int[10], [3]) 是语法错误,编译不过。
有人试图用 reinterpret_cast<char>(arr) + i * sizeof(int)</char> 模拟偏移,这虽然能算出地址,但绕远且易错:
- 手动乘
sizeof容易漏掉类型变化(比如从int改成long long) - 失去类型安全,
reinterpret_cast后再解引用需额外强转,编译器无法帮你检查 - 现代 C++ 中,这种写法在 strict aliasing 规则下可能触发未定义行为
多维数组的指针偏移要小心类型匹配
对于 int mat[3][4],mat 类型是 int[4][4]?不,是 int(*)[4](指向含 4 个 int 的数组的指针)。所以 *(mat + 1) 是第二行(即 int[4]),而 *(*(mat + 1) + 2) 才是 mat[1][2]。
如果强行用一维指针遍历:
-
int* p = &mat[0][0];是安全的,p[i * 4 + j]可访问mat[i][j] - 但
int* p = reinterpret_cast<int>(mat);</int>虽然结果相同,却依赖内存布局假设,可读性差、维护风险高 - 注意:C 风格多维数组是行优先连续存储,但 C++ 标准只保证
mat[i]内部连续,不强制整个二维块连续(不过所有主流实现都满足)
用 std::span 替代裸指针偏移更安全
C++20 引入的 std::span 是对原生数组或连续内存的非拥有视图,它封装了指针 + 大小,并提供 operator[] 和 data(),内部仍用指针偏移,但把边界检查(debug 模式下)、类型安全和语义表达统一起来了。
示例:
int arr[] = {1, 2, 3, 4, 5};
std::span<int> s{arr}; // 自动推导 size
int x = s[2]; // 安全访问,等价于 *(s.data() + 2)
</int>
比起手写 *(ptr + i),std::span 的优势在于:
- 构造时就绑定长度,避免后续传参遗漏 size
- 支持范围 for 循环:
for (int x : s) { ... } - 可隐式转换自
std::vector、std::array、原始数组,复用逻辑更干净
真正需要裸指针偏移的场景越来越少,除非你在写高性能底层代码、对接 C API 或调试内存布局——这时候务必确认对齐、别名和生命周期。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











