C语言必学之指针——从入门到入坑——part6(chapter17)

前言

大家好呀,我是Humble

这是我们指针小专题的最后一篇博客,也是C语言初阶专栏的最后一篇博客。通过前三篇的 基础篇 以及四和五的 应用篇,相信大家对指针的理解都有了提升,那么我们本篇就来做一下练习

 

一.前置知识: sizeof和strlen的对比

在学习操作符的时候,我们学习了 sizeof ,知道它是一种单目操作符

 sizeof 计算变量所占内存内存空间大小的

如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小

sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据

举个例子

​
int main()
{
 int a = 10;
 printf("%d\n", sizeof(a));
 printf("%d\n", sizeof(int));
 
 return 0;
}

​

这里打印的结果是多少呢?

4cf862e45cfc4a16ad419f06d51cc843.png

 

我们发现它们的大小是一样的

我们再对数组进行测试

int main()
{
	int arr1[4] = { 0 }; 
	char arr2[4] = { 0 };
	printf("%zd\n",sizeof(arr1));
	printf("%zd\n", sizeof(arr2));

	return 0;
}

运行一下

ddf869fc3a1444628a5b22ac6ed6268e.png

我们发现sizeof计算大小时只关注占用内存空间的大小,不在乎里面放的数据

 

 

那我们再来复习一下strlen

 

strlen 是C语言库函数,功能是求字符串长度(注意,它只能用来求字符串长度!)

统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数

strlen 函数会⼀直向后找 \0 字符,直到找到为止,所以可能存在越界查找

举个例子

int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	return 0;
}

运行一下

b4b723cedd4a46ea9ab75cf58292ed9b.png

 

我们发现,第一个数据是一个随机数,因为arr1中不包含 \0 ,而strlen找的是\0之前出现的字符个数,所以会返回一个随机值

 

我们再跟sizeof求字符串进行一个对比

​
int main()
{
	char arr1[3] = { 'a', 'b', 'c' };
	char arr2[] = "abc";
	printf("%zd\n", strlen(arr1));
	printf("%zd\n", strlen(arr2));
	printf("%d\n", sizeof(arr1));
	printf("%d\n", sizeof(arr2));
	return 0;
}

​

运行一下

d2c0dd6e04494fca9e05b360b78b78c6.png

 

 

因为arr1占3个字节,所以用sizeof打印的是3,而arr2有abc\0四个数,所以打印4

 

下面我们对sizeof 和 strlen进行一个总结:

sizeof计算操作数所占内存的大小, 单位是字节 3. 不关注内存中存放什么数据

 

srtlen是求字符串长度的,统计的是 \0 之前字符的个数

 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

 

 

 

二.数组和指针笔试题

1.一维整型数组

请看下面代码,判断结果

int main()
{
	int a[] = { 1,2,3,4 };

	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof(a + 0));
	printf("%zd\n", sizeof(*a));
	printf("%zd\n", sizeof(a + 1));
	printf("%zd\n", sizeof(a[1]));
	printf("%zd\n", sizeof(&a));
	printf("%zd\n", sizeof(*&a));
	printf("%zd\n", sizeof(&a + 1));
	printf("%zd\n", sizeof(&a[0]));
	printf("%zd\n", sizeof(&a[0] + 1));
}

在分析之前,我们先来回忆一下关于数组名的理解

数组名一般表示数组首元素的地址,但是有2个例外

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小

2. &数组名,这里的数组名表示整个数组,取出的是数组的地址

除此之外,所有遇到数组名都是数组首元素的地址

 

sizeof(a)中,a作为1个数组名,单独放在sizeof中,属于例外1,所以计算的是整个数组的大小,结果是16 

 

sizeof(a+0)中,数组名a是数组首元素的地址,a的类型是int*,a+0还是首元素的地址

所以sizeof(a+0)算的是数组首元素的地址的大小,

我们之前学过,在64位环境下,一个地址的大小是8个字节

在32位环境下,一个地址的大小是4个字节,我们用X64环境打印,结果就是8

 

sizeof(*a)中,数组名a是数组首元素的地址,*a就是首元素,所以计算的结果大小是4

 

sizeof(a+1)中,数组名a是数组首元素的地址,a+1就是第二个元素的地址,所以计算的结果在64位环境下还是8

 

 sizeof(a[1]) 中,计算的结果就是数组第二个元素的大小,是4

 

sizeof(&a) 中,属于例外2,&a表示一个数组的地址,但是数组的地址也是地址,所以计算的结果在64位环境下还是8

 

sizeof(*&a)  中,这里的*与&抵消,所以结果与 sizeof(a) 一样,算的是整个数组的大小,结果是16

 

 sizeof(&a + 1)  ,属于例外2,&a表示一个数组的地址,所以&a+1跳过整个数组,但它指向的仍然是个地址,所以计算的结果在64位环境下还是8

 

sizeof(&a[0]) 中,&a[0]表示的是数组第一个元素的地址,结果在64位环境下还是8

 

sizeof(&a[0]+1)  中&a[0]+1 是第二个元素的地址,所以计算的结果在64位环境下还是8

 

我们对上面的结果进行一个汇总,结果一个是16,8,4,8,4,8,16,8,8,8

