news 2026/8/4 14:33:06

【C语言程序设计】第29篇:指针与数组的关系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C语言程序设计】第29篇:指针与数组的关系

1 引言

考虑下面这段代码:

c

#include <stdio.h> int main(void) { int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; /* p 指向 arr[0] */ printf("arr[2] = %d\n", arr[2]); /* 使用下标 */ printf("*(arr + 2) = %d\n", *(arr + 2)); /* 使用指针运算 */ printf("p[2] = %d\n", p[2]); /* 指针也可以使用下标 */ printf("*(p + 2) = %d\n", *(p + 2)); /* 标准的指针运算 */ return 0; }

四种方式都输出30。这说明数组和指针在访问元素时可以互换使用。但它们完全等同吗?数组名是指针吗?本章将回答这些问题。


2 数组名的本质

2.1 数组名不是指针变量

数组名在大多数表达式中被当作指向数组首元素的指针,但它不是指针变量,而是一个地址常量

c

int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; /* 合法:arr 被当作指向首元素的指针 */ arr = p; /* 错误!arr 是常量,不能被赋值 */ arr++; /* 错误!arr 是常量,不能自增 */

关键区别

  • 指针是变量,可以修改(指向别处)

  • 数组名是常量,始终指向固定的地址(数组的首地址)

2.2 两个例外情况

数组名不是在任何情况下都等同于指针,有两个重要的例外:

例外1:对数组名使用 sizeof

c

int arr[5]; int *p = arr; printf("sizeof(arr) = %zu\n", sizeof(arr)); /* 20(整个数组的大小) */ printf("sizeof(p) = %zu\n", sizeof(p)); /* 8(指针本身的大小) */

例外2:对数组名使用取地址 &

c

int arr[5]; int *p = arr; int (*parr)[5] = &arr; /* &arr 是指向整个数组的指针 */ printf("arr = %p\n", arr); /* 数组首元素地址 */ printf("&arr = %p\n", &arr); /* 也是数组首地址,但类型不同 */ printf("arr + 1 = %p\n", arr + 1); /* 增加 4 字节(一个 int) */ printf("&arr + 1 = %p\n", &arr + 1); /* 增加 20 字节(整个数组) */
  • arr的类型是int*,指向第一个元素

  • &arr的类型是int (*)[5],指向整个长度为5的数组


3 通过指针访问数组元素

3.1 基本方式

假设有:

c

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

访问数组元素的几种等价方式:

访问方式含义示例结果
arr[i]数组下标arr[2] = 30
*(arr + i)指针运算*(arr + 2) = 30
p[i]指针下标p[2] = 30
*(p + i)指针运算*(p + 2) = 30

3.2 指针移动访问

c

#include <stdio.h> int main(void) { int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; /* 方式1:通过移动指针遍历 */ for (int i = 0; i < 5; i++) { printf("%d ", *p); /* 输出当前指向的元素 */ p++; /* 指向下一个元素 */ } printf("\n"); /* 注意:p 已经指向数组末尾之后,需要重新指向开头 */ p = arr; /* 方式2:通过偏移量访问 */ for (int i = 0; i < 5; i++) { printf("%d ", *(p + i)); /* p 本身不变,通过偏移访问 */ } printf("\n"); return 0; }

3.3 指针加减的步长

指针加减运算的步长由指向的类型决定:

c

char *cp; int *ip; double *dp; cp + 1; /* 地址增加 1 字节 */ ip + 1; /* 地址增加 4 字节(假设 int 占4字节) */ dp + 1; /* 地址增加 8 字节(假设 double 占8字节) */

这正是为什么*(p + i)能正确访问第 i 个元素的原因——编译器会自动根据类型计算偏移量。


4 下标运算的本质

4.1 下标运算的等价性

C语言标准规定:a[i]等价于*(a + i)

这是一个非常重要的等价关系,它意味着:

  • 下标运算是通过指针运算实现的

  • a[i]只是*(a + i)的语法糖

c

int arr[5] = {10, 20, 30, 40, 50}; /* 下面三行完全等价 */ arr[2] = 100; *(arr + 2) = 100; 2[arr] = 100; /* 是的,这也可以!但千万别这么写 */

4.2 为什么可以写成 2[arr]?

根据等价关系:

  • arr[2]*(arr + 2)

  • 2[arr]*(2 + arr)

加法满足交换律,所以*(arr + 2)*(2 + arr)是一样的。因此2[arr]在语法上是合法的,但这种写法极其不推荐,会严重降低代码可读性。

c

/* 合法但反人类的写法 */ int arr[5] = {10, 20, 30, 40, 50}; printf("%d\n", 2[arr]); /* 输出 30 */

4.3 对指针使用下标

因为p[i]等价于*(p + i),所以指针也可以使用下标:

