C++ 指针的算术运算(建议收藏)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言:理解 C++ 指针的算术运算

在 C++ 编程中,指针的算术运算是一项既强大又危险的功能。它允许开发者直接操作内存地址,从而高效地访问和处理数据。然而,对于许多编程初学者而言,指针的算术运算往往令人感到困惑。本文将通过循序渐进的方式,结合实际案例,深入浅出地讲解 C++ 指针的算术运算原理、规则及应用,帮助读者掌握这一核心技能,同时避免常见的陷阱。


指针的基础概念:从“地址”到“指针变量”

什么是内存地址?

计算机内存中的每个字节都有一个唯一的数字标识——内存地址。例如,假设变量 int a = 10; 被分配到内存地址 0x1000,那么该地址即为 a 的存储位置。

指针变量的作用

指针变量用于存储其他变量的内存地址。例如:

int a = 10;  
int* ptr = &a; // ptr 存储 a 的地址(如 0x1000)  

这里,&a 表示“取 a 的地址”,ptr 是一个指向 int 类型的指针变量。

指针算术的核心思想

指针的算术运算允许开发者通过“地址的增减”来访问内存中相邻的数据单元。例如,若 ptr 指向地址 0x1000,则 ptr + 1 将指向下一个 int 类型的内存单元(如 0x1004,因为 int 通常占 4 字节)。


指针算术的运算规则

基础运算符:加法、减法与比较

1. 指针加法(++=

当对指针进行加法运算时,结果地址会根据指针所指向的类型自动调整。例如,若指针指向 int 类型,则 ptr + 1 会跳转到下一个 int 单元的地址。

示例代码:

int arr[] = {1, 2, 3};  
int* ptr = arr; // 指向数组首元素的地址  
std::cout << *ptr << std::endl; // 输出 1  
ptr += 1;  
std::cout << *ptr << std::endl; // 输出 2  

2. 指针减法(--=

减法运算与加法相反,指针会向“更小的地址”移动。例如,ptr - 1 会回到前一个元素的地址。

示例代码:

int* end = ptr + 2; // 指向最后一个元素 3  
end -= 1;  
std::cout << *end << std::endl; // 输出 2  

3. 指针比较(==!=>< 等)

指针可以比较是否指向同一内存地址或相对位置。例如:

if (ptr == end) {  
    std::cout << "指针指向同一地址!";  
}  

运算规则总结(表格)

运算符作用示例说明
ptr + n指针向高地址移动 n 个元素的大小int* ptr + 2 将跳转到当前地址 + 2 * sizeof(int)
ptr - n指针向低地址移动 n 个元素的大小char* ptr - 3 将跳转到当前地址 - 3 * sizeof(char)
ptr += n等价于 ptr = ptr + n简化连续移动操作
ptr -= n等价于 ptr = ptr - n
ptr == another_ptr检查两个指针是否指向同一地址返回 truefalse

指针算术与数组的深层联系

在 C++ 中,数组名本质上是一个“常量指针”,指向数组的首元素。例如:

int arr[3] = {10, 20, 30};  
// arr 的类型是 int[3],但可以隐式转换为 int*  
int* ptr = arr; // 等价于 &arr[0]  

通过指针遍历数组

指针算术是访问数组元素的高效方式:

for (int* p = arr; p != arr + 3; ++p) {  
    std::cout << *p << " "; // 输出 10 20 30  
}  

数组越界的陷阱

若指针超出数组范围(如 arr + 3),则属于未定义行为,可能导致程序崩溃或数据损坏。因此,务必在代码中设置边界检查:

if (ptr < arr || ptr >= arr + 3) {  
    std::cerr << "指针越界!";  
}  

进阶技巧:动态内存与指针算术

动态内存分配中的指针操作

通过 newdelete 分配内存时,指针算术同样适用:

int* dynamic_arr = new int[5];  
// 使用指针访问元素  
for (int* p = dynamic_arr; p < dynamic_arr + 5; ++p) {  
    *p = 0; // 初始化为 0  
}  
delete[] dynamic_arr; // 释放内存  

结构体与指针算术

指针算术也适用于结构体类型。例如:

struct Person {  
    int age;  
    std::string name;  
};  

Person people[2];  
Person* ptr = people;  
ptr += 1; // 跳转到下一个结构体的地址(地址差为 sizeof(Person))  

常见问题与注意事项

问题 1:为什么指针加法的步长是类型相关的?

指针算术的步长由指针所指向的类型决定。例如,int* 每次加 1 会跳转 sizeof(int) 字节,而 char* 则跳转 1 字节。这一设计确保了指针能正确访问连续的同类数据。

问题 2:如何避免指针越界?

  • 使用数组长度或固定边界变量(如 arr + N)作为循环条件。
  • 避免对 const 指针或 nullptr 进行算术运算。

问题 3:指针算术与 sizeof 的关系

sizeof 运算符可计算类型或数组的总字节数。例如:

int arr[5];  
std::cout << sizeof(arr) << std::endl; // 输出 20(假设 int 占 4 字节)  
std::cout << (arr + 5) - arr << std::endl; // 输出 5,即元素个数  

实战案例:实现简易内存拷贝

以下代码通过指针算术实现内存块的拷贝:

void mem_copy(char* dest, const char* src, size_t size) {  
    for (size_t i = 0; i < size; ++i) {  
        *(dest + i) = *(src + i); // 或者 *dest++ = *src++;  
    }  
}  

int main() {  
    char src[] = "Hello, World!";  
    char dest[14];  
    mem_copy(dest, src, sizeof(src));  
    std::cout << dest << std::endl; // 输出 "Hello, World!"  
    return 0;  
}  

结论:安全且高效地使用指针算术

C++ 指针的算术运算是一把“双刃剑”:它赋予开发者直接操作内存的自由,但也要求严谨的逻辑和边界检查。通过理解指针与内存地址的关系、掌握运算规则、并结合实际案例练习,开发者可以安全地利用这一功能实现高效的数据处理。

在未来的编程旅程中,建议读者:

  1. 多使用调试工具(如 GDB)观察指针指向的内存变化;
  2. 对动态分配的内存始终进行生命周期管理;
  3. 在复杂场景中优先使用 std::arraystd::vector 等容器,避免手动操作指针。

掌握指针算术不仅是 C++ 学习的必经之路,更是理解计算机底层逻辑的重要基石。希望本文能帮助你在这条道路上迈出坚实的一步!

最新发布