如何使用new在C++中声明一个二维数组?

638

我如何使用 new 声明一个二维数组?

例如,对于“普通”的数组,我会这样做:

int* ary = new int[Size]

但是

int** ary = new int[sizeY][sizeX]

a) 不能工作/编译,b) 也没有实现什么:

int ary[sizeY][sizeX] 

做什么。


74
只有在sizeX是常量时才起作用:int(*ary)[sizeX] = new int[sizeY][sizeX]; 这是创建一个int[sizeY][sizeX]且所有内存都是连续的正确方式。(我认为这不值得回答,因为很可能你的sizeX不是常量。) - Johannes Schaub - litb
36
我很难相信下面所有的答案都是全部错误的,没有回答问题,但它们都得到了赞同。上面Johanes Shaub的评论是唯一正确的回答. 一个二维数组和一个指向数组的指针数组是两个完全不同的东西,显然每个人都混淆了它们。 - Bregalad
8
@JohannesSchaub-litb: 这并不完全正确。当然,在那种情况下它可以工作,但有一种方法可以使所有维度都变化,详见 https://dev59.com/AYnca4cB1Zd3GeqP8UPo#29375830 - Ben Voigt
30个回答

16

如何在GNU C++中分配连续的多维数组?有一个GNU扩展可以使“标准”语法起作用。

似乎问题来自于 operator new []。请确保使用 operator new :

double (* in)[n][n] = new (double[m][n][n]);  // GNU extension

这就是全部:你将获得一个与C兼容的多维数组...


你使用哪个编译器?使用g++ 4.6.4和4.7.3编译和运行数组语法都很好。只是在等号之前的最后一个]处会出现警告,提示“计算的值未被使用”或“语句没有效果”。然而,如果使用g++ 4.8.1(据说完全符合c++11标准),会报n和o不是常量的错误,“new操作符中数组大小必须是常量”,并指向该行的最后一个]。 - jbo5112
@cmaster double (*in)[m][n] = (double (*)[m][n])new double[k*m*n]; 这个也不行。我在 n 上得到了 C2057、C2540 错误,因为它在编译时是未知的。我不明白为什么不能这样做,因为内存已经正确分配了,而且只是用指针方便地处理这个内存。(VS 2010) - user1234567
3
当我写这个程序时,gcc让我产生了困惑:只使用-std=c++11并不足以开启严格的标准一致性,还需要使用-pedantic-errors。如果没有后面的标志,即使强制类型转换确实不符合C++标准,gcc也会轻松接受它。根据我现在所知,我只能建议在进行大量依赖于多维数组的操作时退回到C语言。 C99在这方面比甚至C++17更为强大。 - cmaster - reinstate monica
@cmaster 动态分配的VLA其实只是语法糖...在C中很好用,因为没有别的选择,但是C++有更好的语法糖 :) - M.M
3
很遗憾,C++没有针对真正的连续多维数组在堆上分配的语法糖,且其尺寸只在运行时才知道。只要不需要这个功能,C++的语法糖就够用了。但是,如果你需要上述所有功能,即使是FORTRAN也能胜过C++... - cmaster - reinstate monica
只是为了记录,使用 malloc 的 C 等效方法使用相同的语法声明指向保存结果的二维数组的指针:在C中使用malloc分配二维数组 - Peter Cordes

13

typedef可以提高代码可读性。

回顾其他答案后,需要对此进行更深入的解释。因为其他答案要么存在性能问题,要么需要使用不常见或繁琐的语法来声明数组或访问数组元素(或者包括以上所有问题)。

首先,本答案假定您在编译时知道数组的维度。如果是这样,那么这是最佳解决方案,因为它既可以给出最佳性能,又允许您使用标准数组语法来访问数组元素

之所以能够提供最佳性能,是因为它将所有数组分配为相邻的内存块,这意味着您可能会有更少的页面错误和更好的空间局部性。在循环中分配可能导致单个数组散布在虚拟内存空间的多个非相邻页面上,因为分配循环可能会被其他线程或进程打断(可能是多次),或者仅仅是由于分配器填充其可用的小型空闲内存块的裁量权而出现这种情况。

其他好处是简单的声明语法和标准的数组访问语法。

在C++中使用new:





















#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {

typedef double (array5k_t)[5000];

array5k_t *array5k = new array5k_t[5000];

array5k[4999][4999] = 10;
printf("array5k[4999][4999] == %f\n", array5k[4999][4999]);

return 0;
}

或者使用 C 语言的 calloc 函数:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {

typedef double (*array5k_t)[5000];

array5k_t array5k = calloc(5000, sizeof(double)*5000);

array5k[4999][4999] = 10;
printf("array5k[4999][4999] == %f\n", array5k[4999][4999]);

return 0;
}

