指针(5)C语言指针笔试题

目录

一.sizeof和strlen的对⽐

1.sizeof

2.strlen - 针对字符串

3.sizeof 和 strlen的对比

二. 数组和指针笔试题解析

1.⼀维数组

2.字符数组

代码1:

代码2:

代码3:

​代码4:

代码5:

3.⼆维数组

三. 指针运算笔试题解析

题⽬1

题⽬2

题⽬3

题⽬4

题⽬5

​题⽬6

​         题⽬7(有点难)


一.sizeof和strlen的对⽐

1.sizeof

sizeof:是单目操作符sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。//都是计算的类型的长度
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

代码举例:

int main()
{
	int a = 10;
	printf("zd\n", sizeof(a));//4
	printf("zd\n", sizeof(int));//4
	printf("zd\n", sizeof a);//4 变量可以去掉括号,
	//printf("zd\n", sizeof int);错误写法,类型不能去掉括号

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

	return 0;
}

总结:
1.sizeof:计算的是类型所占的空间大小,或者是这个变量或者数组占了几个字节。
2.数组去掉名字是类型,sizeof也可以计算数组的类型。

2.strlen - 针对字符串

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen(const char* str);
|                       |
|                     参数
|
返回类型

strlen函数统计的\0之前的字符个数,strlen函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。

代码举例:

int main()
{
	char arr[20] = "abcdef";
	size_t len = strlen(arr);
	printf("%d\n", len);//6

    len = sizeof(arr);
	printf("%zd\n", len);//20

	char arr1[] = { 'a','b','c' };
	//char arr[6] = "abcdef"错误写法,只能放下6个字符的大小,但你要放7个
	printf("%zd\n", strlen(arr1));//随机值 - 找到\0为止

	return 0;
}

3.sizeof 和 strlen的对比

sizeof:
1.sizeof是单目操作符
2.sizeof计算操作数所占内存的⼤⼩,单位是字节
3.不关注内存中存放什么数据
4.sizeof不挑类型

strlen:
1.strlen是库函数,使用需要包含头文件strlen.h
2.strlen是求字符串长度的,统计的是\0之前字符的个数
3.关注内存中是否有\0,如果没有\0,就会持续往后找,可能会越界。
4.只能针对字符串

二. 数组和指针笔试题解析

1.⼀维数组

数组名的理解:
数组名一般表示数组首元素的地址
但有2个列外:
1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,数组名表示整个数组,取出的数组的地址。

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

代码举例:地址根据x86或者64位环境显示结果

int main()
{
	int a[] = { 1,2,3,4 };//a数组有4个元素,每个元素是int类型的数据
	printf("%d\n", sizeof(a));//sizeof(数组名)的情况,计算的是整个数组的大小,单位是字节 - 16
	printf("%d\n", sizeof(a + 0));//a表示的就是数组首元素的地址,a+0还是首元素的地址 - sizeof计算的是第一个元素的地址大小 - 4/8
	printf("%d\n", sizeof(*a));//a表示数组首元素的地址,*a就是首元素,大小就是 - 4
	printf("%d\n", sizeof(a + 1));//a表示数组首元素的地址,a+1就是第二个元素的地址,- sizeof计算的是第一个元素的地址大小 - 4/8
	a == &a[0],类型:int*

	printf("%d\n", sizeof(a[1]));//计算第二个元素的大小 - 4
	printf("%d\n", sizeof(&a));//&a - 取出的是数组的地址,但是数组的地址也是地址,是地址,大小是4/8
	printf("%d\n", sizeof(*&a));//sizeof(a) - 16
	1.&和*抵消了,
	2.&a的类型是数组指针,int(*)[4],*&a就是对数组指针解引用访问一个数组的大小,- 16
	printf("%d\n", sizeof(&a + 1));//&a+1是跳过整个数组后的地址,是地址,大小就是 - 4/8
	printf("%d\n", sizeof(&a[0]));//*a[0]是数组第一个元素的地址,大小就是 - 4/8
	printf("%d\n", sizeof(&a[0] + 1));//&a[0]+1是数组第二个元素的地址,大小就是 - 4/8
	

2.字符数组

代码1:

代码举例:地址根据x86或者64位环境显示结果

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };//arr数组有6个元素

	printf("%d\n", sizeof(arr));//计算的是整个数组的大小 - 6
	printf("%d\n", sizeof(arr + 0));//arr+0 是数组第一个元素的地址 - 4/8
	printf("%d\n", sizeof(*arr));//*arr是首元素,计算的是首元素的大小 - 1
	printf("%d\n", sizeof(arr[1]));//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
}

