指针的算术运算详解

1. 引言

指针是 C/C++ 语言中最重要也最难以掌握的概念之一。很多初学者在理解指针时,往往只关注指针的声明、赋值和解引用,却忽略了指针的算术运算。实际上,指针的算术运算(Pointer Arithmetic)是理解数组、字符串和动态内存管理的核心基础。本文将系统性地讲解指针算术运算的规则、原理和典型应用,帮助你彻底掌握这一关键技能。

2. 指针算术运算的基础规则

2.1 指针的本质

指针本质上是一个变量,它存储的是另一个变量的内存地址。在 32 位系统上,指针占 4 个字节;在 64 位系统上,指针占 8 个字节。但指针算术运算的关键在于:指针加减运算的结果取决于指针所指向的数据类型的大小,而不是简单地加减 1 个字节。

2.2 指针加减整数的规则

指针 p 加上整数 n,结果是指向地址 p + n * sizeof(类型) 的新指针。也就是说,指针每加 1,实际地址增加的是 sizeof(所指向类型) 个字节。

c 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;          // p 指向 arr[0]
int *q = p + 2;        // q 指向 arr[2],地址偏移 2 * sizeof(int) = 8 字节

2.3 指针减法

指针减去整数 n,结果是指向地址 p - n * sizeof(类型) 的新指针。

c 复制代码
int *r = q - 1;        // r 指向 arr[1]

2.4 指针相减

两个同类型的指针相减,结果是它们之间相差的元素个数(不是字节数)。

c 复制代码
int *p = arr;          // 指向 arr[0]
int *q = arr + 4;      // 指向 arr[4]
long diff = q - p;     // diff = 4,表示相差 4 个元素

3. 指针算术运算的详细原理

3.1 为什么指针加 1 不是加 1 个字节?

这是指针算术运算最容易让人困惑的地方。假设 int 类型占 4 个字节,int *p 指向地址 0x1000,那么 p + 1 指向的是 0x1004,而不是 0x1001。这是因为编译器在编译时已经知道 p 指向的是 int 类型,所以会自动乘以 sizeof(int)。

c 复制代码
#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    int *p = arr;
    
    printf("p 的地址: %p\n", (void *)p);
    printf("p+1 的地址: %p\n", (void *)(p + 1));
    printf("p+2 的地址: %p\n", (void *)(p + 2));
    
    return 0;
}

运行结果示例(地址可能因系统而异):

复制代码
p 的地址: 0x7ffc12345670
p+1 的地址: 0x7ffc12345674
p+2 的地址: 0x7ffc12345678

可以看到,每次加 1,地址增加 4 个字节(sizeof(int))。

3.2 不同类型指针的算术运算差异

指针算术运算的结果与指针指向的类型密切相关。下面通过对比来加深理解:

c 复制代码
#include <stdio.h>

int main() {
    char c_arr[4] = {'a', 'b', 'c', 'd'};
    int  i_arr[4] = {1, 2, 3, 4};
    double d_arr[4] = {1.0, 2.0, 3.0, 4.0};
    
    char *cp = c_arr;
    int *ip = i_arr;
    double *dp = d_arr;
    
    printf("char 指针 +1 偏移: %ld 字节\n", (long)((char *)(cp + 1) - (char *)cp));
    printf("int 指针 +1 偏移: %ld 字节\n", (long)((char *)(ip + 1) - (char *)ip));
    printf("double 指针 +1 偏移: %ld 字节\n", (long)((char *)(dp + 1) - (char *)dp));
    
    return 0;
}

输出结果:

复制代码
char 指针 +1 偏移: 1 字节
int 指针 +1 偏移: 4 字节
double 指针 +1 偏移: 8 字节

3.3 指针算术运算的合法性

指针算术运算并不是任意指针都可以进行的,它有以下限制:

  • 指针加减整数:只能对指向数组元素的指针进行(或指向单个对象时,加 1 指向下一个对象)。
  • 指针相减:两个指针必须指向同一个数组(或同一个对象之后的一个位置)。
  • 指针不能相加:两个指针相加没有意义,C 语言也不允许这样做。
  • 指针不能乘除:指针不支持乘法和除法运算。
c 复制代码
int a = 10, b = 20;
int *pa = &a, *pb = &b;
// int *pc = pa + pb;  // 错误!指针不能相加
// int *pd = pa * 2;   // 错误!指针不能乘

4. 指针算术运算的典型应用

4.1 遍历数组

指针算术运算最常见的应用就是遍历数组,比使用下标更灵活、更高效。

c 复制代码
#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p;
    
    // 使用指针遍历数组
    for (p = arr; p < arr + 5; p++) {
        printf("%d ", *p);
    }
    printf("\n");
    
    return 0;
}

4.2 指针与数组下标的关系

在 C 语言中,arr[i] 本质上等价于 *(arr + i)。理解这一点,就能明白为什么数组下标可以从 0 开始------因为 arr[0] 就是 *(arr + 0),即数组首元素。

c 复制代码
#include <stdio.h>

int main() {
    int arr[3] = {100, 200, 300};
    
    printf("arr[1] = %d\n", arr[1]);        // 下标访问
    printf("*(arr+1) = %d\n", *(arr + 1));  // 指针访问,等价
    
    return 0;
}

4.3 计算数组元素个数

利用指针相减,可以方便地计算数组中元素的个数。

c 复制代码
#include <stdio.h>

