c++ - 为什么在没有数组完整大小的情况下传递二维数组仍然可以编译和运行?
问题描述
我编写了一个小程序,将 2D 数组传递给 2 个单独的函数,这些函数存储和显示棋盘的图案。虽然该程序运行良好,但我想问一个更技术性的问题,我无法通过搜索回答自己。
我想知道在传递仅指定列变量而不是行的二维数组时,我的程序如何编译和运行。例如void setBoard(char chessboard[][cols]);
这是程序的链接:https ://codecatch.net/post.php?postID=bf4cb8b5
对于那些不想点击链接的人来说,这里是相同的代码:
#include<iostream>
using namespace std;
const int rows = 8;
const int cols = 8;
char chessboard[rows][cols];
void setBoard(char chessboard[][cols]);
void printBoard(char chessboard[][cols]);
void setBoard(char chessboard[][cols]) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(i % 2 == 0 && j % 2 == 0) {
chessboard[i][j] = 'x';
} else {
if(i % 2 != 0 && j % 2 == 1) {
chessboard[i][j] = 'x';
} else {
chessboard[i][j] = '-';
}
}
}
}
return;
}
void printBoard(char chessboard[][cols]) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
cout << chessboard[i][j] << " ";
}
cout << endl;
}
return;
}
int main(int argc, char const *argv[])
{
setBoard(chessboard);
printBoard(chessboard);
return 0;
}
解决方案
直接回答这个问题:在 C 和 C++ 中,将静态数组作为参数传递需要除最外层之外的所有维度。所以,如果你有 N 维数组,你需要指定 N-1 维的大小,除了最左边的:int array[][4][7][2]...[3]
.
现在,到血腥的细节。
所以,假设你有int a[3];
. 是什么类型的a
?它是int [3]
。现在,您想将它传递给函数。你怎么做到这一点?有两种方法:您可以通过指向第一个元素的指针传递数组(在 C 和 C++ 中工作),或者您可以通过引用传递数组(引用是 C++ 的东西)。让我们考虑这些例子:
#include <iostream>
void foo(int* array, std::size_t len) { // same as foo(int array[],...)
for (std::size_t i = 0; i < len; i++)
std::cout << array[i] << ' ';
std::cout << '\n';
}
void bar(int (&array)[3]) {
for (int n : array)
std::cout << n << ' ';
std::cout << '\n';
}
template <std::size_t N>
void baz(int (&array)[N]) {
for (int n : array)
std::cout << n << ' ';
std::cout << '\n';
}
int main () {
int a[3] = {1, 2, 3};
int b[4] = {1, 2, 3, 4};
foo(a, 3); // BAD: works, but have to specify size manually
foo(b, 4); // BAD: works, but have to specify size manually
bar(a); // BAD: works, but bar() only accepts arrays of size 3
bar(b); // DOESN'T COMPILE: bar() only accepts arrays of size 3
baz(a); // OK: size is part of type, baz() accepts any size
baz(b); // OK: size is part of type, baz() accepts any size
}
让我们考虑一下foo()
。
foo()
的签名也可以写成void foo(int array[], ...)
. 这纯粹是语法糖,int array[]
含义与int* array
. 但是请注意,这仅适用于函数签名,在其他任何地方这些语法都不等效。
当您将其称为foo(a, 3)
时,它的签名设置为接受指向 int的指针作为第一个参数:int* array
。但是你知道那a
是 type int [3]
,那么它是如何工作的呢?发生的情况是,指向数组第一个元素的指针是按值传递的。这意味着,它与写作相同foo(&a[0],...)
。我们获取第一个元素的地址,然后将其复制到int* array
. 您可能会注意到,拥有指向第一个数组的指针并不能告诉我们有关数组大小的任何信息,我们在从数组类型转换int [3]
为int *
. 这就是为什么我们必须提供指定长度的第二个参数a
. 我们称这种隐式转换为“数组到指针衰减”。衰减特别是因为我们被迫丢失了重要信息——我们在类型中就在那里,但现在我们必须有另一个参数来描述数组有多少元素。有点愚蠢和不方便,不是吗?
现在考虑bar()
。
在bar()
我们通过引用传递数组类型。这是 C++ 对 C 的改进。我不会解释什么是引用,但一般来说,您可以将它们视为允许您按照定义的方式获取对象的东西,而无需使用任何指针转换。在这种情况下,类型array
仍然存在int [3]
,所以我们传入了一个数组并且没有丢失任何类型信息。伟大的!这意味着,我们可以使用惯用的 C++ 语法来进一步改进我们的代码。我已经用 for-each 循环替换了在for
中找到的正常循环,foo()
我们只需要提供一个变量来存储元素 ( n
) 和数组 ( array
)。请注意,这可能只是因为array
保留了类型信息!试图做到这一点foo()
会导致编译错误。
但是,这仍然存在问题。bar()
必须将数组大小作为其签名的一部分!这意味着如果a
大小不同,比如 4 个元素,尝试使用bar()
会导致编译错误,因为int [3]
和int [4]
是不兼容的类型。
考虑baz()
,它解决了上述问题。
只需一点点模板就baz()
可以在任何大小的数组上使用,而用法与bar()
.
现在,让我们把它带到多个维度:
#include <iostream>
void foo2d(int (*array)[3], std::size_t rows, std::size_t cols) {
for (std::size_t i = 0; i < rows; i++)
for (std::size_t j = 0; j < cols; j++)
std::cout << array[i][j] << ' ';
std::cout << '\n';
}
void bar2d(int (&array)[2][3]) {
for (std::size_t i = 0; i < 2; i++)
for (std::size_t j = 0; j < 3; j++)
std::cout << array[i][j] << ' ';
std::cout << '\n';
}
template <std::size_t N, std::size_t M>
void baz2d(int (&array)[N][M]) {
for (std::size_t i = 0; i < N; i++)
for (std::size_t j = 0; j < M; j++)
std::cout << array[i][j] << ' ';
std::cout << '\n';
}
int main () {
int c[2][3] = { {1, 2, 3}, {4, 5, 6} };
foo2d(c, 2, 3);
bar2d(c);
baz2d(c);
}
再一次,只baz2d()
不需要硬编码的大小信息。
再举一个例子 of foo3d()
,只是为了说明我所说的仅不需要指定最外层维度的意思:
void foo3d(int (*array)[2][1], std::size_t rows, std::size_t cols, std::size_t K) {
for (std::size_t i = 0; i < rows; i++)
for (std::size_t j = 0; j < cols; j++)
for (std::size_t k = 0; k < K; k++)
std::cout << array[i][j][k] << ' ';
std::cout << '\n';
}
int main () {
int d[3][2][1] = { {{1}, {2}}, {{3}, {4}}, {{5}, {6}} };
foo3d(d, 3, 2, 1);
}
注意它的调用方式与签名中的声明方式。那么,为什么不需要声明最外层尺寸呢?因为只有第一个指针会因为传递给函数而衰减。d[0][0][0]
存储类型元素int
,d[0][0]
存储类型元素int [1]
,存储类型d[0]
元素int [2][1]
。它们本身都是数组!好吧d[0][0][0]
,显然,除了 。那么, in 的类型是array
什么foo3d()
?它是int (*)[2][1]
:指向大小为 2 的数组的指针,其中每个元素都是大小为 1 的数组。
推荐阅读
- mysql - Spring Boot API 使用 AWS RDS MySQL 需要很长时间(使用具有一对多关系的实体)
- python - mysql.connector.errors.ProgrammingError: 1064 (42000): 尝试创建表时 SQL 语法有错误
- javascript - 显示来自 fetch api 的数据
- r - R Shiny/Leaflet 跳转到过滤标记
- reactjs - 使用来自 python lambda 的预签名 url 将图像上传到 s3
- swiftui - SwiftUI - 删除具有 PageViewStyle 的 TabView 之间的空间
- ipfs - 如何正确配置 IPFS 的 DNS 链接?
- laravel - 如何将数组值一一传入邮件类
- kotlin - 如何在 kotlin 的 onCreate 函数中预定义 Volley.newRequestQueue 类型和后期初始化
- html - “无法绑定到‘ngForOf’,因为它不是‘ng-container’的已知属性