C: 指针与数组

数组和指针就像同一个人的正面和侧面——从不同角度看,它们呈现出不同的形态,但底层指向同一片连续的内存空间。

1. 数组名即首元素地址

数组名在大多数表达式中会自动转换为指向首元素的指针:

C
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;

printf("%p\n", (void *)arr);
printf("%p\n", (void *)&arr[0]);
printf("%p\n", (void *)p);

三个地址完全相同。arr&arr[0]等价,都是首元素的地址。

但也有例外:sizeof(arr)返回整个数组的大小,而非指针大小。&arr是指向整个数组的指针,类型是int (*)[5],步长是整个数组。

C
printf("%zu\n", sizeof(arr));
printf("%zu\n", sizeof(p));

在64位系统上分别输出20(5×4字节)和8(指针大小)。


2. 指针遍历数组

既然指针可以指向数组元素,就能通过指针运算遍历整个数组:

C
int arr[] = {10, 20, 30, 40, 50};
int *p;
int len = sizeof(arr) / sizeof(arr[0]);

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

arr + len指向数组末尾之后的位置,作为循环终止标志。这是C语言中非常常见的模式。

也可以用下标方式:

C
int i;
for (i = 0; i < len; i++) {
    printf("%d ", *(arr + i));
}

或者:

C
int *p = arr;
int i;
for (i = 0; i < len; i++) {
    printf("%d ", p[i]);
}
💡 p[i]*(p + i)完全等价。下标运算是指针算术的语法糖。


3. 指针算术运算

指针支持有限的算术运算:

(1) 加减整数

C
int arr[] = {10, 20, 30, 40, 50};
int *p = arr + 2;

printf("%d\n", *p);
printf("%d\n", *(p + 1));
printf("%d\n", *(p - 1));

输出30、40、20。p+1向后移动一个int,p-1向前移动一个int。

(2) 指针减指针

同一数组内两个指针相减,得到元素个数差:

C
int *q = &arr[4];
printf("%ld\n", (long)(q - p));

输出2,因为p指向arr[2],q指向arr[4],相差2个元素。

(3) 指针比较

同一数组内的指针可以用<<=>>=比较位置前后:

C
int *start = arr;
int *end = arr + len;
while (start < end) {
    printf("%d ", *start);
    start++;
}
⚠️ 指针算术仅在同一数组内有意义。跨数组做指针减法或比较是未定义行为。

(4) 指针步长总结

类型 步长(p+1移动字节数)
char * 1
int * 4
double * 8
int (*)[5] 20

步长 = sizeof(指向的类型)


4. 指针与数组的等价关系

以下两种访问方式完全等价:

表达式 等价形式
arr[i] *(arr + i)
&arr[i] arr + i
p[i] *(p + i)
&p[i] p + i

编译器将下标运算转换为指针运算。arr[3]3[arr]在C语言中甚至都合法(因为*(arr+3) == *(3+arr)),后者只是没人这么写。

但指针和数组有本质区别:

C
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;

▶ 示例

用指针实现数组逆序:

C
#include <stdio.h>

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--;
    }
}

int main(void) {
    int data[] = {1, 2, 3, 4, 5, 6, 7};
    int len = sizeof(data) / sizeof(data[0]);
    int i;

    reverse(data, len);

    for (i = 0; i < len; i++) {
        printf("%d ", data[i]);
    }
    printf("\n");
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
7 6 5 4 3 2 1

双指针法:left从头向尾移动,right从尾向头移动,交换它们指向的元素,直到相遇。


5. 数组作为函数参数的本质

当数组传给函数时,编译器只传递首元素地址,数组长度信息丢失。这是"数组退化为指针":

C
void func(int arr[]) {
}

参数int arr[]等价于int *arr。在函数内部:

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

四种等价写法:

C
void func(int *arr, int len);
void func(int arr[], int len);
void func(int arr[5], int len);
void func(int arr[100], int len);

方括号中的数字被编译器忽略,只表示"这是一个指针"。

▶ 示例

用指针实现数组去重(已排序数组):

C
#include <stdio.h>

int unique(int *arr, int len) {
    if (len == 0) return 0;

    int *dst = arr;
    int *src = arr + 1;
    int *end = arr + len;

    while (src < end) {
        if (*src != *dst) {
            dst++;
            *dst = *src;
        }
        src++;
    }

    return (int)(dst - arr + 1);
}

int main(void) {
    int data[] = {1, 1, 2, 2, 2, 3, 4, 4, 5};
    int len = sizeof(data) / sizeof(data[0]);
    int new_len = unique(data, len);
    int i;

    for (i = 0; i < new_len; i++) {
        printf("%d ", data[i]);
    }
    printf("\n");
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
1 2 3 4 5

dst记录去重后的写入位置,src扫描原数组。返回新长度。这是原地去重算法,不需要额外数组。

❓ 常见问题

Q 数组名能赋值吗?
A 不能。arr = p;非法,数组名是常量指针。但p = arr;合法,指针变量可以赋值。
Q arr&arr有什么区别?
A arr是首元素地址,类型int *,步长sizeof(int);&arr是整个数组的地址,类型int (*)[5],步长sizeof(int[5])。数值相同但类型不同。
Q 为什么函数内不能sizeof数组?
A 数组传参时退化为指针,sizeof得到指针大小(4或8),不是数组大小。长度必须单独传递。
Q p[-1]合法吗?
A 语法合法,等价于*(p-1)。如果p指向数组中间,p[-1]访问前一个元素是安全的。但如果超出数组范围则是越界。

📖 小节

📝 作业

  1. 编写函数用指针方式实现数组元素左移一位(第一个元素移到末尾),不使用额外数组。
  2. 编写函数int *find(int *arr, int len, int target),返回第一个等于target的元素指针,未找到返回NULL。
  3. 给定一个已排序数组,用指针实现二分查找,找到返回元素指针,未找到返回NULL。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