代码2:

代码举例:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\n", strlen(arr));//遇到\0为止 - 随机值
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));//程序会崩掉
	//strlen:参数是地址,* arr是元素'a', 'a'的ASCLL码值是97,97被当成地址了访问了,没有分配给你去使用,程序会崩掉
	printf("%d\n", strlen(arr[1]));//传递的第二个元素 - 程序会崩掉
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值
}

代码3:

代码举例:

int main()
{
	char arr[] = "abcdef";

	printf("%zd\n", sizeof(arr));//7
	printf("%zd\n", sizeof(arr + 0));//arr是数组首元素的地址,地址大小是4/8个字节
	printf("%zd\n", sizeof(*arr));//*arr是数组的首元素,这里计算的是首元素的大小 1
	printf("%zd\n", sizeof(arr[1]));//1
	printf("%zd\n", sizeof(&arr));//&arr - 取出的是数组的地址,数组的地址也是地址,大小是4/8
	printf("%zd\n", sizeof(&arr + 1));//&arr + 1是跳过整个数组后的地址,是地址,大小就是 - 4/8
	printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 - 4/8
}

代码4:

代码举例:

int main()
{
	char arr[] = "abcdef";
	printf("%zd\n", strlen(arr));//6
	printf("%zd\n", strlen(arr + 0));//arr+0是数组首元素的地址 - 6
	printf("%zd\n", strlen(*arr));//传递的是'a'-97,把97传给strlen访问97地址内存找字符串,不可访问,错误代码
	printf("%zd\n", strlen(arr[1]));//'b' - 98,会对98解引用访问的,错误代码
	printf("%zd\n", strlen(&arr));//&arr虽然是数组的地址,但是也是指向数组的起始位置 - 6,这样的代码会有警告 ,类型有所差异
	//&arr取出数组的地址,应该放在数组指针里面
	//char(*p)[7] = &arr;数组指针
	//char(*)[7]; 类型
	printf("%zd\n", strlen(&arr + 1));//随机值
	printf("%zd\n", strlen(&arr[0] + 1));//从第二个元素地址开始访问 - 5
}

代码5:

代码举例:

int main()
{
char* p = "abcdef";//常量字符串
//p的类型char*
printf("%zd\n", sizeof(p));//计算的是指针变量的大小 - 4/8
printf("%zd\n", sizeof(p + 1));//p+1是'b'的地址,是地址大小就是 - 4/8
printf("%zd\n", sizeof(*p));//*ps'a',大小是 - 1
printf("%zd\n", sizeof(p[0]));//p[0]-->*(p+0) -- *p - 1
printf("%zd\n", sizeof(&p));//&p也是地址,是指针变量p的地址,大小是 - 4/8
//p是首元素的地址,&p是指针变量的地址,用二级指针存储一级指针的地址

printf("%zd\n", sizeof(&p + 1));//&p+1是指向p指针变量后面的空间,也是地址 - 4/8
//char* p;
//char** q = &p;char**是指针变量的类型 - 指向对象的类型是char*
//q+1//跳过一个char*的变量 

printf("%zd\n", sizeof(&p[0] + 1));//&p[0]+1 -->(p+1)-->是'b'的地址,是地址就是 - 4/8
}

