将一个二维数组传递给C++函数

444
我有一个函数,我想将可变大小的二维数组作为参数传递给它。
到目前为止,我的代码如下:
void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

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

double anArray[10][10];

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

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


2
无法将第3个参数从“double [10][10]”转换为“double **”。 - RogerDarwin
4
е·ІжҺҘеҸ—зҡ„зӯ”жЎҲеҸӘеұ•зӨәдәҶ2з§ҚжҠҖе·§пјҲе…¶дёӯ_пјҲ2пјү_е’Ң_пјҲ3пјү_зӣёеҗҢпјүпјҢдҪҶжҳҜжңү4з§ҚдёҚеҗҢзҡ„ж–№жі•еҸҜд»Ҙе°ҶдәҢз»ҙж•°з»„дј йҖ’з»ҷеҮҪж•°гҖӮ - legends2k
1
严格来说,是的,它们不是2D数组,但这种约定(尽管会导致UB),即具有指向(1D)数组的指针数组似乎很普遍:(拥有m x n长度的扁平化1D数组,并使用辅助函数/类来模拟2D数组可能更好。 - legends2k
1
最简单的- func(int* mat, int r, int c){ for(int i=0; i<r; i++) for(int j=0; j<c; j++) printf("%d ", *(mat+i*c+j)); }。像这样调用- int mat[3][5]; func(mat[0], 3, 5); - Minhas Kamal
未来参考:简而言之,在C/C++中,你不能轻松地将变量大小的二维数组int arr[m][n]传递给函数。解决方法是将&arr[0][0]传递到一个名为func(int arr)的函数中,然后在func内部使用arr[in+j]来访问arr[i][j]。或者你可以在cpp/c中使用new/malloc定义int **arr,然后将其传递给func(int **arr),在其中你可以使用arr[i][j]。 - router
然而,调用myFunction(anArray)会给我一个错误消息。请注意,您需要提供错误消息。 - starball
19个回答

533

传递二维数组到函数有三种方法:

  1. 参数是一个二维数组

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
    
  2. 该参数是一个包含指针的数组

  3. int *array[10];
    for(int i = 0; i < 10; i++)
        array[i] = new int[10];
    void passFunc(int *a[10]) //Array containing pointers
    {
        // ...
    }
    passFunc(array);
    
  4. 这个参数是一个指向指针的指针

  5. int **array;
    array = new int *[10];
    for(int i = 0; i <10; i++)
        array[i] = new int[10];
    void passFunc(int **a)
    {
        // ...
    }
    passFunc(array);
    

5
你可以使用 array[i][j] 来获取数组 array 的元素 :) - shengy
21
对于第一个情况,参数可以声明为int (*a)[10] - Zachary
10
第二种情况,该参数可以声明为int ** - Zachary
3
我会添加一个4。使用vector<vector<int>> - 463035818_is_not_a_number
8
案例2和3不是2D数组,因此这个答案是误导性的。请参考此处 - Lundin
显示剩余10条评论

248

固定大小

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;
    }    
}

前面方法的C语言等价方法是通过指针传递数组。不要将其与通过数组的衰变指针类型(3)传递相混淆,后者是常见、流行的方法,虽然比这种方法不太安全但更加灵活。与(1)一样,在所有数组维度都是固定且在编译时已知的情况下使用此方法。请注意,在调用函数时,应该传递数组的地址process_2d_array_pointer(&a)而不是通过衰减传递第一个元素的地址process_2d_array_pointer(a)

可变大小

这些是从C继承来的,但不太安全,编译器无法检查并保证调用者传递所需的维数。函数只依赖于调用者传递的维数。它们比前面的方法更灵活,因为可以不受限制地传递不同长度的数组。

请注意,在C语言中,没有直接将数组传递给函数的方法[而在C++中可以将数组作为引用传递(1)];(2)需要传递指向数组而不是数组本身的指针。始终按原样传递数组会变成指针复制操作,这是由于数组自然衰变为指针而方便实现的。 3. 通过传递指向衰变类型的指针进行传值
// 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;
    }
}

尽管允许使用int array[][10],但我不建议使用这种语法,因为前面的语法清楚地表明标识符array是指向包含10个整数的数组的单个指针,而这种语法看起来像是二维数组,但实际上是指向包含10个整数的数组的相同指针。在这种情况下,我们知道单行中元素的数量(即列大小,在此处为10),但行数未知,因此必须作为参数传递。在这种情况下,存在一些安全性,因为编译器可以标记传递了第二维大小不等于10的数组指针。第一维是可变部分,可以省略。请参见此处的原理,了解为什么只允许省略第一维。

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;
    }
}

