记录指针和数组直接的关系!

当前C语言环境:

Apple LLVM version 8.0.0 (clang-800.0.38)
Target: x86_64-apple-darwin15.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

一维数组

数组名

数组名的值是一个指针常量,也是数组第一个元素的地址。

注意⚠️
数组具有确定的数量的元素,而指针只是一个标量值,_只有数组名在表达式中使用时,编译器才会为他参数一个指针常量(意思就是你不能修改这个指针的值)_

指针常量所指向的是内存中数组的起始位置,如果修改这个指针常量,唯一可行的操作就是把整个数组移动到内存的其他位置,但是在程序完成链接之后,内存中数组的位置是固定的,所以当程序运行时,在想移动数组就晚了。

数组名不作为指针常量来表示的两种场景

注意:这里说的是不作为指针常量。

  • 1.数组名作为sizeof操作符:
    sizeof返回整个数组所占用的字节,而不是每个元素所占用的字节,也不是指向数组的指针的长度。
  • 2.数组名用于单目操作符&时:
    返回一个指向数组的指针,而不是一个指向指针常量的指针。
int a[10];
int *c;
c = &a[0];c = a;//这里赋值的是一个指针的拷贝。

下标引用和指针(间接取值操作)

array[3],*(array + 3)出了优先级之外,下标引用和间接访问完全相同。

int array[10];
int *ap = array + 2;

  • 1.ap:这是一个指针地址,所以该表达式和&array[2]或者array+2相同。
  • 2.*ap:间接访问,与array[2]*(array + 2)相同。
  • 3.ap[6]:C的下标引用和间接访问表达式是一样的,所以ap[6]和*(ap + 6)相同,与array[8]*(array + 8)相同。
  • 4.ap + 6:ap为一个指针地址,地址向后偏移6,则ap + 6&array[8]array + 8相同。
  • 5.*(ap + 6):上面说过ap+6为一个地址,由此可以得出*(ap+6)为间接求得地址对应的值,它和array[8]*(array+8)相同,其实它和2>.类似。
  • —>6.ap[-1]:使用-1的偏移量使得道它前一个元素,也就是array[1]或者*(array + 2 - 1)

上面说这么多,其实只要抓住:当前表示的地址,元素还是间接获取操作就可以了。

注意⚠️:
上面提到的“C的下标引用和间接访问表达式是一样的”

思考题:

2[array],这个表达式中的array是上文中的array。在这个上下文环境中,2[array]表达的意思是什么:

解答:

因为前面我一直在强调,下标引用和间接表达式求值是相同的,所以:

  • 第一步:我们可以把2[array]改写成*(2 + array)
  • 第二步:由于加法运算符的两个操作数是可以交换位置的,所以把上面的表达式改写为:*(array + 2);
    也就是说2[array]其实和array[2]是相等的。
关于指针间接操作符和下标操作的比较

相对于指针的间接访问和下标操作,在可读性方面下标的方式更好,但是在执行效率上面_下标不会比指针更有效率,但是指针有时候比下标效率更高_

具体的效率比较为:

  • 1.当你根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率更高的代码。
  • 2.声明为寄存器变量的指针通常比用于静态内存和堆栈中的指针效率更高。
    关于如何将变量声明为寄存器变量,我们可以使用register关键字来声明,比如:register int *p1;register:这个关键字请求编译器尽可能的将变量存在CPU 内部寄存器中而不是通过内存寻址访问以提高效率。register 变量必须是一个单个的值,并且其长度应小于或等于整型的长度。而且register 变量可能不存放在内存中,所以不能用取址运算符“&”来获取register 变量的地址。
  • 3.像&array[2]或者array+2这种在运行时求值的常量表达式代价很高。

数组的初始化

数组的初始化需要一系列的值,例如:

int array[5] = {1,2,3,4,5};//如果在初始化的时候,初始化的个数比数组的大小小的话,空余的元素将会被赋值为0。
int array[] =  {1,2,3,4,5};//如果在声明中没有给出数组的长度,编译器会把数组的长度设置为刚好能够容易所有初始值的长度。
对于静态数组:存储于静态内存的数组只初始化一次,当程序执行时,静态数组已经初始化完毕。如果数组没有被初始化,数组元素的初始值将会自动设置为0。
对于自动变量:由于自动变量位于运行时堆栈,所以自动变量在缺省的情况下是未被初始化的。
所以这里需要思考的是:当数组的初始化在一个函数中,程序在每次进入该函数的时候,是否值得每次都对该数组重新进行初始化。如果不需要的的话,我们可以把该数组声明为static