int main() {
    int arr[8] = {0};
    int *start = arr;
    int *end = arr + 8;
    
    long count = end - start;
    printf("数组元素个数: %ld\n", count);  // 输出 8
    
    return 0;
}

4.4 字符串处理

指针算术运算在字符串处理中应用非常广泛,例如实现字符串长度计算:

c 复制代码
#include <stdio.h>

size_t my_strlen(const char *s) {
    const char *p = s;
    while (*p != '\0') {
        p++;
    }
    return p - s;  // 指针相减得到字符个数
}

int main() {
    char str[] = "Hello, Pointer!";
    printf("字符串长度: %zu\n", my_strlen(str));
    return 0;
}

4.5 动态内存管理

指针算术运算在动态内存分配中同样重要,常用于在堆上分配的内存中定位元素。

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int *)malloc(5 * sizeof(int));
    if (arr == NULL) {
        return 1;
    }
    
    // 使用指针算术运算给数组赋值
    for (int i = 0; i < 5; i++) {
        *(arr + i) = i * i;
    }
    
    // 打印结果
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, *(arr + i));
    }
    
    free(arr);
    return 0;
}

5. 指针算术运算的常见误区

5.1 误区一:指针加 1 就是地址加 1

这是最常见的误区。如前所述,指针加 1 实际地址增加的是 sizeof(类型) 字节,而不是 1 字节。

5.2 误区二:对 void 指针进行算术运算

void * 指针没有类型信息,编译器不知道它指向的对象大小,因此不能 对 void * 指针直接进行算术运算。

c 复制代码
void *p = malloc(100);
// void *q = p + 1;  // 错误!void 指针不能进行算术运算

如果需要操作,必须先转换为具体类型的指针:

c 复制代码
char *cp = (char *)p;
char *q = cp + 1;  // 正确

5.3 误区三:指针相减得到的是字节数

指针相减得到的是元素个数 ,不是字节数。如果需要字节数,需要乘以 sizeof(类型)。

c 复制代码
int arr[5] = {0};
int *p = arr;
int *q = arr + 3;
long elements = q - p;          // 3(元素个数)
long bytes = (char *)q - (char *)p;  // 12(字节数)

5.4 误区四:越界访问

指针算术运算很容易导致越界访问,这是 C 语言中常见的 bug 来源。指针可以指向数组最后一个元素之后的一个位置(称为"尾后指针"),但不能解引用它。

c 复制代码
int arr[3] = {1, 2, 3};
int *end = arr + 3;  // 合法:指向尾后位置
// int x = *end;     // 非法:解引用尾后指针是未定义行为

6. 指针算术运算与多维数组

6.1 二维数组的指针算术

对于二维数组,指针算术运算的规则同样适用,但需要理解"数组的数组"这一概念。

c 复制代码
#include <stdio.h>

int main() {
    int matrix[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };
    
    // matrix 是 int[2][3] 类型,matrix + 1 指向第二行
    int (*row)[3] = matrix + 1;  // 指向第 2 行
    printf("第二行第一个元素: %d\n", (*row)[0]);  // 输出 4
    
    // 访问 matrix[1][2]
    printf("matrix[1][2] = %d\n", *(*(matrix + 1) + 2));  // 输出 6
    
    return 0;
}

6.2 指针数组与数组指针

理解指针数组和数组指针的区别,有助于正确使用指针算术运算:

c 复制代码
// 指针数组:数组的每个元素是指针
int *ptr_arr[3];

// 数组指针:指向数组的指针
int (*arr_ptr)[3];

7. 总结

指针的算术运算虽然看似简单,但背后蕴含着 C 语言内存模型的深刻设计。掌握以下几点,就能灵活运用指针算术运算:

  • 指针加/减整数 n,实际地址偏移 n * sizeof(类型) 字节。
  • 两个同类型指针相减,结果是元素个数差。
  • 指针算术运算必须基于同一数组(或同一对象)进行,避免越界。
  • void * 指针不能直接进行算术运算,需先转换为具体类型。
  • 指针算术运算在数组遍历、字符串处理、动态内存管理等场景中应用广泛。

理解指针算术运算,是通往 C/C++ 高级编程的必经之路。建议读者在理解规则后,多动手编写和调试代码,在实践中巩固这些概念。

相关推荐
sunshine22 girl1 小时前
Java学习六 Java常见API-2--String-3-字符串的截取替换和其他方法
java·开发语言·学习
Coder-magician1 小时前
《代码随想录》刷题打卡day47:并查集-part01
数据结构·算法·图论
EatFan1 小时前
从“框架混战“到“运行时收敛“:2026 年 AI Agent 开发框架的三条路线之争
java·数据库·人工智能·多智能体·ai agent·mcp·agent 框架
垆边人似月.1 小时前
VLAN 资源池(100~200 分档)
数据结构·算法
小蒜学长1 小时前
基于SpringBoot的佳新超市管理系统设计与实现系统(代码+数据库+LW)
java·数据库·spring boot·后端·佳新超市管理系统
sunshine22 girl2 小时前
Java学习六 Java常见API-1-Radom随机数
java·开发语言·学习
潼心1412o2 小时前
C++初阶(长期更新)第6讲:类和对象(下)
开发语言·c++·算法
星光开发者2 小时前
基于决策树随机森林算法的小说推荐系统的设计与实现-计算机毕设【课程设计】78485
vue.js·算法·微信小程序·django·php·课程设计·express
wangbing11252 小时前
使用jps jstack排查系统问题
java