再次提醒,int *array[10] 还有一种替代写法,即 int **array。在这种语法中,[10]会被忽略,因为它会衰变成一个指针,从而变成 int **array。也许这只是向调用者传递的一个提示,表明传递的数组至少应该有10列,即使需要确定行数。在任何情况下,编译器不会对长度/大小违规进行标记(它只检查传递的类型是否为指向指针的指针),因此在这里要求同时传递行和列的计数作为参数是有意义的。

注意:(4)是最不安全的选项,因为它几乎没有任何类型检查并且最不方便。不能合法地将2D数组传递给该函数; C-FAQ谴责通常的解决方法是执行int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);,因为这可能导致未定义的行为,由于数组压平。在此方法中传递数组的正确方法让我们进入了不方便的部分,即我们需要一个包含指向实际要传递数组的每一行的相应行的每个元素的附加(代理)指针数组; 然后将此代理传递给函数(见下文); 所有这些都是为了完成与上述方法相同的工作,这些方法更加安全、干净,也许更快。

这里有一个驱动程序来测试上述功能:

#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**
}

在C++中如何将动态分配的数组传递给函数?在C11标准中,可以像这样为静态和动态分配的数组执行fn(int col, int row, int array[col][row]):http://stackoverflow.com/questions/16004668/c-allocating-a-matrix-in-a-function/27366086#27366086 我已经针对此问题提出了问题:http://stackoverflow.com/questions/27457076/is-it-a-similar-function-in-c11-as-in-c11-for-the-twodimensional-matrix - 42n4
@42n4 第四种情况(对于C++也是如此)涉及到动态分配数组。对于动态分配的数组,循环内部的代码行只需从“b[i] = a[i];”更改为“b[i] = new int [10];”。还可以将“b”动态分配为“int ** b = int *[5];”,它仍然可以正常工作。 - legends2k
1
在第4个函数中,如何处理array[i][j]的寻址?因为它已经接收到了指向指针的指针,并且不知道最后一个维度的值,这是必要的,以便进行正确的寻址移位。 - user1234567
2
array[i][j] 只是指针算术,即对于指针 array 的值,它会加上 i 并将结果解引用为 int*,然后再加上 j 并解引用该位置,读取一个 int。因此,不需要知道任何维度。但这正是重点所在!编译器相信程序员的话,如果程序员不正确,就会出现未定义的行为。这就是我提到案例4是最不安全的选项的原因。 - legends2k
在这种情况下,结构体可能会很有用。 - Xofo
你也可以使用:void process_reference_2_pointer(int (&array), size_t rows, size_t cols); - user2338150

49

对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;
}

这些打印语句的作用是展示数组通过引用传递(通过显示变量的地址)。


2
在打印指针时,应该使用%p,即使这样,你也必须将其转换为void *,否则printf()会引发未定义的行为。此外,在调用函数时不应使用取地址符(&),因为函数期望一个类型为double (*)[size_y]的参数,而你当前传递给它们的是double (*)[10][10]double (*)[5][5] - user529758
如果您正在使用模板,则将两个维度作为模板参数更为合适,而且更好,因为可以完全避免低级指针访问。 - legends2k
6
只有在编译时已知数组的大小才能使此方法有效。 - John Doe
@Georg 在回答中的代码正是我建议的。它在GCC 6.3中可以工作 - 在线演示。你是否忘记将参数设为引用? - legends2k

33

惊讶于还没有人提到这一点,但你可以简单地在支持[][]语法的任何2D上模板。

template <typename TwoD>
void myFunction(TwoD& myArray){
     myArray[x][y] = 5;
     etc...
}

// call with
double anArray[10][10];
myFunction(anArray);

它可以与任何2D“类似于数组”的数据结构一起使用,例如std::vector<std::vector<T>>,或用户定义的类型,以最大限度地重用代码。


3
这应该是正确的答案。它解决了所有提到的问题和一些未提及的问题。它保证类型安全,编译时不兼容数组,没有指针运算,没有类型转换,也没有数据复制。适用于C和C ++。 - OpalApps
5
好的,这适用于C++;而C语言不支持模板。在C中需要使用宏来实现相同的功能。 - Gunnar
3
这个答案不够详细,它没有解释如何遍历二维数组中的元素。 - R Sahu
你如何定义类型 TwoD - VHS
2
@VHS 这是一个类型模板,因此它将使用您传递的任何类型进行实例化(并且编译器会推断出类型)。因此,您不必显式定义TwoD。 - LemonPi
这对于大小可变的数组不起作用。 - router

22

您可以像这样创建一个函数模板:

template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
    myArray[x][y] = 5;
    etc...
}

然后你可以通过R和C获得两个维度的大小。对于每个数组大小,都会创建一个不同的函数,因此如果你的函数很大,并且你使用各种不同的数组大小调用它,这可能会很昂贵。但是你可以将其作为另一个函数的包装器来使用:

