指针的算术运算:加减法与数组遍历

指针的算术运算,说白了就是让指针在内存里「走路」。

我个人习惯把指针想象成一个地址计数器。你给它加1,它就往前走一步;减1,就往后退一步。但这一步跨多大?嗯,这取决于指针的类型。

指针加减法的本质

先看一个最基础的问题:p + 1 到底加了多少?

很多人刚学的时候以为就是地址值加1。其实不是。指针加1,加的是一个完整元素的大小。

举个例子:

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;  // p 指向 arr[0]

printf("p 的地址: %p\n", p);
printf("p + 1 的地址: %p\n", p + 1);

在我的机器上,输出可能是:

p 的地址: 0x7ffd12345600
p + 1 的地址: 0x7ffd12345604

看到了吗?地址差了4个字节。因为 int 占4字节,所以 p + 1 实际上跳过了4个字节。

核心公式:

指针 + N 的实际地址 = 指针原地址 + N × sizeof(指针指向的类型)

这个规则对减法也一样:p - N 就是往后退 N 个元素。

数组遍历:指针的经典用法

用指针遍历数组,是我在项目中用得最多的场景之一。

我记得有一次写一个网络数据包解析模块,需要逐字节扫描缓冲区。用下标访问当然可以,但用指针更灵活,也更符合底层操作的直觉。

来看一个标准示例:

int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = arr;
int len = sizeof(arr) / sizeof(arr[0]);

for (int i = 0; i < len; i++) {
    printf("arr[%d] = %d\n", i, *(p + i));
}

这里 *(p + i) 等价于 arr[i]。但指针写法更直接地表达了「从起点偏移 i 个位置」这个语义。

你还可以用自增运算符来遍历:

int *p = arr;
int *end = arr + len;

while (p < end) {
    printf("%d ", *p);
    p++;  // 指针向前移动一个 int
}

这种写法在嵌入式开发中很常见。为什么?因为循环条件 p < end 直接比较地址,省去了下标变量,效率更高。

指针相减:计算元素之间的距离

两个指向同一数组的指针可以相减。结果是一个整数,表示它们之间相隔多少个元素。

int arr[] = {10, 20, 30, 40, 50};
int *p1 = &arr[1];  // 指向 20
int *p2 = &arr[4];  // 指向 50

int diff = p2 - p1;
printf("相差 %d 个元素\n", diff);  // 输出 3

这里 p2 - p1 的结果是 3,不是 12(字节数)。编译器自动帮你做了除法。

注意:指针相减只有在两个指针指向同一数组(或同一块连续内存)时才有意义。指向不同数组的指针相减,结果是未定义的。

我曾经在调试一个内存池管理代码时,就犯过这个错。两个指针分别指向不同分配块,我拿它们相减想算偏移量,结果跑出来的数值完全不对。查了半天才发现是逻辑问题。

避坑指南:越界的陷阱

指针算术运算最大的坑,就是越界。

我曾经接手过一个老项目,里面有一段代码长这样:

int buf[10];
int *p = buf;

for (int i = 0; i <= 10; i++) {
    *p = i * 2;
    p++;
}

看出来问题了吗?循环条件是 i <= 10,但数组只有10个元素(下标0到9)。当 i = 10 时,p 已经指向 buf[10]——那是数组之外的内存。

这个 bug 在测试时没暴露,因为 buf[10] 后面恰好是一块空闲内存。但上线后,在某些特定数据量下,程序就会随机崩溃。排查了整整两天才定位到。

我的建议:

  • 遍历时始终用 < 而不是 <= 做边界判断
  • 养成习惯:指针不要越过数组的最后一个元素
  • 如果一定要访问「尾后位置」,只用于比较,不要解引用

一个完整的示例:反转数组

把指针算术运算综合起来,写一个数组反转函数:

void reverse(int *arr, int len) {
    int *left = arr;
    int *right = arr + len - 1;

    while (left < right) {
        int temp = *left;
        *left = *right;
        *right = temp;

        left++;
        right--;
    }
}

这里用了两个指针,一个从头往后走,一个从尾往前走。每次交换它们指向的值。当 left 和 right 相遇或交错时,反转完成。

这种写法比用下标更直观——你一眼就能看出「两个指针在向中间靠拢」。

知识结构图

下面这张图总结了指针算术运算的核心逻辑:

指针算术运算知识结构 指针算术运算 指针 + N 地址增加 N × sizeof(类型) 指针 - N 地址减少 N × sizeof(类型) 指针 - 指针 返回元素个数 ⚠ 必须指向同一数组,且不要越界解引用

总结一下

指针的加减法,说白了就是「按元素大小步进」。加 N 往前走 N 个元素,减 N 往后退 N 个元素。两个指针相减得到它们之间的元素个数。

用指针遍历数组,比下标更贴近底层,效率也更高。但代价是——你得自己管好边界。越界一次,可能就是一个通宵的调试。

嗯,记住一句话:指针可以指向数组的「尾后位置」,但别解引用它。这是安全底线。


可可精品资料库