一、数组名
1、一个误区:数组名并不表示整个数组,它大多时候只是一个指针常量,指向数组的第一个元素,它的类型取决于数组元素类型。这一点解释了为什么C语言的数组不能整体拷贝,只能循环拷贝赋值。如果你将数组名赋值给另一个数组名,例如,int a[10] ; intb[10]; a = b; ,就已经错的一塌糊涂:
1)数组名是一个常量指针,它不可以被赋值;
2)就是上面提到的数组不能整体拷贝,因为你得到的仅仅是一个指向数组第一个元素的指针常量而已。
注:个人认为这两点其实本质上是一个意思。即,假如数组名不是一个指针常量而是表示整个数组,那么它可以整体赋值。
2、数组名大多时候是一个常量指针,但是有且只有两种情况,数组名不用指针常量来表示:
1)作为sizeof的表达式的时候,数组名表示整个数组,sizeof会返回整个数组占用的字节数;
2)当数组名作为操作符&的操作数时,会产生一个指向该数组的指针。
二、数组的下标引用
1、当用下标来访问数组时,下标值可以为任意整数值,可以为负,但是程序员自己必须保证访问的有效性,即不能跳出数组的前边界;下标值也可以大于数组实际长度,这通常用来实现不对称边界,但是要避免对超出数组有效长度的地址进行访问(只要地址,不要值)。
2、C语言并不会对数组下标有效性进行安全检查,C语言标准也并没有规定编译器必须对数组下标进行有效性检查,对数组下标的有效性检查会大大增加程序运行时间和空间的开销。也就是说保证数组访问不越界的工作应该交由程序员来做。
3、a[2] 和2[a] 都是合法的,并且具有相同的效果,这缘于C语言实现下标的方式,因为在C语言中对下标的访问都可以转换为对等的间接访问表达式。a[2] 、2[a]都会被编译器转换为*(2 + a),这两种下标书写形式对编译器来说并无差异。当然,如果你不是在参加混乱代码大赛,在代码可读性上没有人会喜欢2[a]这种书写方式。
三、指针与下标
1、加入下标和指针都可以实现某种需要,下标一定不会比指针更有效率,并且有时候指针比下标更有效率。
2、指针比下标更有效率的场合:当在数组中循环变量一次以固定步长移动时,固定数字与固定数字的相乘是在编译期间完成的,不会占用运行时间,更有效率一些。
3、一个高效率的数组的拷贝, 将数组y的元素赋给数组x
//#define SIZE 10
int x[size], y[size] ;
void array_to_array(void)
{
register int *p1, *p2;
for (p1=x, p2=y; p1<&x[SIZE]; ) {
*p1++ = *p2++;
}
}
四、数组和指针
1、数组和指针都具有指针值,都可以进行间接访问和下标引用操作,但是需要注意的是他们并不等价。声明一个数组时,编译器首先根据数组元素个数为该数组分配内存,然后再创建数组名,它是一个指针常量,指向数组的第一个元素。声明一个指针变量时,编译器只为该变量分配4个字节的内存。
2、当一个数组名作为参数传递给一个函数时,该数组名退化为指针,此时,在函数内部对其sizeof时,得到的结果是4(32位机器),
3、编译器允许数组不完整初始化,但只允许顺序省略后面的初始值,被省略的初始值全部为0;例如,int a[10] = {1,2}; a[0] = 1, a[1] = 2, a[2]~a[9]全部为0。另外,对于全局数组,默认初始值都是0, 但是如果对于一个局部数组,并且所有元素都没有初始化时,其所有元素初始值都是不确定的。
4、在数组声明时,允许编译器自动计算数组长度,例如,int a[ ] = {1, 2, 3, 4, 5};
5、字符数组的初始化,
方法1、char a[ ] = {‘h’, ‘e’, ‘l’, ‘l’, o”, ‘\0’}; 只适用于较短的字符数组初始化
方法2、char a[ ] = “hello”; 注意这种初始化方法与字符串常量的区别:当它用于初始化一个字符数组时,它就是一个字符数组的初始化列表,其他的任何情况都是字符串常量。
五、多维数组
1、int a[3][4]; 可以将a看作一个具有三个元素的一维数组,只不过每个元素又是一个具有4个元素的一位数组;
同样,int a[2][3][4];将a看作一个具有两个元素的一维数组,其中每个元素又是一个具有三个元素的一维数组,而这三个元素的每一个又是一个具有4个元素的一维数组。可见,多维数组可以看作是很多一维数组的嵌套集合。
2、多维数组在内存中的存储顺序
在C语言中,多维数组元素的存储顺序按照最右边的下标率先变化,这称为行主序。
3、指向数组的指针
例如:int a[3][4], *(p)[4] = a; 此时p指向该二维数组的第一行,p+1指向第二行,每一行是一个具有4个元素的数组,此时p就是一个指向数组的指针。
特别注意:若p初始化为int *p = a; 则是错的,因为p是一个指向整型的指针,而二维数组名a是一个指向二维数组第一行的一维数组的指针常量,也就是说a是一个指向数组的指针,相当于二级指针,用一个二级指针初始化一个一级指针,显然不合适。此时应该使用行指针变量,即int (*p)[4]才正确,要注意,除了第一维声明为指针p外,其他各维必须明确用下标指定。 若将行指针变量定义为int (*p)[ ]; 此时p仍然是一个指向一个一维数组的行指针变量,但是此时一维数组的长度却没有指定,于是当p指针参与运算时,它指向的数组将会被视为空数组,即数组长度为0,计算指针p的步长时将与0相乘,这种情况可能不是你想要的,应该避免。
4、多维数组作为函数参数
例如有一个二维数组: int a[3][4];
a作为参数传递给函数: func(a);
函数func的声明形式为: void func(int p[ ][4]) 或者void func(int (*p)[4])
注:要区别void func(int **p) ,这种声明形式是错误的,p是一个真正的二级指针,即p被声明为一个指向整型的指针的指针。
5、多维数组的长度自动计算
注意,在多维数组中只有第一维的长度才能自动计算,其余各维必须显示指定。