我们运行一下

4857fc625eec45c9bb368d17b47506ec.png

 

 

 

2.字符数组

int main()
{
	
	char arr[] = { 'a','b','c','d','e','f' };  //arr数组中6个元素,没有\0
	printf("%zd\n", sizeof(arr));
	printf("%zd\n", sizeof(arr + 0));
	printf("%zd\n", sizeof(*arr));
	printf("%zd\n", sizeof(arr[1]));
	printf("%zd\n", sizeof(&arr));
	printf("%zd\n", sizeof(&arr + 1));
	printf("%zd\n", sizeof(&arr[0] + 1));

}

 

有了上面对整型数组的讲解,这里的题目看起来应该轻松很多了

我们在64位环境下打印一下,验证一下结果与我们设想的是否相同

 

(特别注意,在下面的题目中,没有特别标明的话就是在X64环境下演示的)

 

926856dc5ba04395add09250310b8e23.png

 

 

继续练习,希望大家对这种题越来越熟练

我们看一下strlen,大家的大脑要切换成strlen了,现在不是sizeof了哦

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) 和  strlen(arr + 0) 结果都是随机值,

第三个 strlen(*arr) 就比较有意思了,*arr是 ’a‘,传给 strlen 的就是a的ASCII值97

strlen就把97当成地址了,但97这个地址没有被分配,所以程序直接奔溃

同理,第四个 strlen(arr[1]),arr[1]是’b‘,

传给 strlen 的就是b的ASCII值98

strlen就把98当成地址了,所以程序也直接奔溃

第五个 strlen(&arr])也是随机值

第六个  strlen(&arr + 1),&arr + 1 跳过了整个数组,那它也是随机值

第七个  strlen(&arr[0] + 1)  ,这里从  'b'  开始  ,  但结果也是随机值(因为数组中没有\0)

 

我们除去第三和第四个代码,它们会让程序崩溃,我们打印剩下的五个,看看结果

7097eb3a7e4446879d3197fd655b620e.png

 

 

 

下一道题

char arr[] = "abcdef";
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));

这是结果

042573e15293479b8c663b529f4b7cec.png

下一道题

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));

这是结果004bcb917130491c922486c63acabe13.png

 

 

再下一道题

int main()
{
	char* p = "abcdef";
	printf("%zd\n", sizeof(p));
	printf("%zd\n", sizeof(p + 1));
	printf("%zd\n", sizeof(*p));
	printf("%zd\n", sizeof(p[0]));
	printf("%zd\n", sizeof(&p));
	printf("%zd\n", sizeof(&p + 1));
	printf("%zd\n", sizeof(&p[0] + 1));

	return 0;

}

这是结果

caf86e5325b64057b2ad263486848615.png

下一道题

	char* p = "abcdef";
	printf("%zd\n", strlen(p));
	printf("%zd\n", strlen(p + 1));
	printf("%zd\n", strlen(*p));
	printf("%zd\n", strlen(p[0]));
	printf("%zd\n", strlen(&p));
	printf("%zd\n", strlen(&p + 1));
	printf("%zd\n", strlen(&p[0] + 1));
	return 0;

这是结果2e74c4dcdd8f47a18b8b41bb34511bab.png

 

 

3.二维数组

 

看完了二维数组,我们再来看一下二维数组

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]));

我们重点看一下最后一个

printf("%zd\n", sizeof(a[3]));  

这个会不会越界?

答案是不会

因为sizeof内部的表达式不会真实计算的

结果还是16

 

打印结果如下

1ae56326eaf54db7b24d6af334cbc44d.png

 

 

 

好,做完这些题后,我们再回顾一下数组名的意义:

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小

2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址

 3. 除此之外所有的数组名都表示首元素的地址

三.. 指针运算笔试题

例子1.


int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

下面是结果:66ab314a74d44ebdbe8bd1fd6ca4df14.png

 

例子2.

//在X86环境下
//结构体的大小是20个字节
//程序输出的结果是多少?
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

下面是结果:

497683b015694130af18c49d391ea63a.png

 

例子3.

 

注意:a数组中是用()不是 { } 哟

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

 这是结果:9ccb883a93284f65b500116914ed0064.png

 

 

例子4.

//假设环境是x86环境,程序输出的结果是多少?

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

这是结果:de9b4bce6f5740b0a26cdbe92af2c7e5.png

 

 

例子5.

int main()
{
 int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 int *ptr1 = (int *)(&aa + 1);
 int *ptr2 = (int *)(*(aa + 1));
 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
 return 0;
}

这是结果:984a5ef1419a4823933cf0c588d2054d.png

例子6.

int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

这是结果:6fbf000455154c629596d1548ecb267d.png

 

结语:

 


好了,看到这的小伙伴,恭喜你初步学完了指针的所有内容!

下一篇博客我会为大家带来关于字符与字符串函数的干货

在学习C语言的道路上与各位同行

希望大家点个赞或者关注吧(感谢感谢)

让我们在接下来的时间里一起成长,一起进步吧!


11d7d69969784ba2a62ccf12606261ed.jpg

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不吃肉的Humble

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

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

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

打赏作者

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

抵扣说明:

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

余额充值