深入解剖指针(5)

 个人主页(找往期文章包括但不限于本期文章中不懂的知识点):我要学编程(ಥ_ಥ)-CSDN博客

目录

sizeof和strlen的对比

sizeof

strlen 

sizeof 和 strlen的对比 

数组和指针笔试题解析

一维数组

字符数组


 

sizeof和strlen的对比

sizeof

在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存空间的大小,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据。

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof a);
	printf("%zd\n", sizeof(int));//类型一定要加括号
	return 0;
}

注意这个sizeof 的返回类型是size_t 。 

strlen 

strlen 是C语言库函数,功能是求字符串长度。函数原型如下:

//返回类型是size_t,参数是字符指针
size_t strlen ( const char * str );

统计的是从 strlen 函数的参数 str 中这个地址开始向后, ’\0‘ 之前字符串中字符的个数。 strlen 函数会一直向后找 ’\0‘ 字符,直到找到为止,所以可能存在越界查找。

#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%zd\n", strlen(arr1));//随机值
	printf("%zd\n", strlen(arr2));//3

	printf("%zd\n", sizeof(arr1));//3
	printf("%zd\n", sizeof(arr1));//3
	return 0;
}

sizeof 和 strlen的对比 

1. sizeof是操作符1. strlen是库函数,使用需要包含头文件 string.h
2. sizeof计算操作数所占内存的大小, 单位是字节2. srtlen是求字符串长度的,统计的是 ‘\0’ 与最开始的字符之间相隔个数
3. 不关注内存中存放什么数据3. 关注内存中是否有 ‘\0’ ,如果没有 ‘\0’ ,就会持续往后找,可能会越界

数组和指针笔试题解析

以下代码的运行都是在VS2022,X64的环境。 

一维数组

#include <stdio.h>
int main()
{
	//数组名有特殊两种情况下,是表示整个数组的地址
	//1.sizeof(数组名),注意:这个数组名是指只有单独的数组名
	//2.&数组名,这里取出的是整个数组的地址
	int a[] = { 1,2,3,4 };//a是数组名
	//sizeof(数组名),这个数组名是只有单独的数组名,
	//因此计算的是整个数组在内存中的大小。
	printf("%zd\n", sizeof(a));//16

	//sizeof(数组名),这个数组名不是只有单独的数组名,
	// 因此,做常规处理,是数组首元素的地址,+0,相当于没加
	printf("%zd\n", sizeof(a + 0));//指针大小:4/8

	//不是上面两种情况,就是首元素的地址,解引用之后,就是首元素
	//相当于计算sizeof(1)
	printf("%zd\n", sizeof(*a));//4

	//不是上面两种情况,就是首元素的地址,+1,跳过的4个字节,
	//也就是计算sizeof(&a[1]),还是地址
	printf("%zd\n", sizeof(a + 1));// 4/8

	printf("%zd\n", sizeof(a[1]));//4

	//这里虽然取出的是整个数组的地址,但是还是计算地址的大小
	printf("%zd\n", sizeof(&a));// 4/8

	//先把整个数组的地址取出来,再解引用,
	//那么拿到的就是整个数组的大小
	//可以简单理解为* 与 & 相互抵消了
	printf("%zd\n", sizeof(*&a));//16
	
	//地址+1,还是地址
	printf("%zd\n", sizeof(&a + 1));// 4/8

	//地址
	printf("%zd\n", sizeof(&a[0]));// 4/8
	
	//地址+1
	printf("%zd\n", sizeof(&a[0] + 1));// 4/8
	return 0;
}

字符数组

代码1:

#include <stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	//sizeof(数组名)
	printf("%zd\n", sizeof(arr));//6
	//sizeof(地址)
	printf("%zd\n", sizeof(arr + 0));// 4/8
	//sizeof('a')
	printf("%zd\n", sizeof(*arr));//1
	//sizeof('b')
	printf("%zd\n", sizeof(arr[1]));//1
	//sizeof(地址)
	printf("%zd\n", sizeof(&arr));// 4/8
	//sizeof(地址)
	printf("%zd\n", sizeof(&arr + 1));// 4/8
	//sizeof(地址)
	printf("%zd\n", sizeof(&arr[0] + 1));// 4/8
	return 0;
}

代码2:

#include <stdio.h>
#include <string.h>
int main()
{
	//注意这里不再是sizeof,而是strlen,
	// strlen统计的是最开始的那个元素到'\0'之前的个数
	char arr[] = { 'a','b','c','d','e','f' };
	//注意这里是一个一个的字符,没有'\0'结尾
	printf("%zd\n", strlen(arr));//随机值
	//这里的arr是首元素的地址,与上面的一样,加0,不变
	printf("%zd\n", strlen(arr + 0));//随机值
	//通过前面的函数原型学习,我们知道strlen的参数是一个指针
	//然而这里却是一个arr[0]-->'a',因此这个是不正确的
	printf("%zd\n", strlen(*arr));//错误
	//与上一个一样
	printf("%zd\n", strlen(arr[1]));//错误
	//&arr,取出的是整个数组的地址,
	//整个数组的地址的起始位置与数组首元素的地址相同
	printf("%zd\n", strlen(&arr));//随机值
	//这也是一个随机值,只不过比起上面的要小6,
	//因为+1,跳过了整个数组了
	printf("%zd\n", strlen(&arr + 1));//随机值-6
	//这个就是从第二个元素开始统计
	printf("%zd\n", strlen(&arr[0] + 1));//随机值-1
	return 0;
}

