在C++中为3D数组分配连续内存

4

我可以在C++中分配连续的内存来为二维数组分配内存。但是我不知道如何为三维数组做同样的事情。我已经阅读了一些帖子,但还没有找到解决方案。

#include <iostream>


using namespace std;

int main(int argc, char **argv){
    cout << "Ints have size " << sizeof(int) << endl;

int rows= 2;
int cols= 3;

int **i= new int*[rows];
int size= rows*cols;
i[0]= new int[size];
for(int j= 1; j < rows; j++) {
  i[j]= &i[0][j*cols];
}

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

让我们回到之前,你并没有分配数组,而是在分配指针的存储空间。 - David C. Rankin
创建一个具有足够存储整个3D数组的1D数组,然后手动执行所有索引操作,将3个维度映射到1个维度。这里有一个2D的示例,您可以将其扩展到3D。 - user4581301
重要的问题是,数组大小在编译时是否已知,或者例如从文件或用户读取并且可以是任何值? - hyde
@hyde 是的,它在编译时已知。 - Marco
3个回答

7

一个有P个平面,每个平面有R行和C列的3D数组需要P*R*C个元素。您可以使用以下命令一次性分配它们:

Element *p = new Element[P*R*C];

如果要访问坐标为 (p, r, c) 的元素,您可以使用以下公式:

int index = (p*R + r)*C + c;

为了让内容易读,一个简单的解决方案是创建一个类。
template<typename T>
struct Matrix3D {
    int P, R, C;
    std::vector<T> elements;

    Matrix3D(int P, int R, int C)
        : P(P), R(R), C(C), elements(P*R*C)
    { }

    T& operator()(int p, int r, int c) {
        return elements[(p*R + r)*C + c];
    }
};

在这个例子中,我使用了一个std::vector来存储元素,因为这样可以简化关于所有元素在内存中的连续性和拥有权/复制的问题。如果你想手动分配存储空间,则需要更多代码。
如果大小在编译时知道,那么可以使PRC成为模板参数,并使用std::array成员代替std::vector。这应该会提高一些性能,因为整个类最终会成为堆中的单个内存块,并允许常数乘法技巧进行元素访问。

3
如果您需要分配的空间必须是连续的,那么它必须由单个“new”进行分配,否则内存将不连续。

它会看起来像这样:

int d1 = 10; // first
int d2 = 10; // second
int d3 = 10; // third dimension

int* array3D = new int[d1 * d2 * d3];

通过这样的方式,您已经为您的三维数组分配了足够的空间,现在需要将其映射到三维空间。

array3D[(1*d1*d2) + (2*d2) + (3)]; // access element at 1,2,3

通过这种方法,你可以将你分配的1D数组中的每个点映射到3D空间中的唯一点。
正如你所看到的,这种方法非常容易出错。因此,你不应该像这样做。
不要使用new/delete来分配这样的数组:
使用std:array或std:vector来为你处理它。使用原始的new/delete会导致错误,如果有任何东西是用new分配的并且你忘记删除它,或者你忽略了某些事情,就会导致内存泄漏。
void test(){
    int* a = new int[20];
    // do stuff with a...
    if(error)
        return; // oops this is a leak

    delete a; // only executed if there was no error,
}

std::array 是在编译时确定大小且永远不需要更改的数组类型。

另一方面,std::vector 可以在运行时动态调整大小,因此适用于大小不确定的情况。

std::array<int, 10> test1; // creates a fixed size array of size 10 and type int.
std::vector<int>    test2(10); // creates an array that can change at runtime:
test2.push_back(2);            // the vector now has size 11 and the last element is equal to 2

这样你也不需要在最后删除数组。

如果您想在代码中更频繁地使用此功能,将所有这些功能包装在一个类中可能非常有帮助:

#include <array>

template<typename T, std::size_t _D1, std::size_t _D2, std::size_t _D3>
class Array3D{
    std::array<T, _D1*_D2*_D3> elements;
public:
    std::size_t D1(){ return _D1; }
    std::size_t D2(){ return _D1; }
    std::size_t D3(){ return _D1; }

    T& element(std::size_t d1, std::size_t d2, std::size_t d3){
        return elements[(d1*_D1*_D2) + (d2*_D2) + (d3)];
    }
};

int main(){ // argc/argv not required if you dont use them
    Array3D<int, 10, 10, 10> array;
    array.element(1,2,3) = 5;

    // loop thorug all elements
    // the methods d1,d2,d3 return the dimensions you gave them initialy
    // this way if you cange the array size you dont have to change this loop at all
    for(std::size_t i = 0; i < array.D1(); i++)
        for(std::size_t j = 0; j < array.D2(); j++)
            for(std::size_t k = 0; k < array.D3(); k++)
                array.element(i,j,k) = 5;

    // no delete
}

2

一个数组的数组(3D数组)就是一个包含其他数组引用的数组。

您只需要分配第一个2D数组,然后对于该数组的每个索引,在其中分配另一个数组即可。

“Original Answer”翻译成中文是“最初的回答”。


1
3D数组也可以是没有指针的三维数组。你所描述的通常被称为“不规则数组”,因为不同的行可以有不同的大小。 - hyde
没错!但是用户并没有特别要求一个“经典”的数组。他可以使用for循环分配索引成员,并仅为每个成员使用常量作为大小。 - cocool97
1
问题要求“连续内存”。 - hyde
@hyde 连续意味着在内存中只占用一个空间范围,而不是所有列都必须具有相同的大小,对吗? - cocool97
1
@cocool97 如果你创建指针并为它们分配内存,整个内存空间将不会是连续的 - 特别是如果数组大于一个虚拟页面。 - Jerry Jeremiah
我曾经在二维矩阵中使用指向行的指针数组,但实际数据是一个连续的块。这种方法在旧的廉价嵌入式系统CPU上相当高效,因为乘法比指针查找慢。但那些日子已经一去不复返了。 - doug

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