C语言设计很失败,起码数组的设计很失败。
看看这是什么:
int a[5];
“这是一个int数组,共有5个元素”——回答正确。
int a[5][3];
“这是一个int二维数组,第一维为5,第二维为3,一共有5X3=15个元素”——不难对吧。
int a[5][3];
a是什么类型?
a+0是什么类型?
*a是什么类型?
“a是二维数组,a+0是一维数组,*a是int”——你确定吗?
我们知道数组名就是数组的首地址——这个说法有没有问题?既然是首地址,为什么不是一个指针?但是数组名确实可以当作指针来用,比如“a+0”就是指针运算。
有点晕吗?我们用代码来研究一下这些东西。
作为前提,我们要知道这两个东西:
以下代码为VS2022的C++代码(64位编译):
#include
#include
int main()
{
int a[5][3];
int* p;
printf("sizeof(int) : %2zd : typeid : %s\n", sizeof(int), typeid(int).name());
printf("sizeof(p) : %2zd : typeid : %s\n", sizeof(p), typeid(p).name());
printf("sizeof(a) : %2zd : typeid : %s\n", sizeof(a), typeid(a).name());
printf("sizeof(*a) : %2zd : typeid : %s\n", sizeof(*a), typeid(*a).name());
printf("sizeof(a[0]) : %2zd : typeid : %s\n", sizeof(a[0]), typeid(a[0]).name());
printf("sizeof(*a[0]) : %2zd : typeid : %s\n", sizeof(*a[0]), typeid(*a[0]).name());
printf("sizeof(a+0) : %2zd : typeid : %s\n", sizeof(a + 0), typeid(a + 0).name());
printf("sizeof(*(a+0)) : %2zd : typeid : %s\n", sizeof(*(a + 0)), typeid(*(a + 0)).name());
printf("sizeof(a[0][0]) : %2zd : typeid : %s\n", sizeof(a[0][0]), typeid(a[0][0]).name());
printf("sizeof(a[0]+0) : %2zd : typeid : %s\n", sizeof(a[0] + 0), typeid(a[0] + 0).name());
printf("sizeof(*(a[0]+0)) : %2zd : typeid : %s\n", sizeof(*(a[0] + 0)), typeid(*(a[0] + 0)).name());
}
急性子的先看一下结果:
sizeof(int) : 4 : typeid : int
sizeof(p) : 8 : typeid : int * __ptr64
sizeof(a) : 60 : typeid : int [5][3]
sizeof(*a) : 12 : typeid : int [3]
sizeof(a[0]) : 12 : typeid : int [3]
sizeof(*a[0]) : 4 : typeid : int
sizeof(a+0) : 8 : typeid : int (* __ptr64)[3]
sizeof(*(a+0)) : 12 : typeid : int [3]
sizeof(a[0][0]) : 4 : typeid : int
sizeof(a[0]+0) : 8 : typeid : int * __ptr64
sizeof(*(a[0]+0)) : 4 : typeid : int
看一下结果应该很清楚了。
由于是64位编译,指针类型的长度都是8,int是4,数组则是int大小乘以元素总数。
“*a”是什么?数组名作为对象,代表数组,数组名作为指针,则代表指向数组元素的指针。因为这里a是二维数组,所以a作为指针指向的是一维数组。
数组名什么时候代表数组、什么时候代表指针呢?当数据名做加减的时候就变成了指针。所以“a”和“a+0”是完全不同的东西,一个是“int [5][3]”,一个是“int[3] *”(非正确写法,示意)。
上面的示例代码输出仔细搞懂,关于数组你就差不多不会迷惑了。
直接上代码(VS2022,64位):
#include
#include
void f(int x[6][3])
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
void f2(int x[][3])
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
void f3(int x[5][2])
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
void f4(int x[5][])//无法编译,最后一维不能省略
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
int main()
{
int a[5][3];
f(a);
f2(a);
f3(a);//无法编译,参数类型不匹配
}
有两处不能编译:
“void f4(int x[5][])”,不允许省略数组最后一维的大小。但是为什么可以省略第一维?这其实暗示了数组名和指针的关系:因为当作指针用的,所以第一维的长度其实无关紧要(所以才可以随便数组越界啊!!!!)。
“f3(a);”,错误提示:“无法将参数 1 从“int [5][3]”转换为“int [][2]”。 这个信息明确告诉我们,第一维的长度被忽略了,这也能够解释为什么对“void f(int x[6][3])”的调用没有出错,因为第一维的长度根本就被忽略了。
删除不能编译的代码,运行程序,输出如下:
sizeof(x) : 8 : typeid : int (* __ptr64)[3]
sizeof(x) : 8 : typeid : int (* __ptr64)[3]
哈哈,函数参数其实是个指针!
但是出错信息用的是数组表示法啊,写编译器的人自己把自己都绕进去了?
#include
#include
void f(int* x)
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
void f2(int(*x)[3])
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
void f3(int* x[3])
{
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
}
int main()
{
int a[5][3];
f(a[0] + 0);
f(a + 0 + 0);//无法将参数 1 从“int (*)[3]”转换为“int *”
f((a + 0) + 0);//无法将参数 1 从“int (*)[3]”转换为“int *”
f2(a);
f3(a);//无法将参数 1 从“int [5][3]”转换为“int *[]”
}
这个代码有三处不能编译。
前两处错误信息相同。“二维数组的数组名a做加减就成了指向一维数组的指针,一维数组的指针再做加减不就是指向int的指针吗?”……啊,我再想想……是我想错了啊,原来“指向一维数组的指针”做加减还是“指向一维数组的指针”,指针加减不会改变指针类型,数组名变成指针的时候脱去了一层,再做加减不会继续脱了。
第三处明显不对的,这样写这是为了显示“int(*x)[3]”和“int* x[3]”的区别,前一个是指向数组的指针,后一个是指针数组,区别要细细品味一下。
删除不能编译的代码,执行结果如下:
sizeof(x) : 8 : typeid : int * __ptr64
sizeof(x) : 8 : typeid : int (* __ptr64)[3]
输出的是f和f2的参数类型,因为定义的就是指针,所以显示出来没什么区别。
(这里是结束)