【C语言】指针进阶(3)

目录

指针和数组笔试题解析

一维数组

字符数组

二维数组

指针笔试题


在前面两篇文章,我们已经学完了指针进阶的所有知识点。在这篇文章中,我们主要学习的是一些常见的笔试题的总结。

指针和数组笔试题解析

在做题之前,我们先复习一下之前关于数组名所学过的知识:

对数组名的理解:
数组名是数组首元素的地址
但是有两个例外:
1.sizeof(数组名) 这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2.&数组名,这里的数组名也表示整个数组,取出的是整个数组的大小

在32位平台上,地址的大小是4字节,在64位平台上,地址的大小是8字节。

一维数组

分析下面代码:

复制代码
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));

分析代码:

数组名和&数组名的区别:

字符数组

分析下面代码:

复制代码
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));

代码分析:

分析下面代码:

复制代码
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));

分析:

strlen 是求字符串长度的库函数,它统计的是字符串中 '\0' 之前的字符个数, 如果没有 '\0' 就会一直往后找。对于上面的数组arr,由于数组中不含 '\0', 所以用strlen求其长度的时候找不到 '\0',便会返回一个随机值。这个随机值是数组越界访问后,strlen找到内存中某个\0返回的结果。

对于下面这行代码:

传的参数类型是数组的首元素,'a' 的ASCLL值是97,那就是将97作为地址传参,strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了。

当我们运行时,程序会崩溃:

strlen可以传数组名和&数组名

对于这句代码:

我们给strlen传的参数是 &arr, 它的类型是 char(*)6, 虽然不是char*的类型,但当传到strlen时,strlen的形参是const char* 类型的指针接收地址,所以&arr会被强制类型转化为char*.

所以传&arr和arr都是可以的。

strlen的参数类型:

分析下面代码:

复制代码
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));

注意:当以双引号 "" 的形式初始化字符串是,字符串末尾会自动添加一个'\0'

代码分析:

分析下面代码:

复制代码
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));

代码分析:

分析下面代码:

复制代码
char* p = "abcdef";
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));

代码分析:

p, &p, &p+1的关系:

分析下面代码:

复制代码
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));

代码分析:

二维数组

分析下面代码:

复制代码
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]));

代码分析:

对于二维数组:

关于sizeof的一点知识:

当sizeof()括号中带有表达式时,表达式的值并不会去计算,sizeof只会根据表达式的类型求出结果。比如下面的代码:

打印的结果是:

表达式 s=a+2 并没有计算。

总结: 数组名的意义:

  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;
}
//程序的结果是什么?

解析:

打印结果:

笔试题2:

本题需要使用x86的环境

复制代码
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

要是p的值是0x100000,我们可以0x100000强制类型转化为 struct Test* 再赋给p:

解析:

打印结果:

笔试题3:

复制代码
int main()
{
 int a[4] = { 1, 2, 3, 4 };
 int *ptr1 = (int *)(&a + 1);
 int *ptr2 = (int *)((int)a + 1);
 printf( "%x,%x", ptr1[-1], *ptr2);
 return 0;
}

对于ptr1指针进行分析:

对于ptr2指针进行分析:

打印结果:

笔试题4:

复制代码
#include <stdio.h>
int main()
{
 int a[3][2] = { (0, 1), (2, 3), (4, 5) };
 int *p;
 p = a[0];
 printf( "%d", p[0]);
 return 0;
}

这道题有一个陷阱,稍不小心就会做错:

这个数组在进行初始化的时候,是采取小扩括号的形式,并不是大括号。我们根据逗号表达式进行求值。

打印结果:

笔试题5:

复制代码
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;
}

分析:

打印结果:

笔试题6:

复制代码
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;
}

解析:

打印结果:

笔试题7:

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

解析:

打印结果:

笔试题8:

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

首先,我们根据前三句代码,画出它们的指向关系:

当执行第一句printf时:

当执行第二句printf时:

当执行第三句printf时:

当执行第四句printf时:

好了,指针进阶系列的文章到这儿就结束了,感谢大家的支持哦

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜9 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa9 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio