news 2026/10/22 7:25:47

【C语言】低级指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C语言】低级指针

学C语言,指针应该是大多数人绕不过去的一座大山,以下是我在指针学习时遇到的问题及解答,欢迎提出见解
• 为什么&arr和arr打印出来的地址一样,加 1之后却不一样了?
• 在推导((arr+i)+j)的过程中,明明 *(arr+i)前面带了个解引用 *,为什么它拿到的还是一个地址,而不是具体的数值?
• 二级指针到底存了谁的地址?
• 为什么 *intarr[3] 这种明明是一维的指针数组,用起来(比如 arr[i][j])却跟二维数组一模一样,底层全是指针?
• 为什么在调用函数时,明明传了地址给形参,把指针也互换了,结果两个数却没换成功?

一、数组名 arr 与取地址 &arr

在数组学习中刚接触到这个概念时,我只知道arr 和 &arr 打印出来的内存地址是相同的。但它们的类型和“步长”不同,这是我不能理解的,在指针学习完之后,我开始逐渐理解这两个概念。在具体使用场景:
int arr[3]={1,2,3};
printf(“%p\n”,&arr);中的 &arr类型是int*[3]表示指向“包含3个int的数组”指针,步长:3*4=12
printf(“%p\n”,arr);中的 arr类型是int* 表示指向“包含1个int”的指针,步长:3*1=3

对比项arr(数组名)&arr(取地址)
内存地址与 &arr 相同(指向数组首元素)与 arr 相同(指向整个数组)
类型int*(退化为指向首元素的指针)int (*)[10](数组指针,不退化)
步长一个元素的大小(4 字节)整个数组的大小(40 字节)
arr + 1指向第 2 个元素(跨 4 字节)指向数组末尾之后(跨 40 字节)

通俗比喻:
• arr + 1:你拿到了第一个房间的钥匙,往前走一步,你只到了第二个房间(跨了1个元素,4字节)。
• &arr + 1:你拿到了整栋楼(10个房间)的产权证,往前走一步,你跨过了整栋楼[跨了整个数组(10个元素),40字节]。
核心: 地址值相同,类型大不同;arr 走一步跨一房,&arr 走一步跨一楼。

*二:为什么(arr+i) 拿到的不是数值?(解引用的降维)

我们来对arr[i][j]的底层逻辑进行展开:

// 由于 arr1 为数组名,以数组名进行计算时,退化为指向首元素的指针;// 而数组 arr 的数据类型为 int*,也正是对应了数组内存储的是三个一维数组首元素的内存地址。int*arr[3]={arr1,arr2,arr3};for(inti=0;i<3;i++){// 内层循环:根据每个一维数组自身的长度遍历for(intj=0;j<lenArr[i];j++){// 这里的 arr 在计算时退化为二级指针,类型为 int**。// arr[i][j]底层等价于 *(*(arr+i)+j)printf("%d ",arr[i][j]);/* 为什么 *(arr+i) 拿到的是首地址而不是具体的值? 因为 arr 本身就为指针,运算时再度退化为二级指针 (int**)。 拆解底层运算 *(*(arr+i)+j) 的四个步骤: 1. arr + i :找到第 i 个指针的位置 2. *(arr + i) :拿到第 i 个一维数组的首地址(解引用了一次,但依然是地址) 3. *(arr + i) + j :在那个一维数组的首地址上,往后移动 j 个 int 的距离 4. *(*(arr + i) + j):把具体的数字打印出来(第二次解引用,终于拿到了具体数值) */}printf("\n");}

对于指针数组 arr,它在参与计算时会退化为二级指针(int),所以*(arr+i)这里还是内存地址。但此前我的错误认知来源于认为它还是一级指针,所以解引用只是剥掉一层指针的壳。剥完一层是啥,完全取决于指针本身是什么类型**:
• int* 剥掉一层是 int(普通数据)。
• int** 剥掉一层是 int*(依然是地址)。

这里补充一下【指针数组】和【数组指针】 的概念:

指针数组:存放指针的数组intp[2]={arr1,arr2};*
数组指针:指向数组的指针 *int(p)[5] = arr;

三:二级指针到底存了谁的地址?