1
访问数组末尾外的内容不能保证会引起错误。如果你很幸运,程序只会崩溃。你肯定处于未定义行为的领域。 - Michael Kristofik
虽然这个例子的目的只是展示如何使用typedef和new一起声明一个二维数组,但它确实是真实的。 - Robert S. Barnes
1
我非常喜欢你的回答。我自己一直是typedef的支持者。 - AnotherDeveloper

9
这个回答的目的不是为了添加任何新内容,因为其他人已经讨论得很全面了,而是在@Kevin Loney的回答上进行扩展。您可以使用轻量级声明:
int *ary = new int[SizeX*SizeY]

访问语法将是:

ary[i*SizeY+j]     // ary[i][j]

但是对于大多数人来说,这种方法很繁琐,容易引起混乱。因此,你可以定义一个宏,如下所示:

#define ary(i, j)   ary[(i)*SizeY + (j)]

现在你可以使用非常相似的语法ary(i, j) // 表示 ary[i][j]来访问数组。这样做的优点是简单美观,同时,在指数位置使用表达式也更简单,更不容易混淆。
要访问例如ary[2+5][3+8],你可以写成ary(2+5, 3+8)而不是复杂的ary[(2+5)*SizeY + (3+8)],这样可以节省括号并提高可读性。 注意事项:
  • 尽管语法非常相似,但它们并不相同。
  • 如果将数组传递给其他函数,则必须以相同名称传递SizeY(或者将其声明为全局变量)。
或者,如果您需要在多个函数中使用数组,则可以在宏定义中添加SizeY作为另一个参数,如下所示:
#define ary(i, j, SizeY)  ary[(i)*(SizeY)+(j)]

你已经有了想法。当然,这个想法变得太长而不实用,但它仍然可以防止+和*的混淆。
这绝对不被推荐,并且大多数经验丰富的用户会谴责它作为不良做法,但由于其优雅性,我无法抗拒分享它。
编辑: 如果你想要一个适用于任意数量数组的便携式解决方案,你可以使用这个语法:
#define access(ar, i, j, SizeY) ar[(i)*(SizeY)+(j)]

然后您可以使用访问语法将任何大小的数组传递给调用。

access(ary, i, j, SizeY)      // ary[i][j]

附言:我已经测试过这些内容,相同的语法在g++14和g++11编译器中都可以作为左值和右值使用。


7

尝试执行以下操作:

int **ary = new int* [sizeY];
for (int i = 0; i < sizeY; i++)
    ary[i] = new int[sizeX];

2

这里有两种选项。第一种显示了数组的概念,或者指针的概念。我更喜欢第二种,因为地址是连续的,就像您在图像中看到的那样。

enter image description here

#include <iostream>

using namespace std;


int main(){

    int **arr_01,**arr_02,i,j,rows=4,cols=5;

    //Implementation 1
    arr_01=new int*[rows];

    for(int i=0;i<rows;i++)
        arr_01[i]=new int[cols];

    for(i=0;i<rows;i++){
        for(j=0;j<cols;j++)
            cout << arr_01[i]+j << " " ;
        cout << endl;
    }


    for(int i=0;i<rows;i++)
        delete[] arr_01[i];
    delete[] arr_01;


    cout << endl;
    //Implementation 2
    arr_02=new int*[rows];
    arr_02[0]=new int[rows*cols];
    for(int i=1;i<rows;i++)
        arr_02[i]=arr_02[0]+cols*i;

    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++)
            cout << arr_02[i]+j << " " ;
        cout << endl;
    }

    delete[] arr_02[0];
    delete[] arr_02;


    return 0;
}

2
以下示例可能有所帮助,
最初的回答:
int main(void)
{
    double **a2d = new double*[5]; 
    /* initializing Number of rows, in this case 5 rows) */
    for (int i = 0; i < 5; i++)
    {
        a2d[i] = new double[3]; /* initializing Number of columns, in this case 3 columns */
    }

    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            a2d[i][j] = 1; /* Assigning value 1 to all elements */
        }
    }

    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            cout << a2d[i][j] << endl;  /* Printing all elements to verify all elements have been correctly assigned or not */
        }
    }

    for (int i = 0; i < 5; i++)
        delete[] a2d[i];

    delete[] a2d;


    return 0;
}

1

这是一个旧的答案,但我喜欢用以下方式声明C++动态数组

int sizeY,sizeX = 10;
 //declaring dynamic 2d array:
    int **ary = new int*[sizeY];
    for (int i = 0; i < sizeY; i++) 
    {
     ary[i] = new int[sizeX];
   }

您可以在运行时像这样更改大小。 这在C ++ 98中进行了测试。

