前言(数组名的总结):
因为本文章大部分与数组名有关,我们首先温习一下数组名的知识,数组名通常都表示数组的首元素地址,但是有两个例外:
- &数组名,这里的数组名就表示整个数组的地址
- sizeof(数组名),这里的数组名也表示整个数组的地址
除上述两种特殊情况外,其余我们见到的数组名都表示数组的首元素地址。
一、int 型数组和 sizeof 的组合
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
此时的数组名就是表示整个数组的地址,所以计算结果就是4*4=16
printf("%d\n", sizeof(a + 0));
此时的数组名不是两种特殊情况,因此就表示数组的首元素地址,+0无效果,所以最终还是数组的首元素地址,因此答案4/8个字节(指针的大小)
printf("%d\n", sizeof(*a));
此时数组名也表示数组的首元素地址,然后解引用*就是数组的第一个元素,也就是4个字节
printf("%d\n", sizeof(a + 1));
这个数组名也表示数组的首元素地址,然后+1,因为a是int *类型,+1就跳过一个int类型的大小,所以a+1就表示数组的第二个元素的地址,答案是4/8(指针大小)
printf("%d\n", sizeof(&a));
此时的数组名确实表示整个数组的地址,但他本质上还是一个指针,不能在门缝里看指针,把指针看扁了,无论什么类型的指针,他的大小都是4/8个字节!
printf("%d\n", sizeof(*&a));
* 和 & 是两个相逆的运算,相当于求导和求微分的意思,所以这两个在一起,就可以抵消。
因此*&a==a
而我们上面已经讲过,答案就是4*4
printf("%d\n", &a + 1);
&a+1 相较于 &a 跳过了整个数组,但本质上还是地址,所以结果是4/8
sizeof计算原理
sizeof只需要知道数据类型是什么,不需要访问内存去计算,甚至sizeof(int),这样的表达式也是可以的
二、char类型和strlen()组合
首先我们明确,strlen计算字符串的长度,计算的是\0之前出现的字符的个数。
char arr[] = { 'a','b','c','d','e','f' };
注意这一种初始化方式,内存中存储的是
a b c d e f
后面内容未知,前面内容也未知。
strlen(arr);
因为arr里面没有存放\0,所以strlen会一直向后寻找,直到找到为止,而后面存储的内容我们是未知的,因此最后结果就是随机值。
strlen(arr + 0);
表示的仍然是首元素的地址,跟第一个一样,因此结果也是随机值
strlen(*arr);
这样写是非法的!
因为strlen()需要的参数是字符指针类型,而我们将*arr也就是数组的第一个元素 'a' 作为参数传递进去,转换为97,而strlen会将97作为地址访问,而97并不是我们的地址,所以就造成了非法访问内存了。
strlen(arr[1]);
这一题跟上一题一样,都是非法访问内存,是错误的。
strlen(&arr);
我们首先需要明确&arr,和数组的首元素的地址值是一样的。
但是类型不一样,前者是 char (*) [ ],后者是 char* 。
但是我们将地址传到strlen之后,函数会将地址自动转换为想要的const char*
因此,还是会从数组的首元素开始访问,答案也就是随机值
strlen(&arr + 1);
这题表示跳过了整个数组,答案也就是随机值,并且跟上一题的结果有联系,相差6,因为跳过整个数组,数组的6个字符元素。
三、char*类型和sizeof()组合(有 '\0' 版本)
char* p[] = { "abcdef" };//字符串已经含有'\0'
把字符串首元素a的地址赋给arr。
printf("%d\n", sizeof(p));
注意这里的p不是数组名,他本质上就是一个指针变量,所以结果就是4/8个字节
printf("%d\n", sizeof(p+1));
首先表示字符串第二个元素的地址,因为 p是char* 类型,所以p+1就跳过一个字符的大小,所以就是b的地址。是地址就是4/8个字节
printf("%d\n", sizeof(*p));
因为p存储的是字符串首元素的地址,所以解引用就是字符串的首元素,因此结果就是一个字符的大小——1
printf("%d\n", &p + 1);
答案是显而易见的,仍然是地址(4/8),但这题的关键在于&p+1到底指向哪里?
我们首先明确&p的类型是char **,p的类型是char*。
+1就跳过char*的内容,而char*的内容指向的是p,所以+1就跳过p,最终指向的是p后面!
注意字符串首元素的地址和p的地址是两码事,是两块不同的内存空间。
四、char*类型和strlen()组合(有 '\0' 版本)
char* p[] = { "abcdef" };//字符串已经含有'\0'
printf("%d\n", strlen(p));
到\0前面停止,就是6个字符
printf("%d\n", strlen(p + 1));
p是a的地址,+1就是b的地址,所以结果就是6-1=5
printf("%d\n", strlen(*p));
此时输入是非法的,因为*p就是把a传进去,而strlen需要一个字符指针。
printf("%d\n", strlen(&p));
p的地址和字符串存的地址没有任何关系,也是随机值
printf("%d\n", strlen(&p + 1));
也是随机值,但与&p有没有关系呢?
其实并没有关系,因为p是一个地址,4/8个字节,我们并不能确定这4~8个字节里面是否含有\0,如果有,那这两者无任何关系
五、二维数组和sizeof()组合
int arr[3][4] = { 0 };
二维数组在内存中的存储方式就是这样的。
printf("%d\n", sizeof(arr));
注意此时的数组名直接放在了sizeof里面,所以就是表示整个数组3*4*4=48
printf("%d\n", sizeof(arr[0]));
TIP:
在二维数组中,我们认为第一行就是第一个元素
arr[0]:二维数组第一行这个一维数组的数组名
arr[1]:二维数组第二行这个一维数组的数组名
arr[2]:二维数组第三行这个一维数组的数组名
以此类推……
所以此时的arr[0]就是二维数组第一行的数组名,既然又是数组名,那么就仍表示整个数组
4*4=16
printf("%d\n", sizeof(arr[0] + 1));
首先arr[0]并没有单独放在sizeof()内部,也没有&arr,所以此时的arr[0]就表示数组第一行的首元素地址,也就是arr[0][0]的地址,再+1就表示arr[0][1]的地址,结果是4/8个字节
printf("%d\n", sizeof(arr + 1));
arr是表示数组的首元素地址,但这是二维数组,所以首元素也就是二维数组的第一行的地址,
再+1就表示第二行整体的地址,是地址就是4/8个字节、
printf("%d\n", sizeof(*(arr + 1)));
第一种思路:
*(arr+1)==arr[1],这两者互相转换,所以我们把arr[1]单独放在sizeof()内部就表示整个第二行的数组,所以就是4*4
第二种思路:
上一题我们讲过arr+1就是数组的第二行元素的地址,类型是int (*) []是数组指针,而我们对数组指针解引用,访问的就是整个数组。4*4
printf("%d\n", sizeof(&arr[0] + 1));
&arr[0]表示的二维数组第一行的地址,再+1就表示第二行的地址。4/8
printf("%d\n", sizeof(*(&arr[0] + 1)));
我们由上一题已经直到&arr[0]+1就表示数组的第二行地址,再解引用就是整个第二行元素的大小4*4
printf("%d\n", sizeof(*arr));
因为数组名没有单独放在sizeof(),也没有&,所以就表示数组的首元素地址,在二维数组中也就是第一行的地址,然后解引用,计算整个第一行,4*4