据《硅谷》杂志2012年第22期刊文称,二维数组使用一般有两种情况,一种是描述一个二维的事物。比如用1表示墙,用0表示通路,我们可以用二维数组来描述一个迷宫地图;用1表示有通路,0表示没有通路,我们可以用二维数组来描述几个城市之间的交通情况。还有一种是描述多个具有多项属性的事物。比如有多个学生,每个学生有语文、数学和英语三门成绩,我们就可以用二维数组来描述。对二维数组使用过程中的几个问题进行探究。
关键词:C++;二维数组;动态分配;参数;指针
C++程序中的new[]和delete[]作为动态内存分配的重要手段在程序设计中有着广泛应用,尤其应用在程序员自定义的类与结构的数组声明上。但由于C++程序语言中并没有给出显示的数组访问限制[1],极易造成"数组越界"的错误。这种错误会引起对非程序数据段的修改,而使得程序崩溃。
1C++中二维数组的动态分配
在C++中动态分配二维数组可以先申请一维的指针数组,然后该数组中的每个指针再申请数组,这样就相当于二维数组了,但是这种方法会导致每行可能不相邻,从而访问效率比较低。那么什么是真正的二维数组呢?C语言中的二维数组在内存组织形式是按行存储的连续的内存区域。所以,必须保证数组元素是按行存储的,而且也是最重要的是内存要连续。
所以,笔者设计如下的一个方法:
假定二维数组的元素变量类型是MyType;可以是C语言接受的除void之外的任何类型,因为编译器不晓得void类型的大小;例如int,float,double等等类型;
introw=2;/////暂假定行数是2,这个可以在运行时刻决定;
intcolumn=3;/////暂假定列数是2,这个可以在运行时刻决定;
void**ptdhead=NULL;//////////在后面说明为什么要用void**类型
void**ptdBody=NULL;//////////在后面说明为什么要用void**类型
ptdhead=(void**)malloc(sizeof(void*)*row+sizeof(MyType)*row*column);
if(!ptdhead)
returnFALSE;
ptdBody=ptdhead+row;
for(intncount=0;ncount<row;ncount++)
ptdhead[ncount]=ptdBody+ncount*column*sizeof(MyType)/sizeof(void*);
MyType**ptdheadRealse;
ptdheadRealse=(MyType**)ptdhead;///////////////////强制转换为自己程序需要的二维数组元素类型的指针
ptdhead=NULL;
for(inti=0;i<row;i++)
{
for(intj=0;j<column;j++)
{ptdheadRealse[i][j]=i+j;////////进行简单的初始化;}
}
这样的一种方法动态分配的二维数组,内存是连续的,是真正意义的C语言二维数组,满足所有二维数组访问的方法,而且内存利用效率高,程序性能好。
2C++二维数组的传递
用二维数组作为参数传递(用二维数组处理矩阵),但是希望接受传递二维数组参数的函数可以处理任意维度的数组(希望矩阵的行数和列数都是不固定的)。但一般传递二维数组的基本规则好像是这样的:可以用二维数组名作为实参或者形参,在被调用函数中对形参数组定义时可以可以指定所有维数的大小,也可以省略第一维的大小说明。如:
voidFunc(intarray[3][10]);voidFunc(intarray[][10]);
二者都是合法而且等价,但是不能把第二维或者更高维的大小省略,如下面的定义是不合法的:voidFunc(intarray[][]);将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维的,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。但是我们在编写程序的时候却需要用到各个维数都不固定的二维数组作为参数,这就难办了,编译器不能识别阿,怎么办呢?不要着急,编译器虽然不能识别,但是我们完全可以不把它当作一个二维数组,而是把它当作一个普通的指针,再另外加上两个参数指明各个维数,然后我们为二维数组手工寻址,这样就达到了将二维数组作为函数的参数传递的目的,根据这个思想,我们可以把维数固定的参数变为维数随即的参数。
3C++中二维数组形参的传递
voidFunc(intarray[3][10]);voidFunc(intarray[][10]);可以省略第一维的大小,错误的是voidFunc(intarray[][].这样的用法只能在初始化时可以用);这样写也是错误:voidFunc(constintm,constintn,intarray[m][n]);或voidFunc(intm,intn,intarray[m][n]);大家都知道数组的索引必须是个常量表达式,voidFunc(constintm,constintn,intarray[m][n]);如果constintm没有初始化,那么系统将m或n自动初始化为0,所以这样些是不对的,如果我们采用这样voidFunc(int**array,intm,intn)的形式,那么在实际的函数调用是,我们就要进行强制转换才可以用,我们可以这样调用voidFunc((int**)array,intm,intn);在函数调用时,要把数组形式写成指针形式如*((int*)array+n*i+j);直接写intarray[i][j]会导致错误,编译可以通过,在VC编译器中执行会出现异常,DEV编译器会出现一个随机值,原因就在于如果写成intarray[i][j],编译器无法正确的寻址,当然各种编译器对它的处理结果是不一样的。如果我们的形参是数组,那么我们在函数体中可以用指针也可以用数组形式,但是如果我们形参数中用的是指针,最好也用指针,有时用数组形式会出错,二维数组就是这样。
4二维数组中的指针问题
1)用指针表示二维数组元素。要用指针处理二维数组,首先要解决从存储的角度对二维数组的认识问题。我们知道,一个二维数组在计算机中存储时,是按照先行后列的顺序依次存储的,当把每一行看作一个整体,即视为一个大的数组元素时,这个存储的二维数组也就变成了一个一维数组了。而每个大数组元素对应二维数组的一行,我们就称之为行数组元素,显然每个行数组元素都是一个一维数组。
2)用二维数组名作地址表示数组元素。我们还可以得到二维数组元素的一种表示方法:对于二维数组a,其a[0]数组由a指向,a[1]数组则由a+1指向,a[2]数组由a+2指向,以此类推。因此,*a与a[0]等价、*(a+1)与a[1]等价、*(a+2)与a[2]等价,┅,即对于a[i]数组,由*(a+i)指向。由此,对于数组元素a[i][j],用数组名a的表示形式为:*(*(a+i)+j)。指向该元素的指针为:*(a+i)+j。数组名虽然是数组的地址,但它和指向数组的指针变量不完全相同。指针变量的值可以改变,即它可以随时指向不同的数组或同类型变量,而数组名自它定义时起就确定下来,不能通过赋值的方式使该数组名指向另外一个数组。
3)行数组指针。在上面的说明中我们已经知道,二维数组名是指向行的,它不能对如下说明的指针变量p直接赋值:inta[3][4]={{10,11,12,13},{20,21,22,23},{30,31,32,33}},*p;其原因就是p与a的对象性质不同,或者说二者不是同一级指针。C语言可以通过定义行数组指针的方法,使得一个指针变量与二维数组名具有相同的性质。行数组指针的定义方法如下:数据类型(*指针变量名)[二维数组列数];例如,对上述a数组,行数组指针定义如下:int(*p)[4];它表示,数组*p有4个int型元素,分别为(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3],亦即p指向的是有4个int型元素的一维数组,即p为行指针此时,可用如下方式对指针p赋值:p=a;
下面说明一下,我碰到的问题,我们定义了一下如下的函数:voidfunction(double**array,intwidth,intheight)。
然后我们定义了一个二维数组doublep[3][3]={{1,2,3},{4,5,6},{7,8,9}};
当我们调用function时,即function(p,3,3),编译器会报错:
errorC2664:'function':cannotconvertparameter1from'double[3][3]'to'double**'
参数传递实际上是一个赋值的过程,为了便于说明我们底下都以赋值的方式加以说明。
我们知道p是数组首地址,地址指向的是第一个行数组,在某种程度上来说可以把二维数组名理解为指针的指针,但是这两者是有区别的。
double*p[3]和double(*p)[3]
double*p[3]是一个指针数组,它是一个数组,里面存放了3个指针;double(*p)[3]它是一个数组指针,它是一个指针,这个指针指向的是一个数组,它和二维数组有相同的性质,具体说明可以看下分割线中红色的字体所示的部分,由此我们可以知道如下的赋值是可行的:doublep[3][3]={{1,2,3},{4,5,6},{7,8,9}};
double(*pp)[3]=p;
这里实际上的话也是应该执行了一次上面所说的自动转化,p转化了一个指向行数组的指针,然后赋值给了数组指针变量pp;另外,我们发现底下的赋值也是可行的:
double*p[3];
double**pp=p;
这里实际上也是执行了一次上面所说的转化,p转化了一个指向指针变量的指针,然后赋值给了pp;这里看下,上面两次转化后唯一的的区别于一个是指向数组的指针,一个是指向指针的指针,而C++不允许接下来的再次转化,说明C++只支持数组到指针的一次转化,而二次转化没有支持。 |
|