将二维数组传递给 C++ 函数
Posted
技术标签:
【中文标题】将二维数组传递给 C++ 函数【英文标题】:Passing a 2D array to a C++ function 【发布时间】:2022-01-12 00:37:59 【问题描述】:我有一个函数,我想将一个可变大小的二维数组作为参数。
到目前为止,我有这个:
void myFunction(double** myArray)
myArray[x][y] = 5;
etc...
我已经在我的代码的其他地方声明了一个数组:
double anArray[10][10];
但是,调用 myFunction(anArray)
会出现错误。
我不想在传入数组时复制它。在myFunction
中所做的任何更改都应该改变anArray
的状态。如果我理解正确,我只想将指向二维数组的指针作为参数传递。该函数还需要接受不同大小的数组。例如,[10][10]
和 [5][5]
。我该怎么做?
【问题讨论】:
无法将参数 3 从 'double [10][10]' 转换为 'double **' accepted answer 仅显示 2 种技术 [其 (2) 和 (3) 相同] 但有 4 unique ways of passing a 2D array to a function . 严格来说,是的,它们不是 2D 数组,但是这种具有指针数组的约定(尽管会导致 UB),每个指针都指向(1D)数组,似乎很普遍: (拥有一个 mxn 长度的扁平 1D 数组,使用辅助函数/类来模拟 2D 数组可能会更好。 最简单-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);
【参考方案1】:
将二维数组传递给函数有三种方式:
参数为二维数组
int array[10][10];
void passFunc(int a[][10])
// ...
passFunc(array);
参数是一个包含指针的数组
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);
参数是指向指针的指针
int **array;
array = new int *[10];
for(int i = 0; i <10; i++)
array[i] = new int[10];
void passFunc(int **a)
// ...
passFunc(array);
【讨论】:
@Overflowh 可以通过array[i][j]
获取array
的元素:)
第一种情况,参数可以声明为int (*a)[10]
。
第二种情况,参数可以声明为int **
。
我会添加一个 4. 使用 vector<vector<int>>
案例 2 和 3 不是二维数组,所以这个答案具有误导性。 See this.【参考方案2】:
固定大小
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++ 中,通过引用传递数组而不丢失维度信息可能是最安全的,因为不必担心调用者传递了不正确的维度(不匹配时的编译器标志)。但是,这对于动态(freestore)数组是不可能的。它仅适用于自动 (usually stack-living) 数组,即在编译时应该知道维度。
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) 将指针传递给数组而不是数组本身。始终按原样传递数组成为指针复制操作,array's nature of decaying into a pointer 促进了这一操作。
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 的数组的指针时进行标记。第一个维度是变化部分,可以省略。 See here for the rationale 为什么只允许省略第一个维度。
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) 是最不安全的选项,因为它几乎没有任何类型检查,而且最不方便。不能合法地将二维数组传递给该函数; C-FAQ condemns 由于数组展平,通常使用 int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);
的解决方法是 may potentially lead to undefined behaviour。在此方法中传递数组的正确方法将我们带到了不方便的部分,即我们需要一个额外的(代理)指针数组,其每个元素都指向实际要传递的数组的相应行;然后将此代理项传递给函数(见下文);所有这些都是为了完成与上述方法相同的工作,这些方法更安全、更清洁,也许更快。
这是一个测试上述功能的驱动程序:
#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]):***.com/questions/16004668/… 我已经针对这个问题提出了问题:***.com/questions/27457076/…跨度> @42n4 案例 4 涵盖(对于 C++ 也是如此)。对于动态分配的数组,只有循环内的行会从b[i] = a[i];
变为b[i] = new int[10];
。也可以使 b
动态分配 int **b = int *[5];
并且它仍然可以按原样工作。
寻址array[i][j]
如何在4)中的函数中起作用?因为它已经接收到ptr到ptr并且不知道最后一维的值,所以需要执行移位才能正确寻址?
array[i][j]
只是指针运算,即指针array
的值,它会添加i
并将结果取消引用为int*
,它会添加j
并取消引用该位置,读取int
。所以,不,它不需要知道任何维度。但是,这就是重点!编译器相信程序员的话,如果程序员不正确,就会出现未定义的行为。这就是我提到案例 4 是最不安全的选择的原因。
在这种情况下,结构可能会很好地为您服务。【参考方案3】:
对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;
打印语句用于显示数组是通过引用传递的(通过显示变量的地址)
【讨论】:
您应该使用%p
打印一个指针,即使那样,您也必须将它转换为void *
,否则printf()
会调用未定义的行为。此外,在调用函数时不应使用 addressof (&
) 运算符,因为函数需要 double (*)[size_y]
类型的参数,而您当前将它们传递给 double (*)[10][10]
和 double (*)[5][5]
。
如果您使用模板,则将两个维度都作为模板参数更合适且更好,因为可以完全避免低级指针访问。
只有在编译时知道数组的大小时才有效。
@Georg 回答上面的代码正是我所建议的。它适用于 GCC 6.3 - online demo。您是否忘记将参数设为引用?【参考方案4】:
很惊讶还没有人提到这一点,但是您可以简单地在任何支持 [][] 语义的 2D 上进行模板化。
template <typename TwoD>
void myFunction(TwoD& myArray)
myArray[x][y] = 5;
etc...
// call with
double anArray[10][10];
myFunction(anArray);
它适用于任何二维“类数组”数据结构,例如std::vector<std::vector<T>>
,或用户定义的类型,以最大限度地重用代码。
【讨论】:
这应该是正确的答案。它解决了所有提到的问题和一些这里没有提到的问题。类型安全,数组的编译时不兼容,无指针运算,无类型转换,无数据复制。适用于 C 和 C++。 嗯,这适用于 C++; C 不支持模板。在 C 中执行此操作需要宏。 这个答案还远远不够。它没有解释如何迭代二维数组的元素。【参考方案5】:您可以像这样创建函数模板:
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);
【讨论】:
size_t
是比 int
更好的数组索引类型。【参考方案6】:
anArray[10][10]
不是指向指针的指针,它是适合存储 100 个 double 类型值的连续内存块,编译器知道如何寻址,因为您指定了维度。您需要将它作为数组传递给函数。可以省略初始维度的大小,如下:
void f(double p[][10])
但是,这将不允许您传递最后一维不是 10 的数组。
C++ 中的最佳解决方案是使用std::vector<std::vector<double> >
:它几乎同样高效,而且更加方便。
【讨论】:
几乎一样高效?对对。指针追踪总是比非指针追踪更昂贵。【参考方案7】:一维数组衰减为指向数组第一个元素的指针。而二维数组衰减到指向第一行的指针。所以,函数原型应该是 -
void myFunction(double (*myArray) [10]);
比起原始数组,我更喜欢 std::vector
。
【讨论】:
【参考方案8】:这是一个向量矩阵示例
#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
【讨论】:
【参考方案9】:你可以做这样的事情......
#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
【讨论】:
在这种情况下,我能想出为什么会破坏数组的唯一原因是因为缺乏关于数组指针如何工作的知识。 i 变量必须乘以列,而不是乘以行,除非在这种情况下列和行相等 * (a+ (i* cols) + j) 为真。不是这个 * (a+( i* rows) + j) 。请修复它 无法编辑,因为建议的编辑队列已满,但正如@Sadegh 所说,应该是* (a+ (i* cols) + j)
,因为您正在跳过该列中的元素以进入下一行。【参考方案10】:
我们可以使用多种方式将二维数组传递给函数:
使用单指针我们必须对二维数组进行类型转换。
#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;
使用双指针这样,我们也对二维数组进行了类型转换
#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;
【讨论】:
Why should I not #include <bits/stdc++.h>?【参考方案11】:传递多维数组的一个重要事项是:
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.使用单个指针: 在这种方法中,我们必须在传递给函数时对二维数组进行类型转换。
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;
【讨论】:
【参考方案12】:您可以使用 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)。
【讨论】:
【参考方案13】:如果要将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);
【讨论】:
【参考方案14】:如果您想将动态大小的二维数组传递给函数,使用一些指针可能对您有用。
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);
【讨论】:
【参考方案15】:您可以省略最左边的维度,因此您最终有两个选择:
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 维信息。
详情在here
虽然,数组和指针并不相同:数组可以衰减为指针,但指针不携带有关它所指向的数据的大小/配置的状态。
char **
是指向包含字符指针的内存块的指针,它们本身指向字符的内存块。 char [][]
是一个包含字符的单个内存块。这会影响编译器如何翻译代码以及最终性能如何。
Source
【讨论】:
以上是关于将二维数组传递给 C++ 函数的主要内容,如果未能解决你的问题,请参考以下文章