上面的那个错误,会导致整个程序崩溃,只能打印出其前面的值。

通过上面两幅图的比较,可以得出上面的结论。具体细节可以使用调试来找到:

但是又有两个个新的问题来了,为什么后面的数组明明已经越界访问了,但是没有报错呢?其实这个编译器认为这个错误比起刚刚那个错误不算什么,所以就没有报错,但是我们平时在写代码时,就不要犯这样的错误。另外一个问题就是明明是随机值,但是为什么我们打印出来的是一个定值呢?其实这个是因为我们在第一次创建这个字符数组的时候,它所对应的地址已经确定了(即'\0',出现的位置已经确定),不会再发生改变了,因此无论我们调用多少次这个strlen函数,其返回值都不会发生变化。这也就意味着这个随机值已经固定。

代码3: 

#include <stdio.h>
int main()
{
	//这个字符数组与上面那个字符数组的区别在于:
	//这个字符数组后面跟了一个'\0',而上面那个没有
	char arr[] = "abcdef";
	//sizeof(数组名)
	printf("%zd\n", sizeof(arr));//7(包括了'\0')
	//sizeof(地址)
	printf("%zd\n", sizeof(arr + 0));// 4/8
	//sizeof('a')
	printf("%zd\n", sizeof(*arr));//1
	//sizeof('b')
	printf("%zd\n", sizeof(arr[1]));//1
	//sizeof(地址)
	printf("%zd\n", sizeof(&arr));// 4/8
	//sizeof(地址)
	printf("%zd\n", sizeof(&arr + 1));// 4/8
	//sizeof(地址)
	printf("%zd\n", sizeof(&arr[0] + 1));// 4/8
	return 0;
}

代码4:

#include <stdio.h>
#include <string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//6
	//arr是数组首元素的地址,+0,还是一样的
	//与上面的结果相同
	printf("%d\n", strlen(arr + 0));//6
	//arr是数组首元素的地址,解引用之后就是首元素'a'
	//strlen的参数是指针,因此这个会报错
	//printf("%d\n", strlen(*arr));//错误
	//arr[1]是'b',与上面一样会报错
	//printf("%d\n", strlen(arr[1]));//错误
	//&arr,虽然是取出整个数组的地址,但是也是从首元素开始的
	printf("%d\n", strlen(&arr));//6
	//&arr+1,会跳过整个数组,但是后面的我们就不知道了
	//就意味着是随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	//&arr[0]+1,拿到首元素的地址后,在跳过1个元素,即&arr[1]
	printf("%d\n", strlen(&arr[0] + 1));//5
	return 0;
}

代码5:

#include <stdio.h>
int main()
{
	//这里是把a的地址存放到p指针里的,而不是这个字符串地址存放到p指针里头
	char* p = "abcdef";
	//p是一个指针
	printf("%zd\n", sizeof(p));// 4/8
	//p+1就是b的地址
	printf("%zd\n", sizeof(p + 1));// 4/8
	//*p的结果就是'a'
	printf("%zd\n", sizeof(*p));//1
	//p[0]<-->*(p+0)<-->*p
	printf("%zd\n", sizeof(p[0]));//1
	//注意p本来就是一个指针,现在把它的地址拿到,就是二级指针
	printf("%zd\n", sizeof(&p));// 4/8
	//在上一个的基础上,再跳过一个char*
	printf("%zd\n", sizeof(&p + 1));// 4/8
	//&p[0]<-->& *(p+0),这个&与*在效果上是一样的,即为p,指针
	printf("%zd\n", sizeof(&p[0] + 1));// 4/8
	return 0;
}

代码6:

#include <stdio.h>
#include <string.h>
int main()
{
	char* p = "abcdef";
	//p=&a
	printf("%d\n", strlen(p));//6
	//p+1=&b
	printf("%d\n", strlen(p + 1));//5
	//*p=a,strlen的参数是指针
	printf("%d\n", strlen(*p));//错误
	//p[0]<-->*(p+0)<-->*p
	printf("%d\n", strlen(p[0]));//错误
	//p是一个二级指针,不知道其后是什么,因此是随机值
	printf("%d\n", strlen(&p));//随机值
	//与上面一样,也是随机值,但是与上面那个随机值没有任何关系
	printf("%d\n", strlen(&p + 1));//随机值
	//&p[0]<-->& *(p+0)<-->p
	printf("%d\n", strlen(&p[0] + 1));//5
	return 0;
}

说明一下'\0'的ASCll值为0,对应的十六进制也是为0
char *p = "abcdef";
假设p的地址为0x0012ff40,那么strlen的结果就是0,p+1,我们也不知道其后面是什么,可能也有'\0',因此这两个随机值并没有任何关系。

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要学编程(ಥ_ಥ)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值