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()
签名的情况下使其工作吗?
This:
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**
,将它或其中的任何内容强制转换为这样的类型是不正确的。如果您使用的是固定数组的数组(如果您愿意,可以称之为二维数组),则可以使用模板制作兼容的打印函数:
#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;
}
可以使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()
函数的签名,但允许你使用它而不会崩溃。
Print
中去掉了endl
,这样我就可以说服自己,这将正确地打印出整个数组。
这是改编自"C中的数值配方"中的nrutil.c
(参见这里的示例源代码-这是该书如何创建可变长度的2D数组(实际上他们更进一步,允许您使用基数1偏移量-我觉得这很可怕,但使代码"看起来像"FORTRAN,这是科学计算的原始语言)。
数组和指针是不同的类型。在某些情况下,数组被隐式转换(衰减)为指向其第一个元素的指针,例如在本例中,当数组传递给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]
求值为类型为(double *)
的M[0][0]
的地址。将此值强制转换为(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);
}
数组不是动态的
有可能使它工作而不改变Print()的签名吗?
不,不可能同时满足两个要求。除非你可以将2D数组复制到另一个是动态的
2D双精度数组不是指向行指针的数组。就内存而言,多维数组是扁平的。M[0][0]
的类型是double
,因此&M[0][0]
的类型是*double
。
请参阅关于使用数组的教程。请注意"多维数组"一章
你可能想要做的事情可以这样做:
void Print(double *M, int j, int i, int width) {
cerr << M[i + width * j] << endl;
}
Print(&M[0][0], 2, 3, sx);
注意:正如chris所评论的,这在技术上是不允许的。另一个参考。
执行函数并在技术上是正确的是可能的,但这要求函数特定于数组的宽度。因为您使用c++,所以这不是问题,因为您可以使用模板:
template<size_t X>
void Print(double M[X], int i) {
cerr << M[i] << endl;
}
Print<sx>(M[2], 3);
旁注:我不认为这一行代码值得作为一个函数。在我看来,它降低了可读性,并没有提高可重用性。
正如其他人已经指出的那样,&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数组。
问题是&M[0][0]
不是double **
,它只是指向双精度(double *
)的指针。您需要将函数原型更改为typedef double my_array_type [3][5];
void Print (my_array_type *M, int j, int i)