c

int arr[5] = {10, 20, 30, 40, 50}; int *p = arr + 2; /* p 指向 arr[2] */ printf("%d\n", p[0]); /* 30,即 arr[2] */ printf("%d\n", p[-1]); /* 20,即 arr[1] —— 负下标也是合法的! */ printf("%d\n", p[2]); /* 50,即 arr[4] */

重要:指针可以使用负下标,只要结果指针仍在数组范围内。这给了我们很大的灵活性,但也要格外小心越界。


5 数组作为函数参数

5.1 数组参数的退化

当数组作为函数参数时,它退化为指针:

c

#include <stdio.h> void func1(int arr[]) /* 这里的 arr 实际上是指针 */ { printf("在 func1 中,sizeof(arr) = %zu\n", sizeof(arr)); /* 8(指针大小) */ } void func2(int *arr) /* 和上面完全等价 */ { printf("在 func2 中,sizeof(arr) = %zu\n", sizeof(arr)); /* 也是 8 */ } int main(void) { int arr[10] = {0}; printf("在 main 中,sizeof(arr) = %zu\n", sizeof(arr)); /* 40 */ func1(arr); func2(arr); return 0; }

重要结论

  • 在函数内部无法通过sizeof(arr)/sizeof(arr[0])获取数组长度

  • 必须额外传递一个参数表示数组长度

5.2 函数参数的几种等价写法

c

/* 这三种写法在函数内部完全等价 */ void print_array(int arr[], int size) void print_array(int *arr, int size) void print_array(int arr[10], int size) /* 10 被忽略,只是文档作用 */ { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); /* 或 *(arr + i) */ } }

5.3 为什么要传递长度

c

#include <stdio.h> /* 错误的尝试:试图在函数内计算长度 */ void bad_print(int arr[]) { int size = sizeof(arr) / sizeof(arr[0]); /* size = 8/4 = 2,错误! */ for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } } /* 正确的方式 */ void good_print(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } } int main(void) { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; bad_print(arr); /* 只输出前两个元素! */ printf("\n"); good_print(arr, 10); /* 正确输出全部 */ return 0; }

6 指针数组 vs 数组指针

这两个概念容易混淆,需要区分清楚:

6.1 指针数组

指针数组:数组的每个元素都是指针。

c

int *arr[5]; /* 包含5个 int* 的数组 */

c

int a = 10, b = 20, c = 30; int *arr[3] = {&a, &b, &c}; for (int i = 0; i < 3; i++) { printf("%d ", *arr[i]); /* 输出 10 20 30 */ }

6.2 数组指针

数组指针:指向整个数组的指针。

c

int (*p)[5]; /* p 是一个指针,指向包含5个int的数组 */

c

int arr[5] = {1,2,3,4,5}; int (*p)[5] = &arr; /* p 指向整个数组 */ for (int i = 0; i < 5; i++) { printf("%d ", (*p)[i]); /* 通过指针访问数组元素 */ }

6.3 区别总结

写法名称含义
int *p[5]指针数组一个数组,包含5个int*元素
int (*p)[5]数组指针一个指针,指向包含5个int的数组

7 综合示例

7.1 数组逆序(使用指针)

c

#include <stdio.h> void reverse_array(int *start, int *end) { /* end 指向最后一个元素 */ while (start < end) { int temp = *start; *start = *end; *end = temp; start++; end--; } } int main(void) { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; printf("原数组:"); for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } printf("\n"); reverse_array(arr, arr + 9); /* 传递首尾指针 */ printf("逆序后:"); for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

7.2 数组的切片访问

c

#include <stdio.h> void print_slice(int *start, int *end) { printf("["); while (start < end) { printf("%d ", *start++); } printf("%d]\n", *end); } int main(void) { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; print_slice(arr, arr + 4); /* 前5个元素 [1 2 3 4 5] */ print_slice(arr + 3, arr + 7); /* 中间5个元素 [4 5 6 7 8] */ print_slice(arr + 5, arr + 9); /* 后5个元素 [6 7 8 9 10] */ return 0; }

7.3 字符串数组与指针

c

#include <stdio.h> int main(void) { /* 二维字符数组:每个字符串存储在连续的内存中 */ char names1[][10] = {"Alice", "Bob", "Charlie"}; /* 指针数组:每个元素指向字符串常量 */ char *names2[] = {"Alice", "Bob", "Charlie"}; /* 访问方式相同 */ for (int i = 0; i < 3; i++) { printf("names1[%d] = %s\n", i, names1[i]); printf("names2[%d] = %s\n", i, names2[i]); } /* 重要区别:能否修改 */ names1[0][0] = 'a'; /* 可以修改(在栈上) */ /* names2[0][0] = 'a'; */ /* 危险!字符串常量在只读区,不能修改 */ return 0; }

