我有一个函数,我想把它作为一个参数,一个可变大小的二维数组。

到目前为止,我有这个:

void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

我在代码的其他地方声明了一个数组:

double anArray[10][10];

然而,调用myFunction(anArray)会给我一个错误。

我不想在传入数组时复制它。在myFunction中所做的任何更改都应该改变anArray的状态。如果我理解正确,我只想传递一个指向2D数组的指针作为参数。该函数还需要接受不同大小的数组。比如[10][10][5][5]。我该怎么做呢?


当前回答

你可以使用c++中的模板功能来实现这一点。我是这样做的:

template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}

这种方法的问题在于,对于您提供的每一个col值,都会使用模板实例化一个新的函数定义。 所以,

int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);

实例化模板两次以生成2个函数定义(其中一个col = 3,另一个col = 5)。

其他回答

一维数组衰减为指向数组中第一个元素的指针。而2D数组则衰减为指向第一行的指针。所以,函数原型应该是-

void myFunction(double (*myArray) [10]);

我更喜欢std::vector而不是原始数组。

对shengy第一个建议的修改,你可以使用模板让函数接受多维数组变量(而不是存储一个必须被管理和删除的指针数组):

template <size_t size_x, size_t size_y>
void func(double (&arr)[size_x][size_y])
{
    printf("%p\n", &arr);
}

int main()
{
    double a1[10][10];
    double a2[5][5];

    printf("%p\n%p\n\n", &a1, &a2);
    func(a1);
    func(a2);

    return 0;
}

打印语句用于显示数组是通过引用传递的(通过显示变量的地址)

你可以使用c++中的模板功能来实现这一点。我是这样做的:

template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}

这种方法的问题在于,对于您提供的每一个col值,都会使用模板实例化一个新的函数定义。 所以,

int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);

实例化模板两次以生成2个函数定义(其中一个col = 3,另一个col = 5)。

固定大小

1. 通过引用传递

template <size_t rows, size_t cols>
void process_2d_array_template(int (&array)[rows][cols])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

在c++中,通过引用传递数组而不丢失维度信息可能是最安全的,因为不需要担心调用者传递不正确的维度(当不匹配时编译器会标记)。然而,这对于动态(独立式)数组是不可能的;它只适用于自动(通常是栈生存的)数组,即维度应该在编译时知道。

2. 传递指针

void process_2d_array_pointer(int (*array)[5][10])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < 5; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << (*array)[i][j] << '\t';
        std::cout << std::endl;
    }    
}

The C equivalent of the previous method is passing the array by pointer. This should not be confused with passing by the array's decayed pointer type (3), which is the common, popular method, albeit less safe than this one but more flexible. Like (1), use this method when all the dimensions of the array is fixed and known at compile-time. Note that when calling the function the array's address should be passed process_2d_array_pointer(&a) and not the address of the first element by decay process_2d_array_pointer(a).

变量的大小

这些维继承自C语言,但不太安全,编译器没有办法检查,确保调用者传递了所需的维。该函数仅依赖调用者传入的维度。它们比上面的更灵活,因为不同长度的数组可以不变地传递给它们。

需要记住的是,在C中没有直接将数组传递给函数这样的事情[而在c++中,它们可以作为引用传递(1)];(2)将指针传递给数组,而不是数组本身。始终按原样传递数组成为一个指针复制操作,这得益于数组衰减为指针的特性。

3.传递一个指向衰减类型的指针(value)

// int array[][10] is just fancy notation for the same thing
void process_2d_array(int (*array)[10], size_t rows)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Although int array[][10] is allowed, I'd not recommend it over the above syntax since the above syntax makes it clear that the identifier array is a single pointer to an array of 10 integers, while this syntax looks like it's a 2D array but is the same pointer to an array of 10 integers. Here we know the number of elements in a single row (i.e. the column size, 10 here) but the number of rows is unknown and hence to be passed as an argument. In this case there's some safety since the compiler can flag when a pointer to an array with second dimension not equal to 10 is passed. The first dimension is the varying part and can be omitted. See here for the rationale on why only the first dimension is allowed to be omitted.