1
如果您的项目是CLI(公共语言运行时支持),则:
您可以使用数组类,不是您在编写时获得的那个。
#include <array>
using namespace std;

换句话说,不是在使用std命名空间和包含数组头文件时得到的未经管理的数组类,也不是在std命名空间和数组头文件中定义的未经管理的数组类,而是CLI的托管类数组。
使用这个类,你可以创建任何你想要的秩的数组。
下面的代码创建一个新的二维数组,有2行3列,类型为int,我称之为"arr":
array<int, 2>^ arr = gcnew array<int, 2>(2, 3);

现在,您可以通过名称访问数组中的元素,并仅写一个平方括号 "[]",并在其中添加行和列,并用逗号 "," 分隔它们。
下面的代码访问了我在上面先前创建的数组中第二行第一列的元素:
arr[0, 1]

只写这一行是为了读取那个单元格的值,即获取此单元格中的值,但如果添加等于号=,则将要在该单元格中写入值,即设置此单元格中的值。 当然,您也可以使用+=、-=、*=和/=运算符,仅适用于数字(int、float、double、__int16、__int32、__int64等),但您肯定已经知道了。

如果您的项目不是CLI,则可以使用std命名空间的非托管数组类,当然,如果您#include <array>。但问题是,此数组类与CLI数组不同。创建此类型的数组与CLI相同,除了您必须删除^符号和gcnew关键字。但不幸的是<>括号中的第二个int参数指定了数组的长度(即大小),而不是其秩!

在这种类型的数组中没有办法指定秩,秩是CLI数组的特性而已。

在C++中,std数组的行为类似于普通数组,您可以使用指针定义,例如int*,然后:new int[size],或者不使用指针:int arr[size]。但与C++中的普通数组不同,std数组提供了可用于数组元素的函数,如fill、begin、end、size等,而普通数组则没有提供任何函数。

但是,std数组仍然是一维数组,就像普通的C++数组一样。 但由于其他人提出的关于如何将普通的C++一维数组转换为二维数组的解决方案,我们可以将相同的思路应用于std数组,例如根据Mehrdad Afshari的想法,我们可以编写以下代码:

array<array<int, 3>, 2> array2d = array<array<int, 3>, 2>();

这行代码创建了一个“嵌套数组”,它是一个一维数组,每个单元格都是或指向另一个一维数组。
如果一维数组中所有的一维数组在长度/大小上都相等,那么你可以将array2d变量视为真正的二维数组,并且你可以使用特殊的方法来处理行或列,取决于你在脑海中如何看待它,在2D数组中,std数组支持此操作。
你还可以使用Kevin Loney的解决方案:
int *ary = new int[sizeX*sizeY];

// ary[i][j] is then rewritten as
ary[i*sizeY+j]

但如果您使用std数组,则代码必须有所不同:

array<int, sizeX*sizeY> ary = array<int, sizeX*sizeY>();
ary.at(i*sizeY+j);

仍然具有std数组的独特功能。

请注意,您仍然可以使用[]括号访问std数组的元素,而且不必调用at函数。 您还可以定义并分配新的int变量,该变量将计算并保留std数组中元素的总数,并使用其值,而不是重复使用sizeX*sizeY

您可以定义自己的二维数组通用类,并将二维数组类的构造函数定义为接收两个整数以指定新二维数组中的行数和列数,并定义get函数,该函数接收两个整数参数来访问二维数组中的元素并返回其值,以及set函数,它接收三个参数,前两个是指定二维数组中的行和列的整数,第三个参数是元素的新值。其类型取决于您在通用类中选择的类型。

您将能够通过使用普通的c++数组(指针或无指针)或std数组之一,并使用其他人建议的其中一个想法之一来实现所有这些,并使其易于像cli数组那样使用,或者像C#中定义,分配和使用的二维数组。


1
我不确定以下答案是否已经提供,但我决定为二维数组的分配添加一些本地优化(例如,一个方阵只需要进行一次分配): int** mat = new int*[n]; mat[0] = new int [n * n]; 然而,由于上面的分配是线性的,删除如下: delete [] mat[0]; delete [] mat;

1
在这个问题的回答中已经提到过:https://dev59.com/53NA5IYBdhLWcg3wgeAd#27672888 还有一个智能指针版本在这里:https://dev59.com/AYnca4cB1Zd3GeqP8UPo#29375830 - Ben Voigt

1
如果你想要一个整数的二维数组,其中元素在内存中是顺序分配的,你必须像下面这样声明它:
int (*intPtr)[n] = new int[x][n]

在这里,您可以用任何维度代替 x,但是n必须在两个位置相同。例如:
int (*intPtr)[8] = new int[75][8];
intPtr[5][5] = 6;
cout<<intPtr[0][45]<<endl;

必须打印6。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接