指针运算
指针运算是指针特有的操作,包括加减整数、指针相减、指针比较等。指针运算的步长由所指向类型的大小决定,这让指针遍历数组变得自然高效。但指针运算也带来风险——越界指针运算可能导致未定义行为。
指针加减整数
指针加减整数时,实际移动的字节数是整数乘以所指向类型的大小:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; /* p 指向 arr[0] */
printf("%d\n", *p); /* 10 */
p++; /* p 指向 arr[1],移动 sizeof(int) 字节 */
printf("%d\n", *p); /* 20 */
p += 2; /* p 指向 arr[3] */
printf("%d\n", *p); /* 40 */
p -= 3; /* p 指向 arr[0] */
printf("%d\n", *p); /* 10 */
char str[] = "Hello";
char *cp = str;
cp++; /* 移动 1 字节 */
printf("%c\n", *cp); /* e */
指针相减
两个指向同一数组的指针相减,结果是它们之间的元素个数:
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[1]; /* 指向 20 */
int *p2 = &arr[4]; /* 指向 50 */
ptrdiff_t diff = p2 - p1; /* 3(元素个数) */
printf("%td\n", diff); /* 3 */
ptrdiff_t 是 <stddef.h> 定义的有符号整数类型,用于表示指针差值。
指针比较
指向同一数组的指针可以比较:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
while (p < arr + 5) { /* 比较指针 */
printf("%d ", *p);
p++;
}
/* 输出:10 20 30 40 50 */
不同数组的指针比较是未定义行为:
int a[5], b[5];
if (a < b) /* 未定义行为! */
/* ... */
指针与数组下标
arr[i] 等价于 *(arr + i),这是 C 语言数组访问的本质:
int arr[5] = {10, 20, 30, 40, 50};
printf("%d\n", arr[2]); /* 30 */
printf("%d\n", *(arr + 2)); /* 30 */
printf("%d\n", 2[arr]); /* 30 —— 等价于 *(2 + arr) */
2[arr] 是合法的 C 语法(虽然不推荐这样写),因为 arr[2] 等价于 *(arr + 2) 等价于 *(2 + arr) 等价于 2[arr]。
常见错误
指针运算越界:
int arr[5];
int *p = arr + 10; /* 越界指针,但未解引用时可能不崩溃 */
*p = 100; /* 未定义行为! */
/* 危险:p 在数组末尾之后 */
int *end = arr + 5;
*end = 100; /* 未定义行为! */
void 运算*:
void *vp = arr;
vp++; /* GCC 扩展:移动 1 字节 */
/* 标准 C:void* 不能运算 */
/* 正确 */
char *cp = (char *)arr;
cp++; /* 移动 1 字节 */
指针相减类型不匹配:
int arr[5];
int *ip = arr;
char *cp = (char *)arr;
ptrdiff_t diff = ip - cp; /* 未定义行为:类型不匹配 */
最佳实践
- 指针运算只在同一数组内进行
- 用
<而非!=作为循环条件(更安全) void*不运算,先转为char*- 指针相减只用于同类型指针
- 注意指针运算的步长是
sizeof(指向类型)