4. 将一个指针传递给另一个指针

// int *array[10] is just fancy notation for the same thing
void process_pointer_2_pointer(int **array, size_t rows, size_t cols)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Again there's an alternative syntax of int *array[10] which is the same as int **array. In this syntax the [10] is ignored as it decays into a pointer thereby becoming int **array. Perhaps it is just a cue to the caller that the passed array should have at least 10 columns, even then row count is required. In any case the compiler doesn't flag for any length/size violations (it only checks if the type passed is a pointer to pointer), hence requiring both row and column counts as parameter makes sense here.

Note: (4) is the least safest option since it hardly has any type check and the most inconvenient. One cannot legitimately pass a 2D array to this function; C-FAQ condemns the usual workaround of doing int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10); as it may potentially lead to undefined behaviour due to array flattening. The right way of passing an array in this method brings us to the inconvenient part i.e. we need an additional (surrogate) array of pointers with each of its element pointing to the respective row of the actual, to-be-passed array; this surrogate is then passed to the function (see below); all this for getting the same job done as the above methods which are more safer, cleaner and perhaps faster.

下面是一个测试上述功能的驱动程序:

#include <iostream>

// copy above functions here

int main()
{
    int a[5][10] = { { } };
    process_2d_array_template(a);
    process_2d_array_pointer(&a);    // <-- notice the unusual usage of addressof (&) operator on an array
    process_2d_array(a, 5);
    // works since a's first dimension decays into a pointer thereby becoming int (*)[10]

    int *b[5];  // surrogate
    for (size_t i = 0; i < 5; ++i)
    {
        b[i] = a[i];
    }
    // another popular way to define b: here the 2D arrays dims may be non-const, runtime var
    // int **b = new int*[5];
    // for (size_t i = 0; i < 5; ++i) b[i] = new int[10];
    process_pointer_2_pointer(b, 5, 10);
    // process_2d_array(b, 5);
    // doesn't work since b's first dimension decays into a pointer thereby becoming int**
}

尽管表面上看,double**隐含的数据结构与固定c数组(double[][])的数据结构根本不兼容。 问题是这两种方法都是C(或c++)中处理数组的流行(尽管)被误导的方法。 参见https://www.fftw.org/fftw3_doc/Dynamic-Arrays-in-C_002dThe-Wrong-Way.html

如果你不能控制代码的任何一部分,你需要一个翻译层(这里称为adapt),解释在这里:https://c-faq.com/aryptr/dynmuldimary.html

您需要生成一个指针的辅助数组,指向c数组的每一行。

#include<algorithm>
#include<cassert>
#include<vector>

void myFunction(double** myArray) {
    myArray[2][3] = 5;
}

template<std::size_t N, std::size_t M>
auto adapt(double(&Carr2D)[N][M]) {
    std::array<double*, N> ret;
    std::transform(
        std::begin(Carr2D), std::end(Carr2D),
        ret.begin(),
        [](auto&& row) { return &row[0];}
    );
    return ret;
}

int main() {
    double anArray[10][10];

    myFunction( adapt(anArray).data() );

    assert(anArray[2][3] == 5);
}

(参见工作代码:https://godbolt.org/z/7M7KPzbWY)

如果它看起来像是一场灾难,那是因为,正如我所说,这两种数据结构从根本上是不兼容的。


如果可以控制代码的两端,那么现在最好使用现代(或半现代)数组库,比如Boost。MultiArray,提振。uBLAS,特征或多。 如果数组要小,你有“小”数组库,例如在Eigen内,或者如果你不能负担任何依赖,你可以尝试简单的std::array<std::array<double, N>, M>。

使用Multi,你可以简单地这样做:

#include<multi/array.hpp>

#include<cassert>

namespace multi = boost::multi;

template<class Array2D>
void myFunction(Array2D&& myArray) {
    myArray[2][3] = 5;
}

int main() {
    multi::array<double, 2> anArray({10, 10});

    myFunction(anArray);

    assert(anArray[2][3] == 5);
}

(工作代码:https://godbolt.org/z/7M7KPzbWY)