问题给出:c++中数组名的数据类型是什么?怎样将数组名赋值给指针变量?怎样定义c++内置的动态多维数组?
先给出《C++ Primer Plus》的描述:
从上可知,数组名直接使用的话,它所代表的指针指向的是数组的第一个元素,即指向的地址的大小是单元素的内存大小;而对数组名取地址,得到的是整个数组的地址,这块地址的大小是元素个数*单元素内存。
怎么去理解呢,先给一段代码,运行试试:
#include <iostream>
using namespace std;
int main()
{
int arr[2][5] = {1,2,3,4,5,6,7,8,9,10};
cout << arr[1][2] << *(*(arr+1)+2) << endl;
int (*p_arr)[5] = arr;
cout << p_arr[1][2] << *(*(p_arr+1)+2) << endl;
int (*p_arr2)[2][5] = &arr;
cout << p_arr2[0][1][2] << *(*(*(p_arr2+0)+1)+2) << endl;
return 0;
}
首先,要知道,无论是数组名还是指针,无论是多维还是一维,都满足arr[i]=*(arr+i),对于多维的情况无非就是嵌套去使用这个公式。
来看代码,
1、cout << arr[1][2] << *(*(arr+1)+2) << endl; //88
这个就是利用上面的这个公式了。两者都是数组第2行第3列的元素值。
2、int (*p_arr)[5] = arr;
开始我也不理解这里为什么不是&arr,直到看到上面《C++ Primer Plus》的描述。虽然上面写的是一维数组,但可以体会到c++对数组名指向地址的大小的处理方式。即,数组名这个“特殊的指针”指向的是第一个元素,它对应的内存空间大小当然就是这个元素的字节数,那么数组名的变量类型是什么呢?答案是指向其元素类型的指针。
例如,对于int arr[5] = {1,2,3,4,5},数组名arr的变量类型是int*;对于int arr[2][5]={1,2,3,4,5,6,7,8,9,10},数组名arr的变量类型是int (*) [5];对于int arr[1][2][3]={1,2,3,4,5,6},数组名arr的变量类型是int (*) [2][3]。再解释一下:int arr[5]的元素是int,所以arr的变量类型是int*;int arr[2][5]的元素是int[5],所以arr的变量类型是int (*) [5],它是一个指向长度为5的一维数组的指针;int arr[1][2][3]的元素是int[2][3],所以arr的变量类型是int (*) [2][3],它是一个指向具有2个长度为3的二维数组的指针。
回到这句:int (*p_arr)[5] = arr,那么这样就解释得通了,赋值表达式右边的变量类型是int (*) [5],所以左边指针的变量类型也应该是这样。
PS:这里再补充一句,int (*) [5]和int (*) [2][3]中的圆括号是为了区分“指向特定大小数组的指针变量”和“特定大小的指针数组”。
3、 cout << p_arr[1][2] << *(*(p_arr+1)+2) << endl; //88
也是用了上面的那个公式,得到数组第2行第3列的元素值。
这里可以提一句,数组名和指针的另一个区别是,数组名是常量,它的值不能修改,也就是不能用arr++,但指针是变量,可以p_arr++,当然修改了指针的话,使用这个指针取数组值时索引要相应减1。
4、int (*p_arr2)[2][5] = &arr;
既然数组名arr的变量类型是int *,那么&arr的变量类型是啥?还是仔细看大神的解释:“对数组名应用地址运算符时,得到的是整个数组的地址”。也就是说相当于取地址符会将多维数组维度提高一层。
例如,对于int arr[5],&arr的变量类型是int (*) [5];对于int arr[2][5],&arr的变量类型是int (*)[2][5];对于int arr[1][2][3],&arr的变量类型是int (*) [1][2][3]。
5、cout << p_arr2[0][1][2] << *(*(*(p_arr2+0)+1)+2) << endl; //88
用了&arr这样的取地址符,再去取数组元素,就需要多加一个下标运算符,相当于把维度提高了一层,只不过第一维是0。
综上,需要理解,指针变量不仅仅指向某地址的“指针”,还指向位于该地址的数据的类型,而“类型”里面又含括了地址空间的大小这一信息。
另外还发现了一个比较玄学的事:
对于上段代码,最后加上
cout << sizeof(arr) << " " << sizeof(&arr) << endl; //40 8
cout << sizeof(p_arr) << " " << sizeof(*p_arr) << endl; //8 20
cout << sizeof(p_arr2) << " " << sizeof(*p_arr2) << endl; //8 40
sizeof(int)是4,那么从输出结果来看,与上面分析的结果并不相符,例如数组名arr变量类型应该是int *[5],但它反而输出整个数组大小;&arr变量类型是int * [2][5],但它输出的是两个int元素的大小(可能是&arr的第一维有两个元素的缘故吧)。
说了上面这么多,有啥用呢?
上面的数组都是静态分配内存的,那么对于动态分配普通多维数组时,就需要用到new,这里面是有坑的,标准C++中不允许有多个动态维度的多维数组,使用内置的C++类型,最多能让第一维的值是动态的。
例如,《C++20实践入门》176页的这个例子:
#include <iostream>
using namespace std;
int main()
{
int rows,columns;
cin >> rows >> columns;
auto carrots {new double[rows][columns]{}} //不能运行,因为columns不是常量
auto carrots {new double[rows][4]{}}; //可以运行,因为只有第一维值是动态的
return 0;
}
可以看到,C++的普通数组无法在所有维度上进行动态分配内存。对于这句,auto carrots {new double[rows][4]{}};虽然可以用auto,但是要知道,根据上面的内容,这句话可以替换成double (*carrots)[4] {new double [rows][4]{}};。当然这里还是没有解决问题:
在C++中怎么创建多维动态数组?
这里给出书上的代码:
#include <iostream>
using namespace std;
int main()
{
int rows,columns;
cin >> rows >> columns;
double** carrots {new double* [rows] {}};
for(size_t i=0;i<rows;++i)
carrots[i] = new double[columns];
for (size_t i=0;i<rows;++i)
delete[] carrots[i];
delete[] carrots;
}
这里用到了指针数组,对于这句double** carrots {new double* [rows] {}},new动态分配一个rows大小的指针数组,那么这个指针数组名(假设它有数组名)它的变量类型是啥呢?如果不清楚,可以先想,它的每个元素的变量类型是啥?答案是:该数组每个元素的变量类型是指向double类型的指针;再上去看《C++ Primer Plus》的描述----“对于一个数组的数组名,它被解释为第一个元素的地址”,数组指针中的元素是指针,那“第一个元素的地址”不就是指针的指针吗?!
所以double** carrots {new double* [rows] {}}这里的new double* [rows] {}的变量类型就是double**。类似的对于任意的动态指针数组,可以这样定义int** ptr {new int * [x] }。
继续讲完上面的代码,carrots数组是double*指针的第一个动态数组,每个double*指针包含一个double数组的地址。那么carrots[i] = new double[columns]是合理的,因为右边的变量类型是double*,而左边的carrots[i]=*(carrots+i),将carrots解依次引用后,就从“指针的指针”变成了“指针”,所以也是double*。最后,new[]总是和delete[]一起使用以防止内存泄漏,首先需要针对指针数组中的每一个指针解除内存,最后再删除指针数组本身。