如何将多维数组传递给 C 和 C++ 中的函数
Posted
技术标签:
【中文标题】如何将多维数组传递给 C 和 C++ 中的函数【英文标题】:How to pass a multidimensional array to a function in C and C++ 【发布时间】:2011-02-19 04:41:53 【问题描述】:#include<stdio.h>
void print(int *arr[], int s1, int s2)
int i, j;
for(i = 0; i<s1; i++)
for(j = 0; j<s2; j++)
printf("%d, ", *((arr+i)+j));
int main()
int a[4][4] = 0;
print(a,4,4);
这适用于 C,但不适用于 C++。
错误:
cannot convert `int (*)[4]' to `int**' for argument `1' to
`void print(int**, int, int)'
为什么它在 C++ 中不起作用?需要做出什么改变?
【问题讨论】:
嗨 - 有没有想过使用 STL-Libs 而不是数组?它们在 C++ 中非常方便,可以解决很多这样的问题 ;-) 用gcc
编译为C,你会得到passing argument 1 of 'print' from incompatible pointer type
的警告;此外,索引逻辑很糟糕,而且似乎只有在sizeof (int) == sizeof (int *)
的情况下才有效(对于某些工作值)
+1 用于使用 STL 容器:cplusplus.com/reference/stl
您是否有理由使用*((arr+i)+j))
而不是更清晰的arr[i][j]
?
来吧伙计们,回答问题。我敢肯定他知道 STL。
【参考方案1】:
因为当今所有(大多数)系统都是 Row Major Ordered。
这是一个不言自明的代码,它利用了多维数组也是串行存储的概念。
示例工作代码:
#include<bits/stdc++.h>
using namespace std;
void print1DArray(int arr[])
cout << "1D array print: " << arr[3] << endl;
int get1DIndex(int r, int c, int noOfColumns)
return r*noOfColumns + c;
void print2DArray(int *mat, int noOfColumns)
cout << "\n-------------------------------\n";
cout << "1D index generated: " << get1DIndex(1, 2, noOfColumns); // prints 5
cout << endl << "Inside printAllPaths: " << mat[get1DIndex(1, 2, noOfColumns)]; // prints 5
int main()
int arr[5] = 0, 1, 2, 3, 4;
int mat[3][3] = 0, 1, 2, 3, 4, 5, 6, 7, 8;
print1DArray(arr);
print2DArray(*mat, 3);
return 0;
【讨论】:
【参考方案2】:对于传递一维数组,请在此处查看我的其他答案:Passing an array as an argument to a function in C
TLDR;
直接跳下来,查看“结论和建议摘要...”部分下的 4 个打印示例。
如何在 C 和 C++ 中使用多维(例如:2D)数组和指向它们的指针作为函数参数
对于包括我自己在内的几乎所有人来说,多维数组都非常令人困惑——对于有经验的程序员和初学者来说都是如此。因此,我想提供一组规范的示例,我可以一遍又一遍地回顾和参考(但是,请参阅此答案下方的 cmets;我并没有涵盖所有内容),并带有一些清晰的演示和易于-遵循指导方针。就这样吧。
前言:
注意:从 C2x(C20 或更高版本)开始,以下“原始原则”准则 15 生效(来源:Wikipedia: C2x --> 原始来源:Programming Language C - C2x Charter):
应用程序编程接口 (API) 应尽可能自行记录。 特别是,应安排函数声明中的参数顺序,以使数组的大小出现在数组之前。目的是允许使用可变长度数组 (VLA) 表示法。这不仅使人类读者更清楚代码的用途,而且使静态分析更容易。添加到标准中的任何新 API 都应考虑到这一点。
所以,如果您想符合 C2x(迄今为止大多数标准 C 函数不符合),那么请重新排列我下面的所有函数,以将数组大小参数放在 之前数组或指向数组的参数。
我开始回答这个问题是为了回答这个问题:Passing a pointer to array to my function。但是,它更适合这里,所以我把它放在这里。
下面的代码可以在我的eRCaGuy_hello_world repo 中找到:c/2d_array_practice.c。它在 C 和 C++ 中编译和运行(在 C11 和 C++17 中测试)。请参阅源代码文件顶部的构建和运行命令。为了安全起见,我使用 gcc 构建选项 -Wall -Wextra -Werror
。
我的答案集中在多维 2D 数组,但可以轻松扩展到任意数量的维度:3D、4D、5D 等...ND 数组。
我在打印函数中使用const
,因为我只是打印数组而不是修改它们。如果您需要就地修改数组,请删除 const
。
首先要注意的数组注意事项:
固定尺寸与未指定尺寸:
数组必须在所有维度上固定(指定)大小,第一个(最外层)维度除外,可以选择未指定。
// OK; 1D array with unspecified 1st (and only) dimension
int array[] = 1, 2, 3;
// OK; 2D array with unspecified 1st dimensions
int array[][2] = 1, 2, 3, 4;
// NOT allowed; 2D array with both dimensions unspecified!:
// `error: array type has incomplete element type ‘int[]’`
int array[][] = 1, 2, 3, 4;
数组类型的自然类型衰减:
首先,让我区分“数组类型”和“指向数组类型的指针”。 “数组类型”是数组,“指向数组类型的指针”是指针。指针不能衰减到(AKA“调整”成为)指针,因为它们是已经的指针。然而,数组可以并且确实衰减为(“调整”为)指针。
(1) 所以,这里有一些数组类型的例子,意味着它们只是普通的“数组”:int array_2d[][2]
, int* array_2d[]
, int array_2d[3][2]
.第一个是二维整数数组,第二个是一维数组int*
,第三个是二维整数数组。
(2) 但是,这是一个指向数组类型的指针,或“指向数组的指针”:int (*array_2d)[3][2]
。数组的 ptrs 总是在星号周围有括号,如下所示:(*)
,就在方括号之前。这样你才能认出它们。因此,上面的前 3 个数组在用作参数时会衰减为 ptrs,而最后一个则不会,因为它已经是一个 ptr。
原理是:当用作函数参数时,所有数组类型(但不是数组类型的ptrs)将第一个维度衰减为一个ptr,无论该维度是大小是否明确指定!因此,尽管int arr[]
(整数数组)和int * arr
(指向整数的指针)不是同一类型,但其中任何一个的函数定义都会自然衰减数组中的第一个维度(这是唯一的在一维数组情况下的维度)向下到一个指针,导致在两种情况下都将类型(int * arr
)传递给函数:
// accepts `int *` as `array` parameter
void my_func(int * array, size_t len)
// also accepts `int *` as `array` parameter, since the `int []` type
// (array of ints) naturally decays down to type `int *` (ptr to int`).
void my_func(int array[], size_t len)
更进一步,将大小指定为数组的第一个维度与此效果无关。即:它没有任何区别,对编译器毫无意义。它只是作为一个视觉指示器或“自我文档”类型程序员就是这样,这个特定的函数需要一个至少这个大小或更大的数组。请在此处查看我的回答 (Passing an array as an argument to a function in C),其中我谈到了这一点,并引用了 MISRA-C 标准,该标准建议将此功能用于自我记录目的。
所以,这些都和上面的函数一样:
// same as above: these ALL accept `int *` as the 1st parameter, and the
// specified size here in square brackets [] has no effect on the compiler.
void my_func(int array[1], size_t len)
void my_func(int array[10], size_t len)
void my_func(int array[100], size_t len)
所以,这很好:
int array[10];
my_func(array); // ok; `array` naturally decays down to type `int *`
这也很好:
int array[10];
int * array_p = array;
my_func(array_p); // ok; is already of type `int *`
但是,对于 指向数组的指针,实际的数组类型和大小确实很重要,并且不会发生从数组到 ptr 的自然类型衰减,因为类型已经一个ptr——指向一个指定类型和大小的数组!阅读my answer above。
示例:以下函数需要一个 ptr 类型的输入参数到大小为 10 的一维数组。它已经是一个ptr,所以不会发生自然类型衰减到一个ptr!由于这个参数是一个数组的ptr,你必须在创建函数时也通过&
字符传递数组的地址调用,如下图。请注意,以下调用中只有 2 个有效:my_func(&array2);
和 my_func(array2_p2);
。然而,我努力展示了所有这些调用,以便能够解释和演示各种数组类型以及它们如何以及何时衰减为指针,以及什么类型。
// 0. Define a function
/// `array` is a "ptr to an array of 10 ints".
void my_func(int (*array)[10])
// 1. Create arrays
int array1[5];
int *array1_p = array1; // array1_p is of type `int *` (ptr to int)
int (*array1_p2)[5] = &array1; // array1_p2 is of type `int (*)[5]` (ptr
// to array of 5 ints)
int array2[10];
int *array2_p = array2; // array2_p is of type `int *` (ptr to int)
int (*array2_p2)[10] = &array2; // array2_p2 is of type `int (*)[10]` (ptr
// to array of 10 ints)
// 2. Make some calls
// 2.1. calling with `int array1[5]`
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to **natural type decay** from
// `int[5]` (array of 5 ints) to `int *` (ptr to int)
my_func(array1);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to dereferencing to `int[5]` (array
// of 5 ints), followed by **natural type decay** from `int[5]`
// (array of 5 ints) to `int *` (ptr to int)
my_func(*array1_p2);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int)
my_func(array1_p);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`; due to **natural type decay** from `int[5]` (array of
// 5 ints) to `int *` (ptr to int), in conjunction with dereferencing
// from that to `int`
my_func(*array1);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`
my_func(*array1_p);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (*)[5]` (ptr to array of 5 ints)
my_func(&array1);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (*)[5]` (ptr to array of 5 ints)
my_func(array1_p2);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (**)[5]` (ptr to "ptr to array of 5 ints")
my_func(&array1_p2);
// 2.2. calling with `int array2[10]`
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to **natural type decay** from
// `int[10]` (array of 10 ints) to `int *` (ptr to int)
my_func(array2);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int); due to dereferencing to `int[10]` (array
// of 10 ints), followed by **natural type decay** from `int[10]`
// (array of 10 ints) to `int *` (ptr to int)
my_func(*array2_p2);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int *` (ptr to int)
my_func(array2_p);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`; due to **natural type decay** from `int[10]` (array of
// 10 ints) to `int *` (ptr to int), in conjunction with dereferencing
// from that to `int`
my_func(*array2);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int`
my_func(*array2_p);
// <===============
// <=== WORKS! ====
// <===============
// Expected and received `int (*)[10]` (ptr to array of 10 ints)
my_func(&array2);
// <===============
// <=== WORKS! ====
// <===============
// Expected and received `int (*)[10]` (ptr to array of 10 ints)
my_func(array2_p2);
// FAILS! Expected `int (*)[10]` (ptr to array of 10 ints) but argument is
// of type `int (**)[10]` (ptr to "ptr to array of 10 ints")
my_func(&array2_p2);
指针快速提醒:
请记住,int *[2]
和 int (*)[2]
类型不是同一类型!括号很重要! int *[2]
是“2 个 int *
s 的数组(指向 int 的指针)”,而 int (*)[2]
是“指向 2 个 int
s 的数组的指针”。这是两个非常不同的东西。
另外,ptr 可以像数组一样被索引,这会导致经常混淆数组是一个ptr,这是错误的。数组不是 ptr!但是,以下概念对于理解下面的代码至关重要:int array_2d[][2]
是一个二维数组。变量array_2d
的类型为int [][2]
(n x 2 (2D) 整数数组),它是具有未指定行数(维度 1)和 2 列(维度 2)的二维数组。当用作函数参数时,此int [][2]
类型自然衰减 类型int (*)[2]
(ptr 到 2 个整数的 (1D) 数组)。那么,如果这个衰减的类型是一个ptr,它怎么还是一个数组呢?好吧,既然 ptr 可以像数组一样被索引,你仍然可以这样做来索引它:array_2d[row][col]
。外部维度是 ptr,可索引为 row
,而内部维度是 [2]
(2 个整数)部分,可索引为 col
umn,因为它是数组中的子数组.这意味着每行包含 2 个整数,因此一旦您索引到 row
,您就需要索引到 col
umn。因此,ptrs 和数组之间的混淆在于 所有 ptr 都可以像数组一样索引,即使 数组不是 ptrs——而是第一个维度当用作参数时,所有数组(但不是数组的指针)衰减为一个指针。
因此,考虑到上述概念,以下内容会更有意义。对于每一个函数定义,注意数组类型是什么,会不会自然衰减,具体是什么。同样,当用作函数参数时,所有 非指针 数组类型都会将数组的第一个维度向下衰减为 ptr,它仍然像数组一样可索引。
关于传递多维数组的结论和建议总结:
以下是我将多维数组作为参数传递的 4 个用例和技术,以及何时使用它们的建议。 您可以从每种技术的函数原型和定义中看到每种技术提供的不同权衡、复杂性和优势。
假设您有以下二维数组:
int arr[][2] =
1, 2,
5, 6,
7, 8,
;
...以及以下宏定义:
// Get the number of elements in any C array
// - from my repo here:
// https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/c/utilities.h#L42
#define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
/// Definitions: `rows` = "rows"; `cols` = "columns"
/// Get number of rows in a 2D array
#define NUM_ROWS(array_2d) ARRAY_LEN(array_2d)
/// Get number of columns in a 2D array
#define NUM_COLS(array_2d) ARRAY_LEN(array_2d[0])
-
固定大小的多维数组:如果 2D 数组每次总是相同的大小(它有 FIXED 行数和 FIXED 列数)(3 行和 2以下示例中的列),请执行以下操作:
// 1. Function definition
/// \brief Print a 2D array which has a FIXED number of rows and
/// FIXED number of columns.
/// \param[in] array_2d a 2D array; is of type `int (*)[3][2]` (ptr to
/// 3 x 2 (2D) array of ints); since it is already
/// explicitly a ptr, it does NOT naturally decay to
/// any other type of ptr
/// \return None
void print_array2(const int (*array_2d)[3][2])
printf("print_array2:\n");
for (size_t row = 0; row < NUM_ROWS(*array_2d); row++)
for (size_t col = 0; col < NUM_COLS(*array_2d); col++)
printf("array_2d[%zu][%zu]=%i ", row, col, (*array_2d)[row][col]);
printf("\n");
printf("\n");
// 2. Basic usage
// NB: `&` is REQUIRED! See my answer for why: https://***.com/a/51527502/4561887
print_array2(&arr);
// 3. Usage via a pointer
// `int (*array_2d)[3][2]` is an explicit ptr to a 3x2 array of `int`. This
// pointer to an array does NOT naturally decay to a simpler type.
int (*p2)[3][2] = &arr; // must use `&` and MUST USE THESE PARENTHESIS!
print_array2(p2);
如果二维数组的行数是可变的,但列数是固定的(在本例中为 2),
做这个:
// 1. Function definition
/// \brief Print a 2D array which has a VARIABLE number of rows but
/// FIXED number of columns.
/// \param[in] array_2d a 2D array; is of type `int [][2]` (n x 2 (2D) array
/// of ints), which naturally decays to type
/// `int (*)[2]` (ptr to (1D) array of 2 ints)
/// \param[in] num_rows The number of rows in the array
/// \return None
void print_array3(const int array_2d[][2], size_t num_rows)
printf("print_array3:\n");
// Technique 1: use `array_2d` directly.
printf("--- Technique 1: ---\n");
for (size_t row = 0; row < num_rows; row++)
for (size_t col = 0; col < NUM_COLS(array_2d); col++)
printf("array_2d[%zu][%zu]=%i ", row, col, array_2d[row][col]);
printf("\n");
// Technique 2: cast the `array_2d` decayed ptr to a ptr to a sized array of
// the correct size, then use that ptr to the properly-sized array
// directly! NB: after obtaining this ptr via the cast below, this
// technique is **exactly identical** to (I copy/pasted it from, then
// renamed the variable) the implementation inside `print_array2()` above!
printf("--- Technique 2: ---\n");
int (*array_2d_ptr)[num_rows][NUM_COLS(array_2d)] =
(int (*)[num_rows][NUM_COLS(array_2d)])array_2d;
for (size_t row = 0; row < NUM_ROWS(*array_2d_ptr); row++)
for (size_t col = 0; col < NUM_COLS(*array_2d_ptr); col++)
printf("array_2d_ptr[%zu][%zu]=%i ", row, col, (*array_2d_ptr)[row][col]);
printf("\n");
printf("\n");
// 2. Basic usage
print_array3(arr, NUM_ROWS(arr));
// 3. Usage via a pointer
// `int array_2d[][2]` (n x 2 (2D) array of ints) naturally decays to
// `int (*)[2]` (ptr to (1D) array of 2 ints)
int (*p3)[2] = arr; // MUST USE THESE PARENTHESIS!
print_array3(p3, NUM_ROWS(arr));
如果二维数组有可变的行数和可变的列数,请执行此操作(
方法是最通用的,因此通常是我的整体首选方法,也是多维数组的首选方法):
// 1. Function definition
/// \brief Print a 2D array which has a VARIABLE number of rows and
/// VARIABLE number of columns.
/// \param[in] array_2d a 2D array; is of type `int *` (ptr to int); even
/// though a 1D array of type `int []` (array of ints)
/// naturally decays to this type, don't think about it
/// that way; rather, think of it as a ptr to the first
/// `int` in a contiguous block of memory containing a
/// multidimensional array, and we will manually index
/// into it as required and according to its dimensions
/// \param[in] num_rows The number of rows in the array
/// \param[in] num_cols The number of columns in the array
/// \return None
void print_array4(const int *array_2d, size_t num_rows, size_t num_cols)
printf("print_array4:\n");
// Technique 1: use `array_2d` directly, manually indexing into this
// contiguous block of memory holding the 2D array data.
printf("--- Technique 1: ---\n");
for (size_t row = 0; row < num_rows; row++)
const int *row_start = &array_2d[row*num_cols];
for (size_t col = 0; col < num_cols; col++)
// NB: THIS PART IS VERY DIFFERENT FROM THE OTHERS! Notice `row_start[col]`.
printf("array_2d[%zu][%zu]=%i ", row, col, row_start[col]);
printf("\n");
// Technique 2: cast the `array_2d` decayed ptr to a ptr to a sized array of
// the correct size, then use that ptr to the properly-sized array
// directly! NB: after obtaining this ptr via the cast below, this
// technique is **exactly identical** to (I copy/pasted it from, then
// renamed the variable) the implementation inside `print_array2()` above!
printf("--- Technique 2: ---\n");
int (*array_2d_ptr)[num_rows][num_cols] =
(int (*)[num_rows][num_cols])array_2d;
for (size_t row = 0; row < NUM_ROWS(*array_2d_ptr); row++)
for (size_t col = 0; col < NUM_COLS(*array_2d_ptr); col++)
printf("array_2d_ptr[%zu][%zu]=%i ", row, col, (*array_2d_ptr)[row][col]);
printf("\n");
printf("\n");
// 2. Basic usage
print_array4((int *)arr, NUM_ROWS(arr), NUM_COLS(arr));
// OR: alternative call technique:
print_array4(&arr[0][0], NUM_ROWS(arr), NUM_COLS(arr));
// 3. Usage via a pointer
// The easiest one by far!
int *p4_1 = (int*)arr;
// OR
int *p4_2 = &arr[0][0];
print_array4(p4_1, NUM_ROWS(arr), NUM_COLS(arr));
print_array4(p4_2, NUM_ROWS(arr), NUM_COLS(arr));
但是,如果您有以下“2D”数组,则必须做一些不同的事情:
// Each row is an array of `int`s.
int row1[] = 1, 2;
int row2[] = 5, 6;
int row3[] = 7, 8;
// This is an array of `int *`, or "pointer to int". The blob of all rows
// together does NOT have to be in contiguous memory. This is very different
// from the `arr` array above, which contains all data in contiguous memory.
int* all_rows[] = row1, row2, row3; // "2D" array
-
如果二维数组实际上是由一堆指向其他数组的 ptr 构成的(如上所示),
做这个:
// 1. Function definition
/// \brief Print a 2D-like array, where the array passed in is an array of
/// ptrs (int *) to other sub-arrays. Each index into the outer
/// array is the row, then each index into a sub-array in a given
/// row is the column. This handles a VARIABLE number of rows and
/// VARIABLE number of columns.
/// \details `array_2d` here is different from all of the cases above. It is
/// NOT a contiguous 2D array of `int`s; rather, it is an array of
/// pointers to ints, where each pointer in the array can be
/// thought of as a sub-array. Therefore, the length of the outer
/// array is the number of rows, and the length of each sub-array,
/// or inner array, is the number of columns. Each sub-array
/// (a single row of `int`s) DOES have to be in contiguous memory,
/// and the array of _pointers_ DOES have to be in contiguous
/// memory, but the total _storage space_ for the combined total of
/// all rows can be in NON-contiguous memory. Again, this is VERY
/// different from every other function above.
/// \param[in] array_2d a 2D array; is of type `int * []` (array of ptrs to
/// int) (where each ptr is a sub-array of ints);
/// `int * []` naturally decays to type `int**` (ptr to
/// "ptr to int")
/// \param[in] num_rows The number of rows in the array (number of elements
/// in the `array_2d` outer array)
/// \param[in] num_cols The number of columns in the array (number of
/// elements in each sub-array)
/// \return None
void print_array5(const int* array_2d[], size_t num_rows, size_t num_cols)
printf("print_array5:\n");
printf("--- Technique 1: use `row_start[col]` ---\n");
for (size_t row = 0; row < num_rows; row++)
const int *row_start = array_2d[row]; // VERY DIFFERENT FROM `print_array4` above!
for (size_t col = 0; col < num_cols; col++)
// Identical to `print_array4` above.
printf("array_2d[%zu][%zu]=%i ", row, col, row_start[col]);
printf("\n");
printf("--- Technique 2: use `array_2d[row][col]` ---\n");
for (size_t row = 0; row < num_rows; row++)
for (size_t col = 0; col < num_cols; col++)
// OR you can simply do this!
printf("array_2d[%zu][%zu]=%i ", row, col, array_2d[row][col]);
printf("\n");
printf("\n");
// 2. Basic usage
print_array5(all_rows, ARRAY_LEN(all_rows), ARRAY_LEN(row1));
// 3. Usage via a pointer
//
// 3.1. Easier way: ptr to "ptr to int"; note: `int* array_2d[]` naturally
// decays to `int**`.
const int **p5_1 = all_rows;
print_array5(p5_1, ARRAY_LEN(all_rows), ARRAY_LEN(row1));
//
// 3.2. OR this more-complicated way, for the sake of demonstration:
// ptr to array of 3 `int*`s
const int* (*p5_2)[ARRAY_LEN(all_rows)] = &all_rows;
// Explanation: the type of `p5_2` is `int* (*)[3]` (ptr to array of 3
// int*), so the type of `*p5_2` is `int* [3]` (array of 3 int*), which
// decays naturally to `int**`, which is what `*p5_2` ends up passing to
// this function call! So, this call to `print_array5()` here and the one
// just above are therefore exactly identical!
print_array5(*p5_2, ARRAY_LEN(all_rows), ARRAY_LEN(row1));
不要忘记结构!
但不要忘记,有时,只使用结构会容易得多!
例子:
typedef struct data_s
int x;
int y;
data_t;
// Array of the above struct
data_t data_array[] =
1, 2,
5, 6,
7, 8,
;
void print_struct_data(data_t * data, size_t len)
for (size_t i = 0; i < len; i++)
printf("[data[%zu].x, data[%zu].y] = [%i, %i]\n",
i, i, data[i].x, data[i].y);
printf("\n");
print_struct_data(data_array, ARRAY_LEN(data_array));
输出:
[data[0].x, data[0].y] = [1, 2] [data[1].x, data[1].y] = [5, 6] [data[2].x, data[2].y] = [7, 8]
完整的、可运行的代码:
完整的、可运行的代码会导致此答案超过答案中允许的最大 30000 个字符。因此,请在此处下载完整代码:c/2d_array_practice.c,在我的 eRCaGuy_hello_world 存储库中。
示例输出(减少;请自行运行完整代码):
print_array1: array_2d[0][0]=1 array_2d[0][1]=2 array_2d[1][0]=5 array_2d[1][1]=6 array_2d[2][0]=7 array_2d[2][1]=8 print_array2: array_2d[0][0]=1 array_2d[0][1]=2 array_2d[1][0]=5 array_2d[1][1]=6 array_2d[2][0]=7 array_2d[2][1]=8 print_array3: --- Technique 1: --- array_2d[0][0]=1 array_2d[0][1]=2 array_2d[1][0]=5 array_2d[1][1]=6 array_2d[2][0]=7 array_2d[2][1]=8 --- Technique 2: --- array_2d_ptr[0][0]=1 array_2d_ptr[0][1]=2 array_2d_ptr[1][0]=5 array_2d_ptr[1][1]=6 array_2d_ptr[2][0]=7 array_2d_ptr[2][1]=8 print_array4: --- Technique 1: --- array_2d[0][0]=1 array_2d[0][1]=2 array_2d[1][0]=5 array_2d[1][1]=6 array_2d[2][0]=7 array_2d[2][1]=8 --- Technique 2: --- array_2d_ptr[0][0]=1 array_2d_ptr[0][1]=2 array_2d_ptr[1][0]=5 array_2d_ptr[1][1]=6 array_2d_ptr[2][0]=7 array_2d_ptr[2][1]=8 print_array5: --- Technique 1: use `row_start[col]` --- array_2d[0][0]=1 array_2d[0][1]=2 array_2d[1][0]=5 array_2d[1][1]=6 array_2d[2][0]=7 array_2d[2][1]=8 --- Technique 2: use `array_2d[row][col]` --- array_2d[0][0]=1 array_2d[0][1]=2 array_2d[1][0]=5 array_2d[1][1]=6 array_2d[2][0]=7 array_2d[2][1]=8 Don't forget about just using structs and arrays of structs instead, which is sometimes much easier! [data[0].x, data[0].y] = [1, 2] [data[1].x, data[1].y] = [5, 6] [data[2].x, data[2].y] = [7, 8]
参考资料:
-
我使用的主要参考资料:我自己的答案,其中包含有关“在 C 中强制数组的类型安全”的信息,以及如何解释和读取指向数组的一维指针,如下所示:
int (*a)[2]
:@ 987654331@
https://www.geeksforgeeks.org/pass-2d-array-parameter-c/
相关:
-
[我的回答]Arduino Stack Exchange: Initializing Array of structs
[我的答案引用了这个答案]Passing a pointer to array to my function
【讨论】:
作为一个规范的答案,它没有涵盖C的变长数组,也没有涵盖the order of parameters .... should be arranged such that the size of an array appears before the array的C2x原理 @chux-ReinstateMonica,是的。我暂时不要管那些。我已经 4+ 小时了。我会稍微修改一下措辞。 您所说的与函数参数有关的“自然衰减”,标准术语已调整。您可以在标准中查找它 请解释一下“非指针数组类型”是什么意思。所有数组类型都不是指针类型,反之亦然。int (*array_2d)[3][2]
是指针类型(不是数组类型)。我认为将其描述为“指针数组类型”会令人困惑,因为它不是数组类型。许多初学者无法区分数组和指针,这种措辞也无济于事。你可以说“指向数组类型的指针”。【参考方案3】:
问题是,
int a[4][4];
实际上将存储在物理上连续的内存中。因此,要访问 4x4 数组的任意部分,函数“print”需要知道数组的维度。例如下面的一小段代码,将以两种不同的方式访问内存的同一部分。
#include <iostream>
void print(int a[][4])
for (int i = 0; i <4; i++)
for (int j = 0; j < 4; j++)
//accessing as 4x4 array
std::cout << a[i][j] <<std::endl;
//accessing corresponding to the physical layout in memory
std::cout << *(*(a)+ i*4 + j) << std::endl;
int main()
int a[4][4];
//populating the array with the corresponding indices from 0 to 15
int m = 0;
for (int i = 0; i<4; i++)
for (int j= 0; j < 4; j++)
a[i][j] = m;
m++;
print(a);
所以内存布局不会改变,但访问方式会改变。它可以像棋盘一样可视化。
0 1 2 3
----------
0| 1 2 3 4
1| 5 6 7 8
2| 9 10 11 12
3|13 14 15 16
但真正的物理内存是这样的。
0*4+0 0*4+1 0*4+2 0*4+3 1*4+0 1*4+1 1*4+2 1*4+3 2*4+1 etc.
-----------------------------------------------------
1 2 3 4 5 6 7 8 9 etc.
在 c++ 中,数组的数据是逐行存储的,并且始终需要行的长度(在本例中为 4)才能获得下一行的正确内存偏移量。因此,第一个下标仅表示声明数组时所需的存储量,但不再需要在之后计算偏移量。
【讨论】:
很好的解释,但是在您的示例代码中,您应该定义传递 i 的限制值的函数,否则循环直到达到 4 是危险的(如果我使用 a[1] 调用[4] 数组我可能会遇到段错误,但对于编译器来说没问题)。 更准确地说,它需要知道列数,而不一定是行数。【参考方案4】:我只想展示 C++ 版本的 bobobobo 的答案。
int numRows = 16 ;
int numCols = 5 ;
int **a ;
a = new int*[ numRows* sizeof(int*) ];
for( int row = 0 ; row < numRows ; row++ )
a[row] = new int[ numCols*sizeof(int) ];
其余代码与bobobobo的相同。
【讨论】:
【参考方案5】:多维数组是连续的内存块。所以你可以这样做:
#include <stdio.h>
void pa(const int *a, int y, int x)
int i, j;
for (i=0;i<y;i++)
for (j=0;j<x;j++)
printf("%i", *(a+j+i*x));
printf("\n");
int main()
int a[4][3] = 1,2,3,
4,5,6,
4,5,6,
7,8,9 ;
pa(a[0], 4, 3);
return 0;
它也适用于 C++;
【讨论】:
【参考方案6】:此代码将不在 C 或 C++ 中工作。 int[4][4]
类型的数组不能转换为 int **
类型的指针(这是 int *arr[]
在参数声明中的含义)。如果您设法在 C 中编译它,那仅仅是因为您可能忽略了与您从 C++ 编译器获得的错误消息格式基本相同的 C 编译器警告。 (有时 C 编译器会发出 warnings 本质上是一个错误。)
因此,再次重申,不要做出不正确的断言。此代码在 C 中不起作用。要将内置 2D 数组转换为 int **
指针,您可以使用类似这样的技术
Converting multidimensional arrays to pointers in c++
(查看接受的答案。问题完全一样。)
编辑:代码似乎在 C 中工作,因为打印代码中的另一个错误正在伪装数组传递中错误的影响。为了正确访问int **
伪数组的元素,您必须使用表达式*(*(arr + i) + j)
,或者更好的是普通的arr[i][j]
(这是同一件事)。您错过了额外的*
,这使它打印的内容与您的数组内容完全无关。再次,将 main
中的数组初始化为其他内容,以查看您在 C 中打印的结果与数组的预期内容完全无关。
如果您如上所示更改printf
语句,您的代码很可能会因为我最初描述的数组传递错误而崩溃。
再一次:您不能将int[4][4]
数组作为int **
伪数组传递。这就是 C++ 在错误消息中告诉您的内容。而且,我敢肯定,这是你的 C 编译器告诉你的,但你可能忽略了它,因为它“只是一个警告”。
【讨论】:
@cambr:再来一次:代码不起作用。无论你从 C 中得到什么结果都是垃圾,只是偶然碰巧看起来像正确的输出。将您的数组初始化为其他值(不是零),然后重试。 好吧。类型不同,但底层内存地址相同。因此,将其视为对 C 编译器的警告是合理的。 @kriss:呃......我不知道“底层内存地址相同”应该是什么意思。int[4][4]
数组的物理结构与int **
伪数组一点也不相似。任何通过int **
指针访问int[4][4]
数组的尝试通常都会导致段错误。或者是毫无意义的结果。在这种情况下,无意义的结果意外地看起来像有意义的结果,这就是 OP 决定代码有效的原因。
@AndreyT:我在答案中将 int** 固定为 int* 这绝对是一个错误。但是&a == &a[0] == &a[0][0]
就是我说的底层内存地址是一样的。
@kriss;好吧,首先,从学究式的角度来说,从标准的角度来看,将 2D 数组重新解释为 1D 数组是非法的,即使内部布局允许这样做。其次,为了做到这一点,您必须将 int[4][4]
转换为 int *
,而不是像 OP 那样转换为 int **
。【参考方案7】:
#include<stdio.h>
void print(int (*arr)[4], int s1, int s2)
int i, j;
for(i = 0; i<s1; i++)
for(j = 0; j<s2; j++)
printf("%d, ", arr[i][j]);
int main()
int a[4][4] = 6;
print(a,4,4);
这将编译编辑:有人已经发布了这个解决方案我不好
【讨论】:
【参考方案8】:这是一个同时工作的版本,但理论上无效(见下文)C90 和 C++98:
#include <stdio.h>
static void print(int *arr, size_t s1, size_t s2)
size_t i, j;
printf("\n");
for(i = 0; i < s1; i++)
for(j = 0; j < s2; j++)
printf("%d, ", arr[i * s2 + j]);
printf("\n");
int main(void)
int a[4][4] = 0;
print(a[0], 4, 4);
return 0;
使用模板的 C++ 版本(改编自 Notinlist's answer)可能如下所示:
#include <iostream>
#include <cstring>
using namespace std;
template <size_t N, size_t M>
struct IntMatrix
int data[N][M];
IntMatrix() memset(data, 0, sizeof data);
;
template <size_t N, size_t M>
ostream& operator<<(ostream& out, const IntMatrix<N,M>& m)
out << "\n";
for(size_t i = 0; i < N; i++)
for(size_t j = 0; j < M; j++)
out << m.data[i][j] << ", ";
out << "\n";
return out;
int main()
IntMatrix<4,4> a;
cout << a;
return 0;
或者,您可以使用嵌套的 STL 容器 - 即 vector< vector<int> >
- 而不是普通数组。
使用 C99,您可以做到
static void print(size_t s1, size_t s2, int arr[s1][s2])
printf("\n");
for(size_t i = 0; i < s1; i++)
for(size_t j = 0; j < s2; j++)
printf("%d, ", arr[i][j]);
printf("\n");
并将其称为
print(4, 4, a);
正如罗伯特在 cmets 中指出的那样,第一个 sn-p 实际上涉及未定义的行为。但是,假设即使涉及未定义的行为(而不是炸毁您的计算机),指针算术也总是会产生一个指针,由于标准中的其他限制,只有一个可能的结果,即这是一个实例标准留下了一些不必要的未定义内容。
据我所知,替换
print(a[0], 4, 4);
与
union m2f int multi[4][4]; int flat[16]; *foo = (union m2f *)&a;
print(foo->flat, 4, 4);
将使其合法 C.
【讨论】:
@Christoph:很高兴展示 C99 可变参数数组,C 程序员还不太了解它们。 第一个版本调用未定义的行为,因为 arr 指向的对象是 int 的 4 元素数组,并且您在 printf 调用中的边界之外进行索引。尽管您知道内存必须相邻,但从技术上讲它是非法的,这是为了允许可能检查数组边界的实现。有关详细信息,请参阅问题 #16 和open-std.org/jtc1/sc22/wg14/www/docs/dr_017.html 的回复。 @Christoph:下标是根据访问单个数组对象中的元素来定义的。您不能对指向数组的指针使用下标来访问该数组之外的元素。这一切都在 c99 6.5.6p8 中明确说明。我引用的缺陷报告阐明了如何处理指向多维数组中元素的指针。在您的示例中,arr 指向 4 个 int 数组中的第一个 int,尝试使用指针访问该数组之外的元素是未定义的,即使它可能在每个实施。 @Robert: 因为函数参数被定义为int*
函数内部没有问题。唯一的问题是编译器是否会在函数调用时接受 a[0]
与 int*
的类型相同。
@kriss:你不能随便投射任何你想要的东西并得到一个确定的结果,你认为如果你尝试将float
投射到int *
会发生这种情况吗?该标准在第 6.3 节(针对指针的 6.3.2.3)中定义了哪些转换(强制转换是显式转换)是有效的(具有定义的行为)。一个实现可能支持额外的转换,但它不是必须的。你指定的转换是无效的,如果你用一个像样的编译器尝试它,它应该会告诉你很多(例如,gcc 给出 不兼容指针类型的赋值)。【参考方案9】:
除了在 C99 中使用可变长度数组之外,如果在编译时不知道数组的大小,您就不能真正可移植地编写一个函数来接受多维数组,请参阅 @ C-FAQ 的 987654321@。处理这个问题的最好方法是使用动态分配的内存来模拟多维数组。 Question 6.16 很好地解释了这样做的细节。
【讨论】:
看4 running examples I have in my answer,好像和你说的有矛盾,你这里的说法好像不对吧?you can't really portably write a function to accept a multidimensional array if the sizes of the arrays are not known at compile-time
.
@GabrielStaples 在您的“4 个运行示例”中,只有一个(#3)解决了“在编译时数组的大小未知”的多维数组。这个例子采用了两种不同的技术,第一种导致未定义的行为,如我在答案中链接到的 C-FAQ 6.19 中所述(因此不可移植),第二种使用可变长度数组(我特别指出地址),所以我不确定你的答案的任何部分与我写的内容相矛盾。【参考方案10】:
首先要做的是选择正确的类型。如果 C++ 的规则在数组类型方面与 C 相同(我很确定它们是),那么给出声明
int a[4][4];
表达式a
的类型为int [4][4]
,当传递给print
时,它被隐式转换(“衰减”)为int (*)[4]
的指针类型(指向int 的4 元素数组的指针),所以你需要把print
改成
void print(int (*arr)[4], int s1, int s2)
int i, j;
for(i = 0; i<s1; i++)
for(j = 0; j<s2; j++)
printf("%d, ", arr[i][j]);
表达式arr[i]
隐式取消引用arr
,因此您无需搞乱显式取消引用。
缺点是print
只能处理Nx4个int数组;如果要处理其他数组大小,则需要采用不同的方法。
您可以做的一件事是,不用传递数组,而是传递第一个元素的地址,并让print
手动计算偏移量,如下所示:
int main()
int a[4][4] = 0;
print(&a[0][0],4,4); // note how a is being passed
void print(int *arr, int s1, int s2) // note that arr is a simple int *
int i, j;
for (i = 0; i < s1; i++)
for (j = 0; j < s2; j++)
printf("%d, ", arr[i * s2 + j]);
【讨论】:
【参考方案11】:简短回答,您可以按以下方式更改程序
void print(int arr[], int s1, int s2)
...
printf("%d,", *(a+i + s2*j));
...
print((int*)a,4,4);
这需要一个更好的答案来解释 C 和 C++ 中指针和指针算术和数组之间的区别。我现在不会开始。也许是其他人?
我显然对与您代码中的其他海报相同的点并不感到震惊。在打印函数标题中最让我困扰的是,您对不打算将初始指针改回的数组使用了双重间接(实际上它是一个常量,因此无法完成)。 @|V|lad 答案通过将一维或二维设置为固定常数来解决此问题,但随后传递 s1 和 s2 变得无用。
一切都取决于你真正想做什么。 print 是通用的数组打印功能还是某些数组类型的专用功能?
【讨论】:
但是,如果他将*((arr+i)+j)
更改为 arr[i][j]
,这将不起作用。如果他不这样做,如果我用你和我的解决方案初始化a[i][j] = i + j
,我会得到奇怪(和不同)的结果。所以是的,我不知道 OP 想要做什么......
如果他将打印更改为*(*(arr+i)+j)
也将不起作用。但是,如果他更改为*((arr+i)+s1*j)
,它将起作用。基本上,如果我理解这一点,您使用 1d 数组来表示 2d 数组...
@|V|lad:是的,你是对的。是的,用一维数组表示二维或更高维数组是一个古老的常见 C 技巧。这与使用 memcpy 复制结构时的逻辑相同。那是 ??? (对于 C 来说,相当于“pythonic”)因为 C 被设计成一种非常低级的语言,一种可移植的汇编语言。缺点是这样做会丢失一些类型检查。 C99 添加了一些很酷的替代品,例如可变参数数组,并且您还可以使用 C++ 做事的方式。但是我觉得进入高级方面,C++已经失去了一些C的这种低级。
int *
与 int (*)[4]
不兼容(这是 a
衰减到的);在对print
的调用中使用表达式a[0]
或&a[0][0]
而不是强制转换,这两者都与int *
兼容。
@John:这就是重点,int*
删除类型检查。因此 print 函数可以与非 [][4] 数组一起使用。我同意删除类型检查是危险的,但它不是被禁止的(并且在上面与罗伯特的论点之后,我现在想知道在尝试访问 a[1]
内部函数时使用 a[0]
是否不会导致边界限制等问题,即使我知道没有编译器这样做,如果感觉像 Java 的东西)。【参考方案12】:
#include<stdio.h>
void print(int arr[][4], int s1, int s2)
int i, j;
printf("\n");
for(i = 0; i<s1; i++)
for(j = 0; j<s2; j++)
printf("%d, ", *((arr+i)+j));
printf("\n");
int main()
int a[4][4] = 0;
print(a,4,4);
这会起作用,我所说的工作是编译。 @AndreyT 解释了为什么你的版本还不能工作。
这就是你应该如何传递一个二维数组。
为清楚起见,您还可以在函数声明中指定这两种尺寸:
#include<stdio.h>
void print(int arr[4][4], int s1, int s2)
int i, j;
printf("\n");
for(i = 0; i<s1; i++)
for(j = 0; j<s2; j++)
printf("%d, ", *((arr+i)+j));
printf("\n");
int main()
int a[4][4] = 0;
print(a,4,4);
两者都可以。
如果您打算访问行i
的j
th 元素,您还应该将*((arr+i)+j)
更改为a[i][j]
(最好)或*(*(arr+i)+j)
。
【讨论】:
好的。但是如果 s1 和 s2 是函数头中定义的固定常量,那么传递它们有什么意义呢?您可以完全用 4 替换它们。 @kriss:问 OP,我只让他的程序工作:)。一个原因是只打印数组的一个子集。 @|V|lad 我明白你解决了(见我的回答),但我相信他正在尝试编写一个适用于任何数组的通用打印函数,所以我提出了另一个。 如果数组大小在编译时已经固定,那么传递数组大小是没有意义的。这是一个人应该首先回答的问题。如果您需要固定大小的二维数组的函数,请不要传递大小。如果您需要一个适用于任何大小的二维数组的函数,请使用正确的数组传递技术。你的变体让我觉得这两者的奇怪混合。我不明白这样做的意义。 @AnT,关键是您可以使用像a[i][j]
这样的下标表示法,而不是像*(*(arr+i)+j)
这样的计算偏移量。这在 C 中处理矩阵时非常方便,因为它可以更轻松地从使用矩阵下标的高级工具(如 Matlab、Python 等)移植矩阵代码。在处理 VR、机器人、OpenGL 和许多其他使用 3D 变换矩阵的领域时,它经常是必要的。不同的库都可以用来理解二维数组,包括。 C++ 和 Python 测试循环可以优化为使用 ctypes 导入的 C 循环。【参考方案13】:
您可以改用int**
。它更加灵活:
#include <stdio.h>
#include <stdlib.h>
void print(int **a, int numRows, int numCols )
int row, col ;
for( int row = 0; row < numRows; row++ )
for( int col = 0; col < numCols ; col++ )
printf("%5d, ", a[row][col]);
puts("");
int main()
int numRows = 16 ;
int numCols = 5 ;
int **a ;
// a will be a 2d array with numRows rows and numCols cols
// allocate an "array of arrays" of int
a = (int**)malloc( numRows* sizeof(int*) ) ;
// each entry in the array of arrays of int
// isn't allocated yet, so allocate it
for( int row = 0 ; row < numRows ; row++ )
// Allocate an array of int's, at each
// entry in the "array of arrays"
a[row] = (int*)malloc( numCols*sizeof(int) ) ;
int count = 1 ;
for( int row = 0 ; row < numRows ; row++ )
for( int col = 0 ; col < numCols ; col++ )
a[row][col] = count++ ;
print( a, numRows, numCols );
你可能感兴趣的另一件事是像D3DMATRIX这样的结构:
typedef 结构 _D3DMATRIX 联合 结构 浮动_11,_12,_13,_14; 浮动_21,_22,_23,_24; 浮动_31,_32,_33,_34; 浮动_41,_42,_43,_44; ; 浮动米[4][4]; ; D3DMATRIX; D3DMATRIX myMatrix ;关于这个小消息的好处是您可以同时使用myMatrix.m[0][0]
(访问第一个元素),或者也可以使用myMatrix._11
访问同一个元素。 union 是秘密。
【讨论】:
对于阅读此答案的任何人的重要说明和区别:int**
是一种好方法,具体取决于您在做什么以及如何构建多维数组,但它不期望也不适用多维数组,它是一个单个连续的内存块。在这种情况下,使用one of my fist 3 approaches here。我的第 4 种方法是使用 int**
或“其他数组的起始元素的 ptrs 数组”。【参考方案14】:
#include<cstdio>
template <size_t N, size_t M>
struct DataHolder
int data[N][M];
DataHolder()
for(int i=0; i<N; ++i)
for(int j=0; j<M; ++j)
data[i][j] = 0;
;
template <size_t N, size_t M>
void print(const DataHolder<N,M>& dataHolder)
printf("\n");
for(int i = 0; i<N; i++)
for(int j = 0; j<M; j++)
printf("%d, ", dataHolder.data[i][j]);
printf("\n");
int main()
DataHolder<4,4> a;
print(a);
【讨论】:
防止不必要的复制。其次,如果大小在编译时是固定的,它应该是模板参数或定义,而不是“到处都是幻数 4”。 没有模板滥用这种东西吗? ;-) @Notinlist:无论如何都不会复制任何内容。我同意大小是一个定义/常量,但我认为你过于复杂了。 @IVlad。这很容易:-)。应该以完全不同的方式处理整个事情(请参阅问题中的 STL 评论),因此无论如何这个答案仍然很尴尬。 :-) 它绝对比 OP 的原始解决方案要好得多,尽管它仍然可以通过使用 std::cout 而不是 printf 来改进。以上是关于如何将多维数组传递给 C 和 C++ 中的函数的主要内容,如果未能解决你的问题,请参考以下文章