代码6:

代码举例:

int main()
{
	char* p = "abcdef";//常量字符串

	printf("%zd\n", strlen(p));//6
	printf("%zd\n", strlen(p + 1));//5
	printf("%zd\n", strlen(*p));//'a'-97-97作为地址,接应用访问,所以错误代码
	printf("%zd\n", strlen(p[0]));//p[0]-->*(p+0)-->*p,接应用访问,所以错误代码
	printf("%zd\n", strlen(&p));//随机值
	printf("%zd\n", strlen(&p + 1));//随机值
	printf("%zd\n", strlen(&p[0] + 1));//5
}

3.⼆维数组

代码举例:地址根据x86或者64位环境显示结果

int main()
{
	//二维数组也是数组,之前对数组名理解也适合
	int a[3][4] = { 0 };

	printf("%d\n", sizeof(a));//12*4 = 48,数组名单独放在sizeof内部,计算的整个数组的大小
	printf("%d\n", sizeof(a[0][0]));//计算的第一个元素 - 4
	printf("%d\n", sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof内部
	//计算的是第一行的大小 - 16

	printf("%d\n", sizeof(a[0] + 1));//a[0]第一行这个一维数组的数组名,这里表示数组首元素
	//也就是a[0][0],a[0]+1是a[0][1]的地址 - 4/8

	printf("%d\n", sizeof(*(a[0] + 1)));//第一行第二个元素,a[0][1] - 4
	printf("%d\n", sizeof(a + 1));//a是二维数组的数组名,但是没有&,也没有单独放在sizeof内部
	//所以这里a是数组首元素的地址,应该是第一行的地址,a+1是第二行的地址,所以大小 - 4/8

	printf("%d\n", sizeof(*(a + 1)));//*(a+1)==>a[1] - 第二行的数组名,所以计算的第二行的大小 - 16
	printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,&a[0]+1就是第二行的地址 - 4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//对第二行地址解引用,访问的是第二行,计算的是第二行的大小 - 16
	printf("%d\n", sizeof(*a));//这里的a是第一行的地址,*a就是第一行,sizeof(*a)计算的是第一行的大小 - 16
	//*a-->*(a+0)-->a[0]
	printf("%d\n", sizeof(a[3]));
	//这里不存在越界,因为sizeof内部的表达式不会真实计算的。
	//计算的是第四行的大小 - 16 - 计算的就是a[0]
	return  0;
	 
}

三. 指针运算笔试题解析

题⽬1

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);//强制转换成int*赋给ptr,解引用(+-)会发生变化
	printf("%d,%d", *(a + 1), *(ptr - 1));//2 5
	//*(a+1) a表示数组首元素地址,a+1表示第二个元素的地址,解引用得到 - 2
	//*(ptr - 1)) 取出整个数组的地址+1,跳过整个数组,(ptr - 1)拿到元素5的地址,解引用 - 5
	return 0;
}

题⽬2

在X86环境下 - x64环境结构体指针的大小会不一样
假设结构体的⼤⼩是20个字节
程序输出的结果是啥?

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;//0x100000是整形,要强制类型转换成结构体指针类型,放到指针变量
int main()
{
	printf("%p\n", p + 0x1);//+1跳过一个结构体的大小,0x100000+20 == 0x100014
	printf("%p\n", (unsigned long)p + 0x1);//p被强制转换整形+1,0x100000+1 == 0x100001
	printf("%p\n", (unsigned int*)p + 0x1);//p被强制类型转换成无符号整形指针+1,0x100000+4 ==0x100001
	return 0;
}

考察指针+1到底加几 - 指针类型变了,解引用和一次访问权限也要跟着变
注意十六进制0x1的1也是1

题⽬3

