一维数组地址
一维数组地址一维
背景
1 | |
答案: a
深究数组地址:
一、取地址运算符 &
1 | |
如果 a 是一个 int 类型的变量,那么 &a 的结果类型是 **int \***(读作:指向 int 的指针)
怎么理解 & 符号?
你可以把它当成一个动作。
a:代表“变量里的值”(在这个房间里住着谁?)。&a:代表“变量的地址”(这个房间的门牌号是多少?)。
&a是一个地址,是什么类型的?
是 int * 而不是 int
虽然“地址”看起来像是一串数字(比如 6422300),在物理上它确实是个整数,但在 C 语言的规则里,它属于指针类型。
二、怎么打印地址?
用什么符号打印?
新知识:%p
就像用 %d 来打印整数(decimal),用 %f 来打印小数(float)一样:
%p是专门用来打印指针(Pointer)/地址的占位符。
它有什么特别的? 如果你用 %d 打印地址,它会显示成一个很长很乱的负数或整数。而 %p 会自动把地址显示成十六进制(比如 000000000061FE14),这是计算机里表示内存地址的标准方式。
1 | |
怎么用printf打印格式?
printf 函数有点“洁癖”。对于 %p 这个占位符,C 语言的标准规定:你必须给我喂一个 void \* 类型的指针。
所以要使用强制类型转换
1 | |
虽然直接传 &a(它是 int * 类型)在很多电脑上也能运行,也能打印出结果,但严格来说,类型不匹配。严谨的编译器(或者开启了严格警告模式)会报错或报警告。
所以,(void*)&a 的意思就是:
“编译器大哥,我知道 &a 是个 int 指针,但在打印这一刻,请把它当做一个通用指针(void*)传进去, 别报错了。”
当你想要打印任何变量的地址时,标准写法就是“三件套”:
- 用
%p做占位符。 - 用
&取出变量的地址。 - 用
(void\*)把地址转换成通用格式(为了不报错)。
三、数组中a和&a分别代表什么?
数值上,它们是同一个地址;但类型(Type)上,它们完全不同。
a:在大多数表达式中,它“退化”为数组首元素(第0个元素)的地址。&a:它是整个数组的地址。
1 | |
虽然它俩打印出来的十六进制数字一模一样,但在 C 语言的法律(语法规则)里,它们的身份有着云泥之别。
想象你在网上买了一箱苹果(共 5 个),快递送到你家门口了。
- 内存地址(那个数值):就是你家门口的地板坐标。
a(即&a[0]):指的是“这箱子里的第一个苹果”。&a:指的是“这整个箱子”。
问: 为什么坐标一样? 答: 因为箱子的起始位置,和箱子里第一个苹果的位置,物理上是在同一个点重合的!如果你把箱子拆了,第一个苹果就在箱子底部的最左边。
那首元素的地址到底是啥?
标准答案是 a (或者 &a[0])。
四、C语言数组退化(Array Decay)
我们弄清a和&a的区别后,会惊奇的发现a指的不是整个数组的地址,而是“退化”为数组首元素(第0个元素)的地址。
那我们在函数调用的时候,按照惯性思维,在主函数中传入的不应该是整个数组的地址&a吗?
大家只传 a(首地址)而不传 &a(整个数组指针),是因为 C 语言在处理函数传参时,有一个非常“抠门”且“赖皮”的规则,即数组退化
怎么解释呢,我的说法是C语言在被创造出来的时候,压根就没想让函数接受一整个数组
不管你在函数定义里写得多像数组:
1 | |
编译器在编译时,都会冷酷无情地把它们统统改成:
1 | |
说白了,数组退化指的就是数组退化成指针,在函数中通过地址进行对数组的操作
这意味着什么? 这意味着当你调用函数时,“箱子”的概念消失了。函数根本不知道这是一个由 5 个元素组成的整体。它只收到了一个起始坐标。
打个比方,就像让工人(函数)削苹果
惯性思维:把封装好的“苹果箱”递给工人。
C 语言的做法:你把工人带到仓库门口,指着地上的第一个苹果说:“从这开始削!”(传入
a)。- 如果你不告诉工人要削几个(传入长度),工人可能会顺着地址一直削下去,削完这 5 个,继续削后面的烂泥巴(内存越界),直到被保安(操作系统)抓走(程序崩溃)。
看看这个代码,更深层次地理解数组在函数中退化成指针:
1 | |
运行:
1 | |
为什么会“缩水”?
这再次印证了我们刚才说的原理:数组退化。
在
main函数里: 编译器非常清楚a是一个int[5]。它手里握着图纸,知道这块地盘占了 20 个字节。所以sizeof(a)返回 20。在
test_size函数里: 虽然你写的是int arr[],但编译器在编译那一瞬间,就已经把它偷梁换柱改成了int *arr。这时候,
sizeof(arr)测量的不再是那一整箱苹果,而是“存放地址的那张小纸条”(指针变量)的大小!- 在 64 位系统里,一个地址(指针)占 8 字节。
- 在 32 位系统(如大多物联网芯片)里,一个地址(指针)占 4 字节。
五、指针运算
看看这两行代码:
1 | |
运行得到:
1 | |
很明显,指针运算的+1并不是数学里,1 + 1 = 2
在 C 语言的指针世界里,1 + 1 的结果取决于你是谁。
指针的“一步”有多大?
在指针的世界里,+1 不代表“加 1 个字节”,而是代表“加 1 个单位(Item)”。或者更通俗地说:“下一个”。
这就好比:
- 对于蚂蚁(char)来说:走一步(+1)就是挪动 1 厘米。
- 对于人类(int)来说:走一步(+1)就是挪动 4 厘米。
- 对于巨人(array)来说:走一步(+1)就是挪动 20 厘米。
计算机怎么知道“步长”是多少? 就是靠类型!
a的类型是int *(指向一个整数)。电脑知道一个整数占 4 字节。所以a+1意味着:地址往后跳 4 个字节。&a的类型是int (*)[5](指向一个包含5个整数的数组)。电脑知道这个大数组总共占5 * 4 = 20字节。所以&a+1意味着:地址往后跳 20 个字节。
如果把数组改成 char a[5](char占1个字节),a+1和&a+1` 分别会增加多少字节?
- 答:1个和5个