指针与数组
数据类型
在 C 中,指针是一种数据类型,数组也是一种数据类型,而且是一种和指针密切相关的数据类型。
作为一门静态强类型的编程语言,C 还很傲娇地宣称「数组」是一种派生类型。
为何?
我先列出目前拥有的知识点:
- C 程序运行时至少有两部分内存,一部分称之为「栈」,一部分称为「堆」
- C 程序运行的抽象模型本质上是一个不断递推、回归,再递归,再回归,最终返回一个值给操作系统,结束运行
- 所有的变量都需要占据内存空间
- 内存数据的索引是通过「地址进行」,内存数据就是 01 比特位,具体怎么解释是由上下文决定,这就是数据类型的来源
- 索引中使用的地址,也可以作为一种变量类型,名字叫指针
- 其它原生变量类型大多都是和数字有关,int,double,float,long,unsigned,此外还有 true 和 false,以及 null
所有内存都要尽可能地回收,以便造成程序拥有无限内存的假象。
那在 C 语言中,我们知道通过 malloc 函数分配内存,都过 free 函数释放内存。可是我们自己声明的变量,比如 int ,long 之类的,它们并没有通过 malloc 分配内存,也没有通过 free 释放内存。那这些内存是怎么释放掉的呢?
这也就是最有意思的地方了:这类变量通常比较少,而且占用内存空间通常比较少,而且生命周期还短。所以,这类变量随函数的递推被创建,随函数的回归被销毁。销毁很容易,其实就是简单的丢弃。不在函数栈内的就被认为是可用空间。因为这样子,函数栈内不同栈帧内部的变量有各自独立的运行环境,所以完全可以同名。就像北村和南庄可以各有一个王二一样,并不会引起混淆。
有意思的问题是:这个创建和销毁变量的过程,在理论上完全可以使用于所有变量类型,但为什么还要对一些变量进行特殊的区分对待呢?
理论上确实可以通用,但程序运行期间,经常会同时维护并操作大量数据,占用空间,而且耗时。
在函数栈的模型中,函数栈帧之间只能通过复制数据进行消息传递和回馈。如果所有数据都存在函数栈帧内部,那么函数栈可能会很大,不同的函数栈里可能会出现大量重复数据,就像克隆了北村里九成村民放到南庄一样。
这在现实中当然是行不通的。
所以就需要其他的实现方式。
其实很自然,我们在寄存器中维护函数栈,使用的是地址索引。同样的,在函数栈帧中维护大量数据,也可以使用地址索引。
这就是指针的中枢作用所在。
再想想,那么数组呢?数组似乎也可以很大,但是貌似也没有使用 malloc 和 free 。而我们知道,数组的名字所代表的变量其实就是指针变量,通过这个指针变量索引数组元素。
既然没有使用 malloc ,数组的创建和销毁具体如何实现,就不再是我们 C 语言的用户所能控制的了,我们可以猜测,猜的对不对甚至都不再重要,大部分时候。这也是抽象的好处吧。
字符串其实是一种特殊的字符数组
在英语国家里,只有二十六个字母,外加其他一些符号。一个字节完全可以有足够的表示能力来表征它们。换句话说,一个个字符可以用编码好的一个个字节来表示,在特定的上下文中,一个个字节,一个个二进制数,就可以表示字符。
只是后来,互联网国际化以后,单字节绝对不够用,各国一开始是自行解决,后来才慢慢开始统一标准。
指针自指,数组自包含
指针指向其他变量,而指针本来就是一种变量,所以指针可以指向指针。
数组是一系列同类型变量,而数组也可以作为一个同类型变量成一系列,所以就有了数组的数组。
同理,可以有一个指向「指向指针的指针」的指针,也可以有一个「数组的数组」的数组。
数组是序列,而指针才是组织起所有数据的中枢,函数本来也是数据。
所以,C 程序就是由指针组织起来的,就像指针组织起机器代码一样。
不知是该恭喜,还是该怎样,总之阅读到该文的,你是第 人。每一次刷新,都是不同的自己。