int main()
{
	//注意逗号表达式
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };//真正初始化的是{1,3,5} <==>{{1,3},{5,0},(0,0)}
	//每一行数组得用大括号初始化,用小括号,是逗号表达式,会从左往右以此计算,整个表达式是最后个表达式结果
	int* p;
	p = a[0];//数组名表示首元素地址,没有放在sizeof内部,也没有&
	printf("%d", p[0]);//1
	return 0;
}

题⽬4

假设环境是x86环境,程序输出的结果是啥?

int main()
{
	int a[5][5];
	int(*p)[4];//p是数组指针,指向的数组4个int类型的元素,一次访问4个整形,或是跳过4个整形
	p = a;//a表示首元素的地址,就是二维数组第一行的地址,类型会有差异,a的地址类型应该是:int(*p)[5]
	//但是p也能接收a的地址,一次访问4个整形,或是跳过4个整形
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//这里就是低地址 - 高地址 = //中间元素个数 
	//%d = -4 - 打印整数的源码,内存存储的是补码
	//%p = -4 - 把-4的补码翻译成16进制打印 - FFFFFFFC
	//10000000000000000000000000000100 - 源码-4
    //11111111111111111111111111111011 - 反码
	//11111111111111111111111111111100 - 补码-4
	//FFFFFFFC - 转成16进制
	return 0;
}

分析:
1.分析指针变量的类型和二维数组的类型是不一样的
2.先找&a元素位置,再找&p的元素的位置,因为数组指针类型不一样,一次跳过的整形个数也会不一样
3.指针 - 指针得到的是中间元素个数
4.内存存储的补码,以补码形式打印出来的地址,转换成16进制。

题⽬5

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);//取出整个数组的地址+1,跳过整个数组
	int* ptr2 = (int*)(*(aa + 1));//aa表示首元素地址,也就是第一行的地址,aa+1就是第二行的位置 
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//5 10
	return 0;
}

题⽬6

int main() 
{
	char* a[] = { "work","at","alibaba" };//a是char*类型的指针数组,初始化了3个元素
	char** pa = a;//pa指向对象是char*类型,pa指向a的第一个元素,第一个字符w起始位置
	pa++;//pa指向a第二个元素,第一个字符a起始位置
	printf("%s\n", *pa);//at
	return 0;
}

题⽬7(有点难)

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);//POINT
	printf("%s\n", *-- * ++cpp + 3);//ER
	printf("%s\n", *cpp[-2] + 3);//ST
	printf("%s\n", cpp[-1][-1] + 1);//EW
	return 0;
}

分析:
1.c是个char*类型的数组,里面有4个元素,放的是4个字符串首字母的地址。
2.cp是个char**类型的数组,里面有4个元素,数组名表示首元素地址,分别指向c数组4个元素
3.cpp是个char***类型的数组,是个3级指针,把2级指针地址存在了3级指针,数组名表示数组首元素地址,存了cp的地址 -相当于指向了cp起始位置
再分析:
1. **++cpp:指向cp起始位置++指向cp1的位置,第一次解引用访问到cp数组c+2这块空间,第二次解引用访问到c+2指向c2的空间。
2. * -- * ++cpp + 3:先看运算符优先级,+号是最低的,++cpp:指向cp2的位置,解引用访问到cp数组c+1这块空间,--:c+1-1里面值-1 = c,
指向了c0,解引用访问到这块空间,然后地址+3,拿到E
3.cpp[-2] -->*(cpp-2),cpp-2指向了cp0,解引用访问到cp数组c+3这块空间,再解引用访问到c3这块空间,然后F地址+3,拿到S
4.cpp[-1]-->*(cpp-1),cpp-1指向cp1,解引用访问到cp数组c+2这块空间,再*(c+2-1),访问到c1这块空间N的地址+1,拿到E

5.cpp[-1][-1]:相当于*(*(cpp-1)-1),先算* (cpp - 1),得到*(c+2-1)再解引用。

总结:画图能帮我们快速解决这些指针运算题,要清楚指针指向了谁,每次地址的访问权限。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值