<C和指针---读书笔记8>
数组 , 数组是C语言的一种数据类型。 我们先从一维数组讲起.
声明和初始化
type 数组名[n] : 声明了一个数组,它由n个元素组成,且这n个元素均是 type类型。
编译器进行初始化时,会为其分配 n个type型存储空间. 如int a[4];
在声明时, 编译器需要为其分配空间,所以需要知道数组的长度信息,类型信息。
int b[] ; 就是非法的。 int b[] = {1,2,3,4};是允许的。原则就是能提供长度信息与否。
数组名的纠纷
我们知道a[0]表示元素0、a[1]表示元素1. 那单字母a表示什么?按道理应该是整个数组的简写。
但实际上并非如此简单。其实,在编译器进行编译时,会有一个记录表格: 它记录了代号---地址的关系.
显然,如果仅仅是int x ; 它记录 x --- addr_880 . 以后访问x,都是去访问的880地址。
指针变量也是如此, int *p ,它也会记录 p ---- addr_990 .
但到了数组名这里就不一样了,因为数组名不是一个变量,它不拥有自己的存储领地, 但编译器仍然记录了下来对应关系.
它记录的是: 数组名 ------ 首个元素的地址 .
我们已经知道了记录的关系了,那在编译过程中,遇到数组名, 编译器将会做什么操作?
下标的引用
在普通的操作中,我们接触到的b[2]。编译器遇到这种形式: 都会先把它们翻译成间接操作*(b+2) .
为什么要翻译成间接操作呢? 因为它本质上想表达的意思是: 访问数组b的第三个元素。
因为编译器没有记录各个不同元素的地址信息,只记录了首元素的地址信息,并对标给了数组名b,所以要把这里作为突破口.
*(b+2) = *(首地址+偏移)。 很明显,编译器在执行这个b+2中,如果只是简简单单的+2,就没有任何的物理意义了。
编译器执行的将是: b + 2* sizeof (int),从而能顺利的访问 b[2]元素。
指针的介入
我们知道,指针变量,可以指向任意内容.如果我们把指针指向了 数组的第一个元素.
int arry[10] ;
int *ap = arry; (把地址存入ap变量内)
int *ap = arry +2 (把"地址+2 * sizeof (int) "存入ap变量内,即把元素2的地址存入指针变量内)
这时候,对指针进行间接访问, 均是访问的元素2.
ap |
指针常量,指向元素2 (编号从0开始) |
*ap |
左值:存入元素2内; 右值,获取元素2内的值 |
Ap[0] |
从字面上,我们觉得这是错的,因为ap是个指针啊,它又不是数组,你怎么对它进行这种操作呢? 其实:我们记得下标引用时 间接访问的一种伪装。所以这个依旧是间接访问的一种 *(arry + 0) = *ap 用作左值:存入元素2内;右值,获取元素2内的值
|
Ap+6 |
Ap是一个指针,指针的加法 = + 6*sizeof(int) 所以是指向 元素8 |
*ap+6 |
arry[2]的内容+6 |
*(ap+6) |
用作左值:存入元素8内;右值,获取元素8内的值 |
ap[6] |
同理, ap[6] = *(arry + 6) ,跟上面一样 |
&ap |
显然是获取指针变量的地址。
|
Ap[-1] |
Ap[-1] = *(arry -1 ) , 用作左值:存入元素1内;右值,获取元素1内的值 |
Ap[9] |
Ap[9] = *(arry +9) 超出了范围。是很冒风险的事 |
2[aary] |
依旧是合法的,很奇怪是吧, 2[arry] = *(2+arry) = *(arry+2) |
指针表示还是b[n]表示
我们可以通过定义一个指针,并让它指向数组的首地址,通过加减指针变量,完成访问。 也可以通过b[n]来访问数组元素。
各有利弊,b[n]看起来易读、易于维护。指针形式效率 ≥ b[n]形式
数组用作函数参数
在子函数定义时,我们时常会把一个数组当成参数的一份子.
在子函数body中,我们通常也会对其进行访问操作,使用的无非是指针,或者b[n]形式。 最终编译器都将这些操作
转换成了*(b+2) = *(首地址+偏移) 操作. 显然我们只要知道 首地址信息就够了. 即子函数定义时,我们的参数列表,
只要指定一个数组名就可以了,不必指定长度了.
在函数调用前的 函数原型声明时, 传进来的参数也是要是一个数组的首地址就够了,你可以用数组名的方式,也可以用指针的方式.