将2D数组传递给C ++函数


324

我有一个函数,希望将可变大小的2D数组作为参数。

到目前为止,我有这个:

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

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

double anArray[10][10];

但是,打电话myFunction(anArray)给我一个错误。

传递数组时,我不想复制该数组。对数组所做的任何更改myFunction都应更改的状态anArray。如果我理解正确,我只想将指向2D数组的指针作为参数传递。该函数还需要接受不同大小的数组。因此,例如[10][10][5][5]。我怎样才能做到这一点?


1
无法将参数3从'double [10] [10]'转换为'double **'
RogerDarwin 2012年

3
所述接受的答案仅示出了2技术[其(2)(3)是相同的],但有是4种独特的传递的2D阵列的功能的方式
legends2k 2014年

严格来说,是的,它们不是2D数组,但是这种惯例(尽管导致UB)是由一个指针数组组成的,每个指针指向一个1D数组,这似乎很普遍:(具有mxn的扁平1D数组长度,使用辅助函数/类来模拟2D数组可能更好
legends2k

最简单的 - 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

Answers:


413

有两种方法可以将2D数组传递给函数:

  1. 参数是2D数组

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

    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);
  3. 该参数是指向指针的指针

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

4
@Overflowh你可以arrayarray[i][j]:) 获得元素:
shengy

14
对于第一种情况,参数可以声明为int (*a)[10]
Zachary 2013年

9
对于第二种情况,参数可以声明为int **
2013年

1
@Zack:是的,实际上只有两种情况。一个是指向指针的指针,另一个是指向大小为n ie的整数数组的单个指针int (*a) [10]
legends2k

3
情况2和3不是2D阵列,因此此答案具有误导性。看到这个
隆丁

178

固定尺寸

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个整数的数组的单个指针,而此语法看起来像是2D数组,但指向相同的指针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])之类的静态和动态分配的数组完成:stackoverflow.com/questions/16004668 / ...我已经对此问题提出了问题:stackoverflow.com/questions/27457076/...
42n4

@ 42n4案例4涵盖了(同样适用于C ++)。对于动态分配的数组,仅循环内的行将从b[i] = a[i];变为b[i] = new int[10];。也可以进行b动态分配int **b = int *[5];,并且仍然可以按原样工作。
legends2k 2014年

1
寻址如何array[i][j]4)的功能中起作用?因为它已经收到ptr到ptr的信息,并且不知道最后一个维的值,所以执行正确的地址转换需要哪一个?
user1234567 2014年

2
array[i][j]只是指针算术,即指向指针的值array,它将i结果添加和取消引用为int*,然后将结果添加j到该位置并取消引用该位置,读取一个int。因此,不,它不需要知道任何维度。但是,这就是重点!编译器秉承程序员的信念,如果程序员不正确,则会出现未定义的行为。这就是我提到案例4是最不安全的选择的原因。
legends2k 2014年

在这种情况下,结构可以很好地为您服务。
Xofo

40

对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()调用未定义的行为。此外,&在调用函数时,请勿使用addressof()运算符,因为函数期望使用类型为实参double (*)[size_y],而当前您将其传递给double (*)[10][10]double (*)[5][5]

如果您使用的是模板,则将这两个维度都用作模板参数会更合适,并且会更好,因为可以完全避免低级指针访问。
legends2k 2014年

3
仅当在编译时知道数组的大小时,此方法才有效。
jeb_is_a_mess

上面答案中的@Georg代码正是我所建议的。它可以在GCC 6.3 在线演示中使用。您是否忘记了将参数作为参考?
legends2k

21

惊讶的是没有人提到这一点,但是您可以简单地在任何支持[] []语义的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>>或用户定义的类型)一起使用,以最大程度地重复使用代码。


1
这应该是正确的答案。它解决了所有提到的问题,这里没有提到。类型安全,数组的编译时间不兼容,没有指针算术,没有类型转换,没有数据复制。适用于C和C ++。
OpalApps