void myFunction(double * arr, int R, int C)
{
    arr[x * C + y] = 5;
    etc...
}

它将数组视为一维数组,并使用算术运算来计算索引的偏移量。在这种情况下,你需要像这样定义模板:

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}

3
int相比,size_t更适合用作数组索引的类型。 - Andrew Tomazos

13

anArray[10][10]不是指向指针的指针,而是一块适合存储100个double类型值的连续内存块,编译器知道如何寻址它,因为你指定了维度。你需要将其作为数组传递给函数。你可以省略初始维度的大小,如下所示:

void f(double p[][10]) {
}

然而,这种方法不能通过最后一个维度不是十的数组。

C++中最好的解决方案是使用std::vector<std::vector<double> >:它几乎和数组一样高效,但更加方便。


1
几乎一样高效?是吗。指针追踪总比非指针追踪更昂贵。 - Thomas Eding

9
这是一个向量的向量矩阵示例。
#include <iostream>
#include <vector>
using namespace std;

typedef vector< vector<int> > Matrix;

void print(Matrix& m)
{
   int M=m.size();
   int N=m[0].size();
   for(int i=0; i<M; i++) {
      for(int j=0; j<N; j++)
         cout << m[i][j] << " ";
      cout << endl;
   }
   cout << endl;
}


int main()
{
    Matrix m = { {1,2,3,4},
                 {5,6,7,8},
                 {9,1,2,3} };
    print(m);

    //To initialize a 3 x 4 matrix with 0:
    Matrix n( 3,vector<int>(4,0));
    print(n);
    return 0;
}

输出:

1 2 3 4
5 6 7 8
9 1 2 3

0 0 0 0
0 0 0 0
0 0 0 0

8
我们可以使用几种方法将2D数组传递到函数中:
  • Using single pointer we have to typecast the 2D array.

     #include<bits/stdc++.h>
     using namespace std;
    
    
     void func(int *arr, int m, int n)
     {
         for (int i=0; i<m; i++)
         {
            for (int j=0; j<n; j++)
            {
               cout<<*((arr+i*n) + j)<<" ";
            }
            cout<<endl;
         }
     }
    
     int main()
     {
         int m = 3, n = 3;
         int arr[m][n] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
         func((int *)arr, m, n);
         return 0;
     }
    
  • Using double pointer In this way, we also typecast the 2d array

     #include<bits/stdc++.h>
     using namespace std;
    
    void func(int **arr, int row, int col)
    {
       for (int i=0; i<row; i++)
       {
          for(int j=0 ; j<col; j++)
          {
            cout<<arr[i][j]<<" ";
          }
          printf("\n");
       }
    }
    
    int main()
    {
      int row, colum;
      cin>>row>>colum;
      int** arr = new int*[row];
    
      for(int i=0; i<row; i++)
      {
         arr[i] = new int[colum];
      }
    
      for(int i=0; i<row; i++)
      {
          for(int j=0; j<colum; j++)
          {
             cin>>arr[i][j];
          }
      }
      func(arr, row, colum);
    
      return 0;
    }
    

2
为什么我不应该#include <bits/stdc++.h>? - David C. Rankin

8

一维数组会衰变成指向数组第一个元素的指针,而二维数组会衰变成指向第一行的指针。因此,函数原型应该是 -

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

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


7
您可以像这样做...
#include<iostream>

using namespace std;

//for changing values in 2D array
void myFunc(double *a,int rows,int cols){
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            *(a+ i*rows + j)+=10.0;
        }
    }
}

//for printing 2D array,similar to myFunc
void printArray(double *a,int rows,int cols){
    cout<<"Printing your array...\n";
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            cout<<*(a+ i*rows + j)<<"  ";
        }
    cout<<"\n";
    }
}

int main(){
    //declare and initialize your array
    double a[2][2]={{1.5 , 2.5},{3.5 , 4.5}};

    //the 1st argument is the address of the first row i.e
    //the first 1D array
    //the 2nd argument is the no of rows of your array
    //the 3rd argument is the no of columns of your array
    myFunc(a[0],2,2);

    //same way as myFunc
    printArray(a[0],2,2);

    return 0;
}

你的输出将如下所示...
11.5  12.5
13.5  14.5

1
我能想到的唯一一个在这种情况下对数组进行操作的原因是,一个人缺乏关于数组指针工作原理的知识。 - Lundin
5
变量i必须乘以列数,而不是行数,除非列数和行数相等,就像在这种情况下一样。 - Andrey Chernukha
1
(a+(icols)+j)是正确的,而不是*(a+(i*rows)+j),请修正。 - Sadegh J
无法编辑,因为建议的编辑队列已满,但正如@Sadegh所说,应该是*(a+(i* cols)+ j),因为您正在跳过该列中的元素以到达下一行。 - James Newton

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