在平时,咱们常常会遇到将整个数组做为函数参数的状况,一维数组的状况,就是用数组名当形参和实参,传递的是数组的首地址。二维数组咱们用的也不少,可是老是有各类问题,今天我总结一下数组
有个很重要的一点,字符串“China”在编译器眼里就是一个地址!操做字符串是经过它在内存中的存储单元的首地址进行的,这是字符串的终极本质函数
若是 "China", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .spa
s = "China" ,赋值了什么,地址。指针
其实真正的意义是 s ="China" = 0x3000;code
首先咱们从指针的角度从新认识下二维数组,int a[3][4],这里的a是一个行指针,指向的是二维数组的行首,a+1指向的是第二行,指的是a[1],它的类型是int(*)[4]。a[i]是一个元素指针,好比a[0]是第1行的一维数组的数组名,指向的是a[0][0],a[0]+1指向的是a[0][1]。它的类型是int*。blog
int (*p)[4],这个时候p和a是等价的,由于都是行指针,类型都是int(*)[4]。内存
(1)二维数组指定行数列数做为形参,实参是二维数组名字符串
void f(int a[3][4]); void f(int a[][4]); void f(int (*a)[4]);
上述三种都是等价的。可是不能省略数组的列数,由于实参传递的是二维数组的起始地址,主要缘由是二维数组在栈内分配的内存是连续的,它的每一行都有相同的元素,这样,a[i][j] 和 *(*(a +i) +j)是同样的,程序是知道array+i的i实际上偏移了i*N个单位,这也致使了在二维数组array[3][3]中,使用下标array[2][1]和array[1][4]是访问的同一个元素,尽管后者的下标对于一个3*3矩阵来讲是非法的,但这并不影响访问。省略了列数,内存不知道如何存放二维数组了。编译器
缺点:该方式的缺点是,必须事先固定数组的行数列数,不太方便。若是个人数组事先不知道多大,这种方法就不适合了。it
(2)二维数组当作一维数组访问,实参的形式有两种,但都是int* 型,一种是*a,一种是a[0],都指向的是一维数组的第一个元素
int a[2][2] = {2,3,4,5}; //4个元素时连续排列的内存段 //void f(int p[][2], int row, int col )//这种方式必须事先知道除第一维之外的维度的大小,不灵活 void f(int *p , int row, int col )//转化为一维数组来访问 { for(int i = 0; i < row; i++) { for(int j =0 ;j < col; j++) { cout<<p[i*col+j]<<" "; } } cout<<endl; }
在被调函数中,寻址的方式能够是程序中的方式,也能够是*(p+i*col+j)的方式。
这里int** p =a;是错误的,由于p是int** 型,而a是int(*)[2]的。
解释下缘由:(1)类型不一样是很明显的(2)从指向的角度来讲,a表示的是数组中a[0]的地址,也就至关于a[0][0]的地址,若是p=a的话,p也表明a[0][0]的地址,*p表明的是a[0][0]自己的值,**p访问的是地址为a[0][0](这里等于2)的内存空间,这是不容许的。
可是有一种状况就能够这么赋值
char *a[2]={"hello","world"}; char** p=a;
这些定义中a的类型不是int(*)[2],而是int* 了(按上述说应该是char*),p=a的话,*p=a[0],因此a表明的数组首地址,即a[0]的地址。*p也就是a[0]中存放的是"hello"的首地址,**p也就表示的是'h'了。这是合法的。
(3)二维数组经过二级指针传递,实参必须为指针
void subfun(int n, char **subargs) { int i; for (i = 0; i < n; i++) { printf("subargs[%d] = %s\n", i, subargs[i]); } } void main() { char *a[3]; char args[][5] = {"abc", "def", "ghi"}; a[0] = args[0]; //equals with a[0] = &args[0][0]; a[1] = args[1]; a[2] = args[2]; subfun(3, a); //若此处为subfun(3, args);则会编译出错 }
void print(float **tab,int rows,int cols) { for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ cout<<tab[i][j]<<" "; } cout<<endl; } } int main() { float ta[2][3]={{1.0,2.0,3.0},{4.0,5.0,6.0}}; float **p=new float *[2];//开辟行空间 for(int i=0;i<3;i++) p[i]=new float[i];//开辟列空间 for(int i=0;i<2;i++){ //赋值 for(int j=0;j<3;j++){ p[i][j]=ta[i][j]; } } cout<<"ta: "<<endl; print(p,2,3);//打印 //p的内存释放方式 for(int i=0;i<3;i++) delete[]p[i]; delete []p; return 0; }
其实上述两个程序是相同的处理方式,都是又额外申请了一段二级指针指向的内存,而后把数组值拷贝到这一块内存中,用完后必须手动释放内存。这样就能够和被调函数的二级指针对应上了。
另外还有一种强制转换的传参方式。
实参传递:
int a[3][4]; f((int **)a,3,4);
这样在被调用数组中对对元素a[i][j]的访问可使用以下形式:
*((int *)a+n*i+j);
注意不能使用a[i][j]来直接访问,由于编译器没法为其定位。
这里会有寻址方式的改变,好比何时下标寻址,何时只能指针寻址,暂时我没有找到权威的解答,先不说了。之后补充。