自从知道了vector的增长规则后,觉得还是用数组靠谱。最近发现数组用起来有很多问题,数组和指针的区别,多维数组传参,数组指针和指针数组,函数指针,到了C++后还出现了数组的引用等等。搞得有点头疼。这里开一篇文章梳理一下。
一、数组、指针以及引用
复合类型(compound type),是基于其他类型定义的类型(《C++ Primer》p45)。数组指针引用都属于复合类型。
数组(array)是保存若干个某个类型的数据结构。
type_name id_name[d];
其中type_name为类型,id_name为数组名,d为数组维度。d说明了数组的大小,必须大于0。在某些时候,也可以不指定数组的维度。
指针(pointer)是指向(point to)另外一种类型的复合类型,准确的说,指针的值,就是所指向的对象的地址。
type_name *p_name;
其中type_name为指向的对象的类型,p_name为指针名。
引用(reference)为对象起了另外一个名字,应该相当于是编译器里面一个对象有两个名字(待确定)
type_name &r_name = target;
引用必须初始化,也就是在定义的时候和某个对象绑定。
二、数组、指针以及引用的嵌套定义
一些复杂的定义方法,肉眼看几乎很难看出来是什么意思,我们先从一些简单的例子开始。
int *a[10]; //指针数组,一个存放了10个(int *)元素的数组
int (*b)[10]; //数组指针,b指向一个含有10个int元素的数组
int &c[10] = what; //compile error,没有引用数组
int (&c)[10] = arr; //数组引用,r为arr的另一个别名,arr为含有10个int的数组
对于如何识别更复杂定义的对象,参考螺旋判别法。
三、数组和指针
数组名的值是一个地址,指向的位置是数组开始的位置,但是指向的对象是数组的下一个维度的切片。这里很有意思,虽然说指针本质是内存的地址,但是数组名的本质是有属性的地址。比如下面这个复杂的例子:
int w[3][4][6][8][9];
w[0][0][0][0][0] = 321
w[0][0][2][0][5] = 123;//设置某一个数字,方便后面验证
printf("%p\n", w); // 指向 [0][:][:][:][:]
printf("%p\n", *w); // 指向 [0][0][:][:][:]
printf("%p\n", **w); // 指向 [0][0][0][:][:]
printf("%p\n", ***w); //指向 [0][0][0][0][:]
printf("%p\n", ****w); // 指向 [0][0][0][0][0]
printf("%d\n", *****w); // 从地址中取出值,为整数
printf("%d\n", *(int *)w); // 实际上指向的位置,是第一个值
printf("%d\n", *(*(*(**(w)+2))+5)); // w[0][0][2][0][5]
上面例子的输出:
0x7ffeeb7f0760
0x7ffeeb7f0760
0x7ffeeb7f0760
0x7ffeeb7f0760
0x7ffeeb7f0760
321
321
123
地址中有一个有趣的操作,就是加操作,比如上面的w+2, +5。这里的+,是按照类型的大小来加的。比如,**(w)指向的是第三维度切片数组的开始,那么。(**w)+1代表的就是第三维度上的下一个位置。所以说,地址的+,需要从操作数的类型推断。如果无法推断,证明会有问题。比如下面的例子。
int qq[9][9];
qq[0][0] = 123;
int **pp = (int **)qq;
printf("%d\n", qq[0][0]); // 输出123.
printf("%d\n", pp[0]); // 输出123。因为qq的值为第一个元素的值,pp[0]相当于*(pp),取出第一个值。但这里的123的type实际上是int *,是一个地址。
printf("%d\n", *pp+1); //输出127. 因为*pp为int *,且值为qq[0][0]。运算符+识别*pp为地址类型,所以+1操作变为地址上加4个字节,输出123+4.
printf("%d\n", pp[0][0]); // segmentation fault。由于pp的类型为int **,所以**pp为取两次地址,第二次取地址时,显然会出错。
以前一直认为int **就对应这二维数组,其实两个差别巨大。这样的话,如何通过int **像int [][]一样,定义一个连续的二维数组呢?
int *x1 = (int *)malloc(sizeof(int) * 8 * 8); //开辟int 空间
int **x2 = (int **)malloc(sizeof(int *) * 8); //开辟指针数组,存放每行的起始地址
for(int i=0; i<8*8;i++){
x1[i] = i + 1;
}
for(int i=0; i<8; i++){
x2[i] = x1 + i * 8; //存放每行起始地址
}
for(int i=0; i<8; i++){
printf("%p\n", x2 + i);
printf("%d\n", **(x2 + i));
}
当然,也可以使用c++的语法:
int *z1 = new int[8 * 8];
int **z2 = new int *[8];
for(int i=0; i<8*8; i++){
z1[i] = i + 1;
}
for(int i=0; i<8; i++){
z2[i] = z1 + i * 8;
}
for(int i=0;i<8;i++){
printf("%p\n", z2+i);
printf("%d\n", **(z2+i));
}
也就是说,二维数组(多维)有两种表达方式,一种是直接声明数组,这个要求静态声明,第二种是int **动态声明。
四、传参问题
常见一维数组传参,那么二维数组如何传参?参数传递需要注意一个问题,就是类型一致。实参参数类型需要和形参类型一致,否则无法通过编译。既然设计到二维数组,我们就需要设计二维数组的数据结构,所以,一共有四种可能性:
1)实参为指针int **,形参也为int **
2)实参为int **,形参为int [5][5]
3)实参为数组int [5][5],形参为int **
4)实参为数组int [5][5],形参为int [5][5]
这里好多坑啊。
这段代码太神奇了,回头再找原因。
传参B时候
1)fun里面第三个和第四个输出不一样
2)fun2输出不对
3)fun3第二条对,第三条不对
反正现在记住,实参为int **的时候,形参用int **
实参为int [][],形参用int [][]。
void fun(int a[5][5]){
printf("%p\n", a);
printf("%p\n", &a[0][0]);
printf("%d\n", a[0][0]);
printf("%d\n", ((int **)a)[0][0]);
}
void fun2(int *a[]){
printf("%p\n", a);
printf("%d\n", *(*((int (*)[5]) a +1)+0));
}
void fun3(int **a){
printf("%p\n", a);
printf("%d\n", a[0][0]);
printf("%d\n", *(*((int (*)[5]) a +0)+0));
//printf("wocao2%d\n", *(*((int [][2])a +1)+1));
}
int main(int argc, char *argv[]){
int A[5][5];
for(int i=0; i<5; i++){
for(int j=0; j<5; j++){
A[i][j] = i * j;
}
}
//fun(A);
//fun2(A);
//fun3((int **)A);
int **B = new int *[5];
B[0] = new int[5 * 5];
for(int i=1; i<5; i++){
B[i] = B[i-1] + 5;
}
for(int i=0; i<5; i++){
for(int j=0; j<5; j++){
B[i][j] = i + j;
}
}
printf("%p\n", B);
printf("%d\n", B[0][0]);
fun((int (*)[5])B);
fun2(B);
fun3(B);
return 0;
}
5619

被折叠的 条评论
为什么被折叠?



