声明
C语言中只有一维数组,其元素可以是任何类型的,也可以是另外一个数组,如:
int array[5][10];
该表达式声明了一个array一维数组,其元素个数是5,元素类型是一个含有10个整型元素的数组。
数组的大小必须在编译期作为一个常数确定,如程序中存在如下代码:
int buf_size = 512;
int buf[buf_size];
可能会导致编译出错(没问题,也不推荐这样写),改成如下可编译通过
const int buf_size = 512;
int buf[buf_size];
最好是将buf_size定义成一个宏,如要动态申请,则用malloc之类的函数。
对于一个数组,你需要知道的是该数组的大小,及其首地址,即数组下标为0的元素的指针,亦数组名所代表的地址。
其有关的数组操作,哪怕看上去是通过下标去操作的,实质也是通过指针进行的。
初始化
这里就说下二维数组的初始化(要转换其实质是一维数组),如下:
int array[2][4] = {0} // 将整个数组初始化为0
int tmp[2][4] = {{1,2}, {3,4}}
一维数组的初始化用{}表示,首先tmp是一维数组,先在最外层使用{},其元素是含4个整型的数组,所以内部每个元素也用一个{}表示,则{1,2}用于初始化tmp[0],那么1用于初始化tmp[0][0], 2用于初始化tmp[0][1],tmp[0][2]和tmp[0][3]则初始化为0。
数组名
数组名作为sizeof的操作数,则是用于计算该数组的大小,其余情况都转换成起始元素首地址。
对数组名进行取地址操作,如&array,这种写法在ANSI C中是非法的,或者就等于array。
数组操作,亦理解指针的操作
先从简单的一维数组介绍:
int array[10];
array是一个含有10个元素的一维数组,其元素是一个整型数据。
要访问第5个元素,数组下标操作就是array[4],指针操作则是 *(array+4)。
那如何声明一个指针指向该数组呢?
声明一个指针就是要确定该指针的类型,也就需要明白所指向的数组元素类型是什么,对于上述一维数组,其元素类型是int整型数据,那么指针的类型就是int整型:
int *p = array;
如要访问第5个元素,相应的操作就是 *(p+4)
int array[2][2] = {{1, 2}, {3, 4}};
那么如何声明一个指针指向该二维数组呢,通过上述理解,亦就是找出该array一维数组的元素类型是什么,其类型是含有2个元素的数组,则声明指针如下:
int (*p)[2];
p = array;
那么该二维数组中的值2所对应的读取操作是 *((*p)+1), 而值4所对应的读取操作是 *(*(p+1)+1) 。
这里需要重点理解的是 指针加1操作后指针指向了哪? 我在这里有吃过不少亏! 指针加1操作所移动的步长是该指针类型的长度,在该例子中p+1指向了p所指向地址的后8个字节(2*sizeof(int))! 在平常类似的指针操作中都要注意!
作为参数的数组声明
前面有提到数组名除作sizeof的操作数外,其他情况都会转换成数组第一个元素首地址,所以像下面这样的写法:
int fun(int array[], int array_size)
{
// do something
}
与下面的写法是一样的:
int fun(int *p_array, int array_size)
{
// do something
}
int fun(int (*p_array)[10], int array_size)
{
// do something
}
也可以写成如下形式:
int fun(int **p_array, int array_size)
{
// do something
}
数组在栈中的分配
变量在栈中的分配是由高到低进行的,如进行如下声明:
int i = 0;
int a[3];
for (int i = 0; i <= 3; i++)
{
a[i] = 0;
}
那么就会进入死循环,当i = 3时,a[3] = 0; 相当与 i = 0。 因为a[3]所指向的地址就是i。
可以printf(" &i = %p", &i) 、printf("&a[3] = %p", &a[3]) 将这两者的地址打印出来,是一样的。
注:a[3]作为数组中实际不存在的“溢出”元素的地址位于数组所占内存之后,这个地址可以用于进行赋值和比较,但如果引用该元素,就是非法操作。
参考《C陷阱与缺陷》