8 常见错误与注意事项

8.1 混淆数组和指针的 sizeof

c

void func(int arr[]) { int size = sizeof(arr) / sizeof(arr[0]); /* 错误!arr 是指针 */ }

8.2 数组名自增

c

int arr[5]; arr++; /* 错误!数组名是常量 */

8.3 指针越界

c

int arr[5] = {1,2,3,4,5}; int *p = arr + 5; /* 指向数组末尾之后(允许) */ int x = *p; /* 危险!越界访问 */

8.4 混淆指针数组和数组指针

c

int *p1[5]; /* 指针数组 */ int (*p2)[5]; /* 数组指针 */ p2 = &arr; /* 正确 */ p1 = &arr; /* 错误!类型不匹配 */

8.5 试图返回局部数组

c

int* get_array(void) { int arr[5] = {1,2,3,4,5}; return arr; /* 危险!arr 是局部变量 */ }

9 本章小结

本章系统介绍了指针与数组的关系:

1. 数组名的本质

  • 大多数情况下被当作指向首元素的指针

  • 但它是常量,不能修改

  • 两个例外:sizeof&运算符

2. 访问元素的等价方式

  • a[i]等价于*(a + i)

  • 指针也可以用下标:p[i]等价于*(p + i)

  • 甚至i[a]也合法(但千万别用)

3. 指针加减的步长

  • 由指向的类型决定

  • p + i增加i * sizeof(指向类型)字节

4. 数组作为函数参数

  • 退化为指针

  • 必须额外传递长度

  • 三种写法等价:int arr[]int *arrint arr[10]

5. 容易混淆的概念

  • 指针数组int *p[5],数组元素是指针

  • 数组指针int (*p)[5],指向整个数组的指针

6. 核心结论

text

a[i] == *(a + i) == i[a] p[i] == *(p + i)

理解这个等价关系,就掌握了 C 语言中数组和指针的精髓。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/4 14:32:42

酵母展示技术:真核筛选平台与生物医药应用

酵母展示技术作为依托真核酵母细胞构建的高效分子筛选与优化平台&#xff0c;通过将外源蛋白或多肽锚定表达于酵母细胞表面&#xff0c;借助真核生物特有的蛋白折叠与修饰系统&#xff0c;实现靶标分子的高特异性筛选与功能优化。该技术凭借真核表达优势、高通量筛选能力及广泛…

作者头像 李华
网站建设 2026/8/4 14:32:53

SSH隧道实战:内网穿透与端口转发

SSH隧道实战&#xff1a;内网穿透与端口转发 在当今数字化时代&#xff0c;远程访问内网资源成为许多企业和开发者的刚需。由于防火墙或NAT的限制&#xff0c;直接访问内网服务往往困难重重。SSH隧道作为一种安全高效的解决方案&#xff0c;能够轻松实现内网穿透和端口转发&am…

作者头像 李华
网站建设 2026/8/4 14:32:26

如何评估大数据产品的用户满意度?

如何评估大数据产品的用户满意度?从框架到落地的全流程指南 引言:大数据产品的“隐形痛点”,你真的懂吗? 作为一名大数据产品经理,我曾遇到过这样的困惑: 我们花了6个月优化数据查询性能,把复杂SQL的执行时间从15秒压缩到3秒,以为用户会拍手叫好——结果用户反馈里,…

作者头像 李华
网站建设 2026/7/14 15:11:51

开源软件的使用贡献与社区参与经验分享

开源世界的大门&#xff1a;我的贡献与成长之旅 在数字化浪潮中&#xff0c;开源软件已成为技术发展的核心驱动力。从个人开发者到大型企业&#xff0c;无数人通过使用、改进和共享代码推动创新。作为一名长期参与开源项目的技术爱好者&#xff0c;我深刻体会到开源不仅是工具…

作者头像 李华
网站建设 2026/7/14 15:11:54

0硬件知识体系目录2021-10-12

硬件知识体系第1章 元器件基本知识1.1 电阻 RES Resistance1.2 电容 CAP Capacitance1.3 电感 IND Inductance1.4 磁珠 Bead1.5 二极管 Diode1.6 三极管 BJT Bipolar Junction Transistor1.7 场效应管 MOS Metal-Oxide-Semiconductor Field-Effect Transistor1.8 晶振 CRY Crys…

作者头像 李华
网站建设 2026/7/14 15:11:53

机器学习11:代价敏感学习

作者&#xff1a;非妃是公主 专栏&#xff1a;《机器学习》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 专栏地址 《机器学习》专栏地址 专栏系列文章 机器学习01&#xff1a;绪论 机器学习02&#xff1a;模型评估…

作者头像 李华