本文带大家详细了解 sizeof 与 strlen 在指针和数组方面的应用,让大家不再傻傻分不清楚,提高大家对其的理解。(注:以下代码均在x86 (32位) 环境下运行)
数组名的意义:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
sizeof 与 strlen 的作用:
sizeof 是计算对象或者类型创建的对象所占内存空间的大小,单位是字节
sizeof 是操作符,不是函数, sizeof 不关注 \0
strlen 求字符串长度的,从给定的地址出发计算字符串中 \0 之前出现的字符的个数,统计到 \0 为止,如果没有看到 \0,会继续往后找
strlen 是库函数,使用需要包含头文件
指针加一跳多长?
char *p; //一级指针 *p里的*说明p是指针,指向的类型是char型
char* *pp=&p; //二级指针 *pp里的*说明pp是指针,指向的类型是char*型
p+1跳过的是一个char型对象
pp+1跳过的是一个char*型对象
指针加一跳多长,取决于指针指向对象的类型
下面一段代码大家可以先试着做一做,想一想
#include
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0] + 1));
return 0;
}
int a[] = {1,2,3,4}; //数组a有4个元素,每个元素占4个字节,
printf("%d\n",sizeof(a)); // 16
//a作为数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
printf("%d\n",sizeof(a+0)); // 4/8
//a并非单独放在sizeof内部,也没有&,所以数组名a就是数组首元素的地址
//a+0还是数组首元素的地址,是地址其大小就是 4/8 个字节(x86是4字节,x64是8字节)
printf("%d\n",sizeof(*a)); // 4
//a是首元素的地址,*a就是首元素,sizeof(*a)算的就是首元素的大小
printf("%d\n",sizeof(a+1)); // 4/8
//a是首元素的地址,a+1是第二个元素的地址,sizeof(a+1)计算的是指针也就是地址大小
printf("%d\n",sizeof(a[1])); // 4
//a[1]就是数组的第二个元素,sizeof(a[1])的大小 - 4个字节
printf("%d\n",sizeof(&a)); // 4/8
//&a取出的整个数组的地址,数组的地址,也是地址呀,sizeof(&a)就是 4/8 个字节
printf("%d\n",sizeof(*&a)); // 16
//&a是数组的地址,是数组指针类型,*&a是数组指针解引用,访问一个数组的大小
//sizeof(*&a) ==> sizeof(a) =16
printf("%d\n",sizeof(&a+1)); // 4/8
//&a数组的地址,&a+1跳过整个数组,&a+1还是地址,是 4/8 个字节
printf("%d\n",sizeof(&a[0])); // 4/8
//a[0]是数组的第一个元素,&a[0]是第一个元素的地址,是 4/8 个字节
printf("%d\n",sizeof(&a[0]+1)); // 4/8
//&a[0]是第一个元素的地址,&a[0]+1就是第二个元素的地址,是 4/8 个字节
有了以上讲解相信大家对下面代码应该会得心应手
#include
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
return 0;
}
char arr[] = { 'a','b','c','d','e','f' }; //每个 char 型字符占一个字节,整个数组占6个字节
printf("%d\n", sizeof(arr)); // 6
//arr是数组名,并且是单独放在sizeof内部,计算的是数组总大小,单位是字节 - 6
printf("%d\n", sizeof(arr + 0)); // 4/8
//arr是数组名,并非单独放在sizeof内部,arr表示首元素的地址,arr+0还是首元素的地址
printf("%d\n", sizeof(*arr)); // 1
//arr是首元素的地址,*arr就是首元素,sizeof计算的是首元素的大小,是1字节
printf("%d\n", sizeof(arr[1])); // 1
printf("%d\n", sizeof(&arr)); // 4/8
//&arr-取出的是数组的地址,sizeof(&arr)计算的是数组的地址的大小,是地址就是4/8字节
printf("%d\n", sizeof(&arr + 1)); // 4/8
//&arr是数组的地址,&arr+1跳过整个数组,指向'f'的后边,&arr+1的本质还是地址
printf("%d\n", sizeof(&arr[0] + 1)); // 4/8
//&arr[0]是‘a’的地址,&arr[0]+1是'b'的地址,是地址就是4/8字节
我们再来看看 strlen 函数的使用:
#include
#include
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
return 0;
}
输出:
为什么会出现这种情况呢?strlen(*arr) 有什么问题呢?
arr是数组首元素的地址,*arr 是首元素 - ‘a’ - 97
strlen就把 ‘a’ 的ASCII码值 97 当成了地址
导致非法访问内存,也就是 err(注:像这种地址都是不能访问的)
它下面的一段代码是 arr[1] ,也就是 ‘b’ - 98,也是不能访问的。
我们将它俩屏蔽再来看看结果:
不知道大家有没有发现好像两次输出的结果有点小差异,第一次输出是:23 23,这与19 19不同,为什么呢?
我们来分析一下:
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr)); //随机值
//arr是数组名,但是没有放在sizeof内部,也没&,arr就是首元素的地址
//strlen得到arr后,从arr数组首元素的地方开始计算字符串的长度,直到直到\0,但是arr数组中没有\0,arr内存的后边是否有\0,在什么位置是不确定的,所以\0之前出现了多少个字符是随机的。
printf("%d\n", strlen(arr + 0)); //随机值
//arr是数组首元素的地址,arr+0还是首元素的地址
//printf("%d\n", strlen(*arr));
//printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr)); //随机值
//&arr是数组的地址,数组的地址也是指向数组起始位置,和第一个案例一样
printf("%d\n", strlen(&arr + 1)); //随机值-6
//跳过整个数组,整个数组大小为6个字节,所以在随机值的基础上-6
printf("%d\n", strlen(&arr[0] + 1)); //随机值-1
//&arr[0] + 1是第二个元素地址,所以是随机值-1
废话不多说上代码:
#include
int main()
{
char arr[] = "abcdef"; //看清楚是 sizeof
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
return 0;
}
输出:
相信大家应该挺疑惑的吧,为什么会是 7 不是 6 呢?
首先我们得知道,"abcdef"里面存了啥?
其实在这个字符串最后面有一个 \0 没有写出来
写成大括号的形式就是{‘a’,‘b’,‘c’,‘d’,‘e’,‘f’,‘\0’}
我们再看 sizeof 的用法:strlen 求字符串长度的,计算的是字符串中 \0 之前出现的字符的个数,统计到 \0 为止,如果没有看到 \0,会继续往后找
如此想必大家都明白了吧。
分析:
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); // 7
printf("%d\n", sizeof(arr + 0)); // 4/8
//首元素地址
printf("%d\n", sizeof(*arr)); // 1
//arr[0]的大小
printf("%d\n", sizeof(arr[1])); // 1
printf("%d\n", sizeof(&arr)); // 4/8 不多解释了
printf("%d\n", sizeof(&arr + 1)); // 4/8
printf("%d\n", sizeof(&arr[0] + 1)); // 4/8
再来看关于 strlen 的:
#include
#include
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
//printf("%d\n", strlen(*arr)); //这两行代码会导致非法访问,屏蔽
//printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
return 0;
}
printf("%d\n", strlen(arr)); // 6 arr是整个数组地址
printf("%d\n", strlen(arr + 0)); // 6 arr+0是首元素地址
printf("%d\n", strlen(&arr)); // 6 &arr是整个数组地址
printf("%d\n", strlen(&arr + 1)); //随机值 &arr+1是跳过整个数组后的地址
printf("%d\n", strlen(&arr[0] + 1)); //5 &arr[0] + 1是第二个元素的地址
#include
int main()
{
const char* p = "abcdef"; //p存放的是字符串首元素 —‘a’的地址,可以通过该地址找到后面的元素
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
return 0;
}
printf("%d\n", sizeof(p)); // 4/8
//p存放的是字符串首元素 —‘a’的地址,地址大小就是 4/8
printf("%d\n", sizeof(p + 1)); // 4/8 ‘b’的地址
printf("%d\n", sizeof(*p)); // 1 首元素‘a’的大小为一字节
printf("%d\n", sizeof(p[0])); // 1 首元素‘a’的大小为一字节
printf("%d\n", sizeof(&p)); // 4/8 取出首元素地址
printf("%d\n", sizeof(&p + 1)); // 4/8 ‘b’的地址
printf("%d\n", sizeof(&p[0] + 1)); // 4/8 ‘b’的地址
再上strlen:
#include
#include
int main()
{
const char* p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p + 1));
//printf("%d\n", strlen(*p)); //老规矩,非法访问,先屏蔽
//printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p + 1));
printf("%d\n", strlen(&p[0] + 1));
return 0;
}
const char* p = "abcdef";
printf("%d\n", strlen(p)); //6 首元素地址
printf("%d\n", strlen(p + 1)); //5 第二个元素地址
printf("%d\n", strlen(&p)); //随机值
//p是首元素地址,那么&p就是把 p 的地址取出来,strlen从 p 的地址处开始找\0,见下图
printf("%d\n", strlen(&p + 1)); //随机值
printf("%d\n", strlen(&p[0] + 1)); //5 第二个元素地址
假设‘a’的地址是 0x0012ff40 ,那么p里存放的就是该地址,如下图:
strlen(&p)就是把p的地址放进去,然后从p的地址处开始找 \0 ,如图
图中标出&p的位置,从该处往后找 \0 ,地址是占4个或8个字节,我们这里用x86,占4个字节,那里面的数是怎么样的呢?(假设我们是小端存储,如有不懂请看往期)
我们可以看到第四个字节位置是00,也就是 \0 ,strlen应该是读到这里为止,那么strlen(&p)应该为3,这是在‘a’的地址确定的情况下,问题是我们不知道‘a’的地址是多少,在每次测试时都有可能不同,所以就是随机值了。strlen(&p + 1)也是同样的道理。
首先来补充一点:(假设有个二维数组 int arr[3][4])
arr 也可以看做另类的一维数组,我们可以把每行当成一个元素,这样第一行就可以用 a[0]来表示,第二行用 a[1]来表示,以此类推(a[0]、a[1]若是单独放在sizeof里面,或者前面加了个&,则a[0]、a[1]的类型才是int(*)[4],才能代表一行元素的地址,否则只能代表一行的首元素地址。如有疑惑看往期—指针进阶),那么每一行里的具体元素怎么表示呢?我们可以再加个括号即可,如:第一行第二个元素,我们可以写成:a[0][1]也可以写成 *(a[0]+1) 还可以写成 * ( *(a+0)+1)来表示,为什么呢?
我们知道 *(a+1)==a[1] ,那么二维数组第一行 a[0] 也可以写成 *(a+0) ,同样的 a[0][1]也就可以写成 *(a[0]+1)
下面我们来练练二维数组:
#include
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0] + 1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
return 0;
}
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); // 48
//a是二维数组的数组名,数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
printf("%d\n", sizeof(a[0][0])); // 4
//a[0][0]是一个整型元素,大小是4个字节
printf("%d\n", sizeof(a[0])); // 16
//a[0]是第一行的数组名,第一行的数组名单独放在sizeof内部,计算的是第一行的总大小,单位是字节 - 16
printf("%d\n", sizeof(a[0] + 1)); // 4/8
//a[0]虽然是第一行的数组名,但是并非单独放在sizeof内部
//a[0]作为第一行的数组名并非表示整个第一行这个数组,a[0]就是第一行首元素的地址,a[0]--> &a[0][0] - int*
//a[0]+1,跳过一个int,是a[0][1]的地址 4/8字节
printf("%d\n", sizeof(*(a[0] + 1))); // 4
//a[0]+1是第一行第二个元素的地址,所以*(a[0]+1)就是a[0][1],大小是4个字节
printf("%d\n", sizeof(a + 1)); // 4/8
//a是二维数组的数组名,没单独放在sizeof内部,也没有&,所以a就是数组首元素的地址
//二维数组,我们把它想象成一维数组,它的第一个元素就是二维数组的第一行
//a就是第一行的地址,a+1 是第二行的地址,是地址,大小就是 4/8 个字节
printf("%d\n", sizeof(*(a + 1))); // 16
//a+1是第二行的地址,*(a+1) 找到的就是第二行,sizeof(*(a + 1))也就是sizeof(a[1])计算的就是第二行的大小
printf("%d\n", sizeof(&a[0] + 1)); // 4/8
//&a[0]是第一行的地址,&a[0]+1就是第二行的地址,sizeof(&a[0] + 1)计算的第二行地址大小
printf("%d\n", sizeof(*(&a[0] + 1))); // 16
//&a[0] + 1是第二行的地址,*(&a[0] + 1)拿到的就是第二行,大小就是16个字节
printf("%d\n", sizeof(*a)); // 16
//a表示首元素的地址,就是第一行的地址 - &a[0]
printf("%d\n", sizeof(a[3])); //16
//a[3]是二维数组的第4行,虽然没有第四行,但是类型能够确定,大小就是确定的。大小就是一行的大小,单位是字节 - 16
做这类题就是要抓住核心要点,明白 strlen、sizeof 的用法,再多加练习就能做到秒杀,相信大家看完本期一定收获满满吧,以后碰到就不怕了。