关于多维数组与指针--数组名、指针、动态多维数组的使用

文章详细阐述了C++中数组名的性质,它实际上代表了数组第一个元素的地址,而对其取地址则得到整个数组的地址。指针变量可以用来存储数组的地址,数组名的类型取决于其元素类型,例如int(*)[5]表示指向长度为5的一维数组的指针。对于动态多维数组,C++标准只允许第一维是动态的,可以通过指针数组实现类似的功能,但需要手动管理内存,防止内存泄漏。文章举例说明了如何创建和销毁这种动态多维数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题给出: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[]一起使用以防止内存泄漏,首先需要针对指针数组中的每一个指针解除内存,最后再删除指针数组本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值