好吧,这适用于C ++。C不支持模板。在C中执行此操作将需要宏。
贡纳尔

20

您可以创建一个功能模板,如下所示:

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

2
size_t是比数组索引更好的数组索引类型int
Andrew Tomazos

13

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

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

但是,这将不允许您传递最后一个维度为十的数组。

C ++最好的解决方案是使用std::vector<std::vector<double> >:它几乎一样有效,并且方便得多。


1
我更喜欢这种解决方案,因为std库非常有效-通过我喜欢dasblinkenlight的方式;我曾经使用过dasblikenlicht
mozillanerd 2012年

效率差不多吗?是的,对。指针追逐总是比非指针追逐更昂贵。
Thomas Eding

8

一维数组会衰减为指向该数组中第一个元素的指针指针。而2D数组衰减到指向第一行的指针。因此,函数原型应为-

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

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


8

你可以做这样的事情...

#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
在这种情况下,我想出一个为什么会破坏数组的唯一原因是,因为人们缺乏关于数组指针如何工作的知识。
伦丁2015年

3
i变量必须乘以列,而不是行,除非这种情况下列和行相等
Andrey Chernukha

4

这是向量矩阵的向量示例

#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

2

我们可以使用几种方法将2D数组传递给函数:

  • 使用单指针,我们必须对2D数组进行类型转换。

    #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;
    }
  • 使用双指针以这种方式,我们还转换了2d数组

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

1

传递多维数组的重要一件事是:

  • First array dimension 无需指定。
  • Second(any any further)dimension 必须指定。

1,全局只有第二维可用时(作为宏或全局常数)

`const int N = 3;

`void print(int arr[][N], int m)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < N; j++)
    printf("%d ", arr[i][j]);
}`

int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
print(arr, 3);
return 0;
}`

2.使用单个指针:在此方法中,传递给函数时必须对2D数组进行类型转换。

`void print(int *arr, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < n; j++)
    printf("%d ", *((arr+i*n) + j));
 }

`int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int m = 3, n = 3;

// We can also use "print(&arr[0][0], m, n);"
print((int *)arr, m, n);
return 0;
}`

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的一个)。


0

如果要通过int a[2][3],则void func(int** pp)需要执行以下辅助步骤。

int a[2][3];
int* p[2] = {a[0],a[1]};
int** pp = p;

func(pp);

由于[2]可以隐式指定第一个,因此可以进一步简化。

int a[][3];
int* p[] = {a[0],a[1]};
int** pp = p;

func(pp);

0

如果您想将动态大小的二维数组传递给函数,则可以使用一些指针。

void func1(int *arr, int n, int m){
    ...
    int i_j_the_element = arr[i * m + j];  // use the idiom of i * m + j for arr[i][j] 
    ...
}

void func2(){
    ...
    int arr[n][m];
    ...
    func1(&(arr[0][0]), n, m);
}

0

您可以省略最左边的尺寸,因此最终有两个选择:

void f1(double a[][2][3]) { ... }

void f2(double (*a)[2][3]) { ... }

double a[1][2][3];

f1(a); // ok
f2(a); // ok 

指针也是如此:

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double***’ 
// double ***p1 = a;

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double (**)[3]’
// double (**p2)[3] = a;

double (*p3)[2][3] = a; // ok

// compilation error: array of pointers != pointer to array
// double *p4[2][3] = a;

double (*p5)[3] = a[0]; // ok

double *p6 = a[0][1]; // ok

C ++标准允许将N维数组衰减为指向N-1维数组的指针,因为您可以丢失最左侧的维,并且仍然能够正确访问具有N-1维信息的数组元素。

详情在这里

但是,数组和指针并不相同:数组可以衰减为指针,但是指针不会携带有关其指向的数据的大小/配置的状态。

A char **是指向包含字符指针的存储块的指针字符指针本身指向字符的存储块。A char [][]包含字符的单个存储块。这会影响编译器如何翻译代码以及最终性能如何。

资源

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.