指定初始化器(c99)

只初始化数组中的指定元素,方法是:在初始化列表中使用带方括号的下标指明待初始化的元素。

1
int array[5] = {[5] = 23};//把array[5]初始化为23

多维数组

int xy[3][2]数组维数不止一个的称为多维数组
数组xy[3][2]在内存中的存储顺序为:

(0,0) (0,1)  (1,0) (1,1)  (2,0) (2,1)//多维数组中的元素存储顺序按照最右边的下标率先变化的原则,称为__行主序__。
关于是把第一个下标(上面定义的数组xy中的3)解释为行还是解释为列,都可以的,只要你在使用这个数组的时候使用同一种就可以。如果你把第一个下标解释为行第二个下标解释为列,那么当你按照存储顺序逐个访问数组元素时,所获得的元素是按行排列的,相反则是按列排列的。

数组名和下标

int xy[3][2],数组名xy是一个指向包涵2个整型元素的数组的指针(指向数组的指针)。
下面我们来看看数组的下标和指针之间的关系:

  • 1.xy:在三个包含两个整型元素的数组中,xy为指向第一个子数组。(注意:这是指针,说明了指向)
  • 2.xy + 1:在三个包含两个整型元素的数组中,xy为指向第二个子数组,+1是根据包含2个整型元素的数组长度进行调整的,所以是指向第二个子数组。(注意:同上,这是指针,说明了指向)
  • 3.*(xy + 1):获取指向第二个子数组的指针,通过间接操作符得到这个子数组,或者可以把该表达式写为xy[1](即表示的是第二个子数组)。
  • 4.*(xy + 1) + 1:在第三点中我们取得了第二个子数组,记得我们在上面讲一维数组的时候,获取数组第n个元素的地址的办法是:array + n或者&array[n],在和*(xy + 1) + 1进行对比之后不难发现,其表达的意思就是获取第二个子数组中第2个元素(因为数组下标是从0开始)的地址或者可以把该表达式写为xy[1] + 1(注意:同上,这是指针,是一个地址)
  • 5.*(*(xy + 1) + 1):由间接操作符,获得第4点中的指针指向的具体元素。同上我们可以写为:*(xy[1] + 1),进一步改写xy[1][1];

指向数组的指针

int vector[10];
int matrix[3][4];
int \*vp = vector;
int (\*mp)[10] = matrix;//下标引用的优先级是高于间接引用的,所以我们需要在间接引用这里手动加上括号才行
所以mp是一个指向整型数组的指针,vp是指向整型变量的指针。

这里需要注意一下:
一定不要把指向数组的指针和指针数组浓混了,那个确保算术优先级的括号是很重要的,int (*mp)[10]代表的是指向数组的指针,而int *mp[10]表示的是指针数组。

当数组作为函数的参数的时候,多维数组和一维数组相同,都是用指针作为参数进行传递,这个指针是指向数组第一个元素的指针

一维数组作为函数参数的函数声明形式

实参:int vectors[10];
函数声明:void vector(int *vec);或者void vector (int vet[])

多维数组作为函数参数的函数声明形式

实参:int matrixs[3][10];
函数声明:void matrix(int (*mat)[10]);或者void matrix(int mat[][10]);void matrix(int mat[][10]);
这里的关键在于编译器必须知道第二个及以后各维的长度才能对各下标进行求值,因此在原型中必须声明这个维的长度

关于多维数组初始化
  • 第一种方式:

    int xy[3][2] = {1,2,3,4,5,6};
    这种就是在声明变量的时候就在后面跟着一长串的初始数据。

  • 第二种方式:

    int xy[3][2];
    xy[0][0] = 1;
    xy[0][1] = 2;
    ...
    xy[2][0] = 5
    xy[2][1] = 6;
    这和第一种的差别就在于,这一种是对数组元素一个一个的去赋值,这种存储顺序是以最右边下标率先变化的(前面注释提到的行主序)。

  • 第三种方式:
    因为多维数组可以看成是每个元素由一个子数组组成,所以我们可以按照一个子数组为一个个体来初始化。

    int xy[3][2] = {
    {1,2},
    {3,4},
    {5,6}
    };
    
    这样看起来和第一种的方式是有点类似的。

总结

  • 1 .数组变量是const指针,所以是不能被赋值的;
  • 2.如果指针指向的不是一片连续的空间,那么对地址进行加操作(或者减操作)是没有意义;