2D C风格数组的常见问题。该程序编译,但在调用Print()
函数时崩溃:
#include <iostream>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
{ 0.1, 1.1, 2.1, 3.1, 4.1 },
{ 0.2, 1.2, 2.2, 3.2, 4.2 } };
void Print(double **M, int j, int i)
{
cerr << M[j][i] << endl;
}
int main()
{
cerr << M[2][3] << endl; // Prints 3.2
Print((double**)&M[0][0], 2, 3); // Crash
}
我的代码有什么问题?
是否可以在不更改Print()
签名的情况下使其正常工作?
数组不是动态的。
答案 0 :(得分:3)
可以使Print
函数有效,但您需要更改分配数组的方式。具体来说,您需要一个指向每行第一个元素的指针向量,并将其作为参数传递。
您可以这样做:
#include <iostream>
#include <cstdlib>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
{ 0.1, 1.1, 2.1, 3.1, 4.1 },
{ 0.2, 1.2, 2.2, 3.2, 4.2 } };
double **twoD(int nr, int nc) {
// function that allocates a 2D matrix and returns the result
// as a vector of pointers to the first element of each row
double **p;
int ii;
p = (double**)malloc(nr * sizeof(double*));
p[0] = (double*)malloc(nr * nc * sizeof(double));
for(ii=1; ii<nr; ii++) {
p[ii] = p[0] + ii * nc;
}
return p;
}
void free2d(double **p) {
free(p[0]);
free(p);
}
void Print(double **M, int j, int i)
{
cerr << M[j][i] << " ";
}
int main()
{
double **P;
P = twoD(sy, sx);
memcpy(P[0], M, sx * sy * sizeof(double));
cerr << M[2][3] << endl; // Prints 3.2
int ii,jj;
for(ii=0; ii<sy; ii++) {
for(jj=0; jj<sx; jj++) {
Print(P, ii, jj);
}
cerr << endl;
}
free2d(P);
}
正如您所看到的 - 它不会更改Print()
功能的签名,但允许您在不崩溃的情况下使用它。
我并不是说我推荐这是最好的方法 - 但它会从字面上回答这个问题。请注意,我从endl
中取出Print
只是为了让我自己相信这会正确地打印整个数组。
改编自C {#34;&#34;数字食谱中的nrutil.c
。 (参见例如源代码here - 这就是本书创建可变长度2D数组的方式(实际上它们更进了一步,允许你使用基数1偏移 - 我发现它很可怕,但是代码和#34;看起来像#FOR; FORTRAN这是科学计算的原始语言。)
答案 1 :(得分:3)
此:
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
{ 0.1, 1.1, 2.1, 3.1, 4.1 },
{ 0.2, 1.2, 2.2, 3.2, 4.2 } };
与double**
无关,并且将其或其中任何内容转换为此类型是不正确的。如果您正在使用固定的数组数组(如果需要,可以将其称为2D数组),可以使用模板制作兼容的打印功能:
#include <iostream>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
{ 0.1, 1.1, 2.1, 3.1, 4.1 },
{ 0.2, 1.2, 2.2, 3.2, 4.2 } };
template<int M>
void Print( double(*ar)[M] , int j, int i )
{
std::cout << ar[j][i] << '\n';
}
int main()
{
Print(M, 2, 3);
return 0;
}
答案 2 :(得分:2)
数组和指针是不同的类型。在某些情况下,数组被隐式转换(衰减)为指向其第一个元素的指针,就像在这种情况下将数组传递给print function
一样。
以下陈述
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
{ 0.1, 1.1, 2.1, 3.1, 4.1 },
{ 0.2, 1.2, 2.2, 3.2, 4.2 } };
将M
定义为sy
的数组,即3
元素,其中每个元素的类型为double[5]
,即5
的数组双打。因此,在print
函数调用
Print((double**)&M[0][0], 2, 3);
&M[0][0]
评估M[0][0]
类型为(double *)
的地址。 将此值投射到(double **)
类型是错误的,因为此地址的值为double
,而不是指向double
的指针。
现在,在print
函数中,M[i][j]
被评估为*(*(M + i) + j)
。这里,变量M
(print
函数的本地)是M[0][0]
的地址。因此,*(M + i)
评估为M[0][i]
double
。接下来,将j
添加到此值并假设此值为有效的内存地址,将获取该地址的值。这是完全错误的,并调用未定义的行为。
您需要更改print
功能的签名。
#include <iostream>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
{ 0.1, 1.1, 2.1, 3.1, 4.1 },
{ 0.2, 1.2, 2.2, 3.2, 4.2 } };
void Print(double *M, int j, int i)
{
cerr << M[j*sx + i] << endl;
}
int main()
{
cerr << M[2][3] << endl; // Prints 3.2
Print(&M[0][0], 2, 3);
}
答案 3 :(得分:1)
数组不是动态的
是否可以在不更改Print()签名的情况下使其正常工作?
不,不可能达到这两个要求。除非您可以将该2D阵列复制到另一个 动态的
二维2D数组不是指向行的指针数组。就内存而言,多维数组是平的。 M[0][0]
的类型为double
,因此&M[0][0]
的类型为*double
。
有关使用数组的信息,请参阅此tutorial。请注意“多维数组”一章。
你可能想做的事情可以这样做:
void Print(double *M, int j, int i, int width) {
cerr << M[i + width * j] << endl;
}
Print(&M[0][0], 2, 3, sx);
注意:正如克里斯评论的那样,这在技术上是不允许的。 Another reference
可以执行您的功能并在技术上正确,但这需要该功能特定于数组的宽度。由于您使用C ++,因此您可以使用模板:
template<size_t X>
void Print(double M[X], int i) {
cerr << M[i] << endl;
}
Print<sx>(M[2], 3);
旁注:我不相信这种单线是值得的功能。在我看来,它降低了可读性,并没有提高可重用性。
答案 4 :(得分:1)
正如其他人已经指出&M[0][0]
是double *
,因为您只是检索元素M[0][0]
的地址。如果你把它作为double **
投射,然后试着大步前进,假装它真的是double **
,你基本上是在寻找麻烦。
我猜你想要做的事情可以通过明确地进行两层分配来安全地实现,例如:
double ** M = new double*[sy];
for (int iy=0; iy<sy; iy++) {
M[iy] = new double[sx];
}
然后(初始化后)Print(M, 2, 3);
将正常工作。我担心你不得不放弃使用花括号表示法的数组式初始化,如果你这样做的话,并寻找一些解决方法。 (注意:上面的建议对于大型数组来说远非有效,因为2D阵列的各种1D块不能保证连续分配。通常,如果尺寸和性能是问题,您应该考虑将2D阵列表示为1D阵列。 )
答案 5 :(得分:0)
问题是&M[0][0]
不是double **
,它只是指向双精度(double *
)的指针。
您需要将函数原型更改为
typedef double my_array_type [3][5];
void Print (my_array_type *M, int j, int i)