数组的本质
任何数组的元素在内存中都是连续字节存放的,数组下标必须是整数或整数表达式,并且下标操作符返回的是一个元素的引用:
int arr[10] = { 0 };
arr[3] = 1; // 编译器计算地址arr+3*sizeof(int),并且返回该地址的引用,和下边两句是语义上等价
//int &ri = arr[3];(c++)
//ri = 1;
使用[]引用数组元素的时候,编译器必须把它转成同类型指针然后编译,因为C/C++数组本身不会保存下标值和元素对象之间的对应关系。
arr[3] = 1; // 转换为*(arr+3)=1;
我们可以写一段代码验证:
int a = 10;
int *p = &a;
printf("%d", p[0]);
这种做法是合理的,p[0]等价于*(p+0)
int arr[5]={ 1, 2, 3, 4, 5 };
printf("%d", 2[arr]);
这样编译器会自动转变成*(2+arr)和arr[2]是等价的。
数组名
《C和指针》中提到,这个概念实际上以一种相当优雅的方式把一些完全不同的概念联系在一起。
数组名的值是一个常量指针,它的类型是指向其元素类型的常量指针,如上述arr就是int * const arr,由于不能仅通过数组名(不用下标和迭代)遍历数组,所以两个数组不能直接赋值。需要注意的是数组名具有一些和指针完全不同的特征比如,数组有确定的元素个数,指针是一个标量值。编译器用数组名记住这些属性。
只有数组名在表达式中使用时,编译器才会为它产生一个指针常量。
这两种情况下数组名并不是指针:
数组名作为sizeof操作符,或单目操作符&的操作数时,sizeof返回整个数组的长度,对数组名取地址返回的是指向数组的指针。
数组初始化
静态变量初始化:存储于静态内存的数组只能在程序开始执行之前初始化一次,由链接器完成。
自动变量初始化:自动变量位于运行时堆栈中,每次所处的内存位置并不一定相同,编译器没有办法对这些位置进行初始化,所以自动变量在缺省的情况下是未初始化的,如果自动变量给出了初始值,每次进入所在作用域时,变量就会被一条隐式的赋值语句初始化,这条语句也是需要时间和空间的。
因此,如果在程序的执行流每次进入该函数(或代码块)时,每次初始化是不值得的,那么就将它声明为static
关于下标越界
由于数组下标可以为整型表达式,所以无法执行静态检查
一般来说数组大小的信息保存在程序中第一个元素位置前面,占用一个int字节数,地址为a-sizeof(int),如果每次访问都和它比较会极大增加开销,另外又可以通过指针形式访问数组每个元素,无法确定是否越界(指针含有的信息只有类型和值)所以无法执行动态检查
二维数组
在C/C++中以行序优先来存储的
int arr[3][4] = { 1, 2, 3, 4,
5, 6, 7, 8,
9,10,11,12 };
创建了arr数组,可以将他看作一个一维数组,包含三个元素,只不过每个元素恰好是包含4个整型元素的数组。
arr这个名字的值是一个指向它第1个元素的指针,所以arr是一个指向一个包含4个整数类型的数组指针。
访问arr[1][2]时,编译器会转换为*((arr+1*4)+2)不需要知道行数3,所以它可以通过arr这个int (*) [4]指针访问任何元素;
多维数组
任何维数的数组都可以看做是由比它少一维的数组组成的一维数组,int a[3][4][5]可以看作3个4行5列的二维数组组成新的一维数组;
数组和指针之间的等价关系
int a[10] <=> int * const a;
int b[3][4] <=> int (* const b)[4];
int c[3][4][5] <=> int (* const c)[4][5];
数组传递
数组不能从函数的return语句返回,但是数组可以作为函数的参数
void test(int arr[], int size) {
printf("%d\n", sizeof(arr)); // 是4而不是400
}
int main() {
int arr[100] = { 0 };
test(arr);
printf("%d\n", sizeof(arr)); // 400
return 0;
}
数组名的值就是首元素的地址,自然传递的时候是值传递,函数内部创建一个该指针的拷贝。
编译器会改写成这样void test(int * arr, int size)
在32位系统下指针是4个字节,函数内部下标访问编译器就会自动转换成为*(arr+i),并且可以修改arr的值。
如果想按值传递数组,可以封装成struct。
由于任何数组的元素在内存中都是连续字节存放的,编译器可以同构地址计算来引用数组中元素,同时这样做可以减少开销
对于多维数组,必须传入除了第一维以外所有维的长度。
ElemType arr[m][n][o]...[x][y][z]
和指针类型由这样的关系:
ElemType (* const arr)[n][o]...[x][y][z]
数组的动态创建
C++动态创建二维数组注意事项:
char *p = new char[5][4]; // 这是错误的定义方法
char (*p)[4] = new char[5][4]; // 正确,语义等价