之前我对它的理解总是自相矛盾,现在用“房间与纸条”的比喻,彻底理顺了:
• 变量 a(存数据10):是一个房间,里面放着真实的数据 10。
• 一级指针 p(存 a 的地址):是一个房间,里面放着一张纸条1,写着“房间A的地址”。
• 二级指针 pp(存 p 的地址):是一个房间,里面放着一张纸条2,写着“房间P的地址”。
所以,二级指针 pp 存储的是“一级指针变量 p 自身的内存地址(&p)”。

四:看似二维数组的“一维指针数组”

当你看到这段代码时:
int arr1[3] = {1, 2, 3};
int arr2[5] = {1, 2, 3, 4, 5};
int *arr[2] = {arr1, arr2};
是否会认为这里的arr是一个二维数组?
但是它不是真正的二维数组,,它是一维指针数组。(真正的二维数组 int arr[3][5] 在内存中是连续排列的矩阵)
• 右边:arr1、arr2 作为右值参与计算,自动退化为指向各自首元素的指针。
• 左边:intarr[2] 规定了 arr 里面存的元素类型必须是 int(指针)。
• 结果:arr 内部存的是 arr1、arr2 这些一维数组的首地址。 虽然它是一维的,但因为它存了其他数组的地址,所以你可以用 arr[i][j] 这种类似二维数组的语法去访问。

五:函数调用中,为什么交换了指针却没用?

在学习函数传参时
int a=3;
int b=5;

错误示范:
void swap(int *p, int *q) {
int *t;
t = p;
p = q;
q = t;
}
// 调用 swap(&a, &b) 后,a 依然是 3,b 依然是 5。

为什么失败?
C语言函数传参是值传递。当你调用 swap(&a, &b) 时,实际上是复制了两份地址,交给了局部变量(形参)p 和 q。此时 p 拿着 a 的地址复印件,q 拿着 b 的地址复印件。
错误的代码仅仅是把 p 和 q 手里拿的“复印件”互换了。函数内部完全没有用到,这意味着你根本没有通过地址去访问具体的数值。函数执行完毕,p、q、t 这些形参随着作用域结束被销毁,主函数里的 a 和 b 毫无变化。
正确示范:
void swap(int *p, int *q) {
int t;
t = *p;
*p = *q;
*q = t;
}
// 调用 swap(&a, &b) 后,成功交换!

*为什么成功?
虽然 p 和 q 依然是地址的复印件,但只要我们在它们前面加上(解引用),就相当于拿着复印的地址,去访问具体的数据,并且换了位置。主函数中 a 和 b 所在的内存区域被永久改变了。即便函数调用完,形参 p 和 q(复印件)消失了,但实参 a 和 b 的值已经被成功交换。

在上述初级指针的学习中,我总结了以下感悟:

  1. 遇到多级指针或者函数传参,直接画方块,画箭头。谁指向谁,谁就拷贝了谁。
  2. 类型决定了命运。 指针的加减运算(步长)、解引用拿到的结果,全部由它的数据类型决定。
  3. 解引用的本质是“降维”。
  4. 作用域是对局部变量的封印。 函数内部永远无法直接改变外部传入的普通变量。指针的真正作用在于:跨作用域去修改真实内存的数据。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 7:25:46

4、Python 内置容器类型:list、tuple、set、dict 详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:25:19

高并发登录接口性能测试全解析

功能模块性能测试用例验证 功能模块上线前&#xff0c;性能测试用例是保障系统稳定性的关键环节。本文以高并发接口为例&#xff0c;演示性能测试用例的设计、执行与验证流程。 一、测试目标与场景设计 被测模块&#xff1a;用户登录鉴权接口 /api/login&#xff0c;核心指标如…

作者头像 李华
网站建设 2026/10/22 7:24:24

python杂识--24--Nuitak打包exe

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:21:40

【脚本】 【Linux】重命名网卡

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:19:57

Lightpanda 无头浏览器完整上手指南:1/16 内存跑满 JS 渲染页面

Lightpanda 无头浏览器完整上手指南&#xff1a;1/16 内存跑满 JS 渲染页面 【免费下载链接】browser Lightpanda: the headless browser designed for AI and automation 项目地址: https://gitcode.com/GitHub_Trending/browser32/browser Lightpanda 是一个用 Zig 从…

作者头像 李华