文章目录
- ▶️1.预备知识:💯
-
- ▶️1.1二维数组是怎么存储的?💯
- ▶️1.1 总结💯
- ▶️2.二维数组和指针相关笔试题💯
-
- ▶️2.1二维数组和指针相关笔试题讲解💯
- ▶️2.2 VS运行结果演示💯
- ▶️2.3总结💯
- ▶️3.指针运算笔试题解析💯
-
- ▶️3.1题目1:💯
- ▶️3.1.1 题目1讲解:💯
- ▶️3.1.2 vs测试结果:💯
- ▶️3.2 题目2:💯
- ▶️3.2.1 题目2讲解:💯
- ▶️3.2.2 vs测试结果:💯
- ▶️3.3 题目3:💯
- ▶️3.3.1 题目3讲解:💯
- ▶️3.3.2 vs测试结果:💯
- ▶️3.4 题目4:💯
- ▶️3.4.1 题目4讲解:💯
- ▶️3.4.2 vs测试结果:💯
-
- ▶️3.4.2 VS警告:💯
- ▶️3.5 题目5:💯
- ▶️3.5.1 题目5讲解:💯
- ▶️3.5.2 vs测试结果:💯
- ▶️3.6 题目6:💯
- ▶️3.6.1 题目6讲解:💯
- ▶️3.6.2 vs测试结果:💯
- ▶️3.7 题目7:💯
- ▶️3.7.1 题目7讲解:💯
- ▶️3.7.2 vs测试结果:💯
- ▶️4.总结💯
Hello,大家好呀!今天我们继续来讲解C语言指针相关笔试题!!!
在讲解之前,让来我们来回顾一下上次博客:C语言-数组&&指针笔试题讲解(1)-干货满满!!!
👇👇讲了什么来吧:
1.
strlen和sizeof的对比:
1.sizeof
是操作符。而strlen
是库函数,需要包含头文件string.h
。2.
sizeof
是计算操作数所占内存的大小,单位是字节。strlen
是求字符串长度的,统计的是\0
之前字符的隔个数。3.
sizeof
操作符是不关心内存中存放什么数据,而strlen函数是关注内存中是否有\0
,如果没有\0
,就会持续往后找,可能会越界。
2. 整型数组和字符数组和字符指针相关笔试题举例分析和讲解。
👉👉那今天博主会把剩下的二维数组和指针笔试题,以及指针运算相关笔试题全面进行讲解。
👇👇讲的内容如下图所示:
▶️1.预备知识:💯
在讲解二维数组和指针相关笔试题之前,我们先给大家讲一下二维数组在内存中的存储知识~
▶️1.1二维数组是怎么存储的?💯
其实它像一维数组一样,我们如果想研究二维数组在内存中的存储方式,是可以打印数组中所有元素。
代码如下:
#include <stdio.h>
int main() {
int a[3][5] = { 0 };
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("a[%d][%d]=%p\n",i,j, &a[i][j]);
}
}
return 0;
}
输出的结果:
分析代码: 从输出的结果来看,二维数组中每一行内部的每个元素都是相邻的,地址之间相差4个字节,跨行位置的两个元素 (如:
arr[0][4
]和arr[1][0]
) 之间也是差4个字节。
因此我们可以得出以下结论: 二维数组中的每个元素都是连续存放的。
另外,我们曾经在:C语言-指针讲解(3)
讲过:二维数组的数组起始可以看作是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。
如下图所示:
分析: 我们根据上图,可以知道第一行一维数组的类型就是
int[5]
,所以第一行的地址就是数组指针类型int(*)[5]
。
▶️1.1 总结💯
二维数组中的每个元素都是连续存放的。
好了,当我们介绍了这些前置知识后,那我们就开始讲解一下二位数组和指针相关的笔试题吧~
▶️2.二维数组和指针相关笔试题💯
接下来,我们来看一下二维数组和指针相关的笔试题~
题目如下:
//二维数组相关题目
#include<stdio.h>
int main() {
int a[3][4] = { 0 };
printf("%zd\n", sizeof(a));//1.输出结果是什么?
printf("%zd\n", sizeof(a[0][0]));//2.输出结果是什么?
printf("%zd\n", sizeof(a[0]));//3.输出结果是什么?
printf("%zd\n", sizeof(a[0] + 1));//4.输出结果是什么?
printf("%zd\n", sizeof(*(a[0] + 1)));//5.输出结果是什么?
printf("%zd\n", sizeof(a + 1));//6.输出结果是什么?
printf("%zd\n", sizeof(*(a + 1)));//7.输出结果是什么?
printf("%zd\n", sizeof(&a[0] + 1));//8.输出结果是什么?
printf("%zd\n", sizeof(*(&a[0] + 1)));//9.输出结果是什么?
printf("%zd\n", sizeof(*a));//10.输出结果是什么?
printf("%zd\n", sizeof(a[3]));//11.输出结果是什么?
return 0;
}
大家可以先思考一下这11道题的输出结果是什么,一会博主会进行讲解~
▶️2.1二维数组和指针相关笔试题讲解💯
如下图所示:
1.我们知道,sizeof(
数组名)是计算整个数组的大小。
那从上图,我们得知,这个二维数组一共有12个元素,并且每个元素占的是4个字节,那这里总共占了12*4个字节。所以它的大小是48。
2.从上图:我们知道a[0][0]
是第一行第一个元素,大小是4个字节。
如下图所示:
3.虽然这个二维数组在我们假想中是一个多行多列的形式。
但事实上我们刚刚就介绍过二维数组在内存中是连续存放的,也就是像上图这样存储。
我们可以把这个二维数组每一行看作是一个一维数组,然后我们给每一行的一维数组都起个名,分别是a[0]
,a[1]
,a[2]
。
所以说,这里a[0]
实际上就是第一行的数组名,然后这里的数组名单独放在sizeof
内部了,计算的是第一行的大小,而且第一行是有4个整型,所以它的大小是16个字节。
4.这里的a[0]是第一行这个数组的数组名。
但是这个数组名并非是单独放在sizeof内部,所以数组名表示数组首元素的地址,也就是a[0][0]的地址。
那a[0]+1是第一行第二个元素(a[0][1]
),是地址它的大小就是4/8
个字节。
5.从上题我们知道**a[0]+1
是第一行第二个元素(a[0][1]
)的地址。**
所以,*(a[0]+1)
拿到的就是第一行第二个元素,大小是4个字节。
如下图所示:
6.这里我们发现a没有单独放在sizeof
内部,没有&
,数组名a
就是首元素的地址,也就是第一行的地址。
所以a+1
,就是第二行的地址。它的大小也就是4/8。
这里可能有同学对此表示疑惑,为什么a+1
是第二行的地址呢?
因为a
是数组首元素的地址,而从上图,我们得知首行(第一行)是四个整型元素的数组,所以a
的类型为int ( * )[4]
,是个数组指针类型。
那我们想一想,如果a
是这个类型的话,+1是不是要跳过这个4个整型的数组啊。 就是把第一行跳过去指向第二行,如上图绿色部分显示。所以它的大小为4/8个字节。
7.因为我们知道a=int( * )[4]
,那a+1
=int( * )[4]
,它们的类型本质上都是个数组指针类型。
对一个数组指针进行解引用操作实际上就是访问一个数组的大小,是不是这个道理?对于一个数组指针+1跳过一个数组,对于一个数组指针+1就是得到一个数组。
所以sizeof(a+1)
这里的**a+1
**指向的是第二行的地址,那*(a+1)
就是第二行的元素,它的大小16。
这里还有第二种解读方式: 这里的*(a+1) ==a[1]
,因为a[1]
恰好是第二行的数组名,数组名单独放在sizeof
的内部,所以它的值也是16。
如下图所示:
8.我们之前讲过这个&数组名 取出的是整个数组的地址。
那同理:a[0]
是第一行的数组名,&a[0]
取出的是第一行的地址,那&a[0]+1
得到的就是第二行的地址。是地址的话大小就是4/8。
需要注意的是:这里的a+1
==&a[0]+1
,因为它们本质上都是第一行的地址+1,指向的是第二行的地址。
9.我们从上题可以得知:&a[0]
+1得到的就是第二行的地址。
那*(&a[0]+1)
就是指向的就是第二行,它的类型也是int ( * )[4]
,对其解引用得到的是第二行的元素,大小是16。
如下图所示:
10.这里的a
表示的是二维数组的数组名。
由于它这里没有单独放在sizeof
内部,也没有&
数组名。
所以数组名a
就是数组首元素的地址,也就是第一行的地址,* a
就是第一行的,所以它的大小就是16。
这里还需注意一下:* `a = = *(a+0) == a[0] 这三种表达的意思其实是等价的。
如下图所示:
11.如上图,这里的a[3]指的就是第四行,因为只有第四行才能表示成a[3],对不对?
那这里或许很多同学都会认为它这里会报错,实际上,它会不会报错呢?
实际上是不会的。
因为我们之前讲过,sizeof
去计算的时候,他压根不会计算表达式里面的值,它不会真实访问第四行的,所以不会存在越界访问。
a[3]
- -a[0]
,这个通过类型就可以推断出来的,是不是一回事呢,a[3]
– a[0]
这不就是表示第几行的数组名吗?数组名单独放在sizeof
的内部,它的大小就是16。,它的类型跟a[0]
是一样的。
▶️2.2 VS运行结果演示💯
通过上面的讲解分析,我们不妨拿VS来测试一下结果,看看分析的是否正确把~
这里我们分别以x64和x86环境来进行测试运行用例~
x86运行环境:
x64运行环境:
实际上,从vs运行的结果来看,我们对这11题代码分析的结果是没错的。
▶️2.3总结💯
这里我们通过讲解二维数组和指针相关笔试题,请
大家再次注意以下知识点的巩固:
1.sizeof
(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址,它的类型是一个数组指针类型。
3.除了上面两个例子外,其余的数组名都表示数组首元素的地址。
▶️3.指针运算笔试题解析💯
接下来我们将给大家讲解7道关于指针运算的笔试题。
▶️3.1题目1:💯
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?
大家可以先思考一下这道题的输出结果是什么,一会博主会进行讲解~
▶️3.1.1 题目1讲解:💯
如下图所示:
分析:根据上图,我们已经知道&a
是取出整个数组的地址。那&a+1
就是跳过整个数组的地址,也就是指向元素为5后面的地址
另外,我们知道这是一个整型数组,所以&a
是类型是int( * )[5]
,那(&a+1)
还是这个类型。
现在要把它的值赋给ptr
,因为ptr
的类型是int *
,所以我们要对(&a+1)
强制类型转换为int*,
然后再赋给ptr
,所以ptr
指向的也是元素为5后面的地址。
那我们再看: 由于ptr
是一个整型指针,整型指针+1是向后跳过一个整型,那-1呢?就是向前跳一个整型。
根据我们上面画的图,ptr-1
指向的是5的地址,因为它是个整型指针,那我们对其进行解引用,就能访问它里面元素的值,也就是5。
前面* (a+1)
输出结果也是同样的道理: 因为a
是数组首元素的地址,那a+1
就相当于跳过一个整型元素,指向的是第二个元素的地址,对其进行解引用的话,拿到的是数组第二个元素的值,也就是2。
▶️3.1.2 vs测试结果:💯
我们不妨用vs测试一下,看看我们分析的是否正确吧~
我们就以x64的环境演示一下吧:
▶️3.2 题目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);//1.输出结果是什么?
printf("%p\n", (unsigned long)p + 0x1);//2.输出结果是什么?
printf("%p\n", (unsigned int*)p + 0x1);//3.输出结果是什么?
return 0;
}
大家可以先思考一下这道题的输出结果是什么,一会博主会进行讲解~
▶️3.2.1 题目2讲解:💯
题目分析:这题本质上考察的就是指针运算中的指针±整数 。
并且我们发现这个指针是个结构体类型的,这个结构体是什么,我们压根不用关心,因为题目已经给出这个结构体的大小为20字节。
1.我们来看一下第一题+1到底跳过多少个字节呢?
这个其实是取决于它的指针类型,我们现在这里是个结构体的指针,所以它+1就是跳过一个结构体的大小,所以这里p+0x1,就相当于加了个20字节。
需要注意的是:这里+1得到的不是100020
,因为它这个0x
本质上是个16进制的数字,那加20之后就变成100014
。
具体换算过程如下:
2.这里需要注意的是: 很多同学误以为这里整型+1是跳过一个元素,实际上是错的,为什么呢?接下来我给大家详细解释一下~
分析: 这里的结构体指针类型被强制转换为unsigned long
,它就不是一个指针类型了。因为我把p
转换为unsigned long
类型,让它是一个无符号的整型,整型+1加几?
比如说:500+1
它的结果是多少,501
还是504
?就是501
吧。
因为这玩意不是指针,我们说只有指针+1才想着跳过1个元素,但现在我是个unsigned long
,是整型啊,整型+1就是+1。因为只有整型指针+1我才跳过一个整型元素的大小,一个结构体指针+1我才跳过一个结构体,一个字符指针+1我才跳过一个字符,而现在我是unsigned long
,+1就是+1。它只是简单地将地址增加一个字节。
所以打印结果就是100001
。
3.这题我们发现,我们这是把这个结构体指针强制类型转换为unsigned int *
,那整型指针+1是加几?
是不是加4,所以就变成100001
。所以最终打印结果就是1000001
。
▶️3.2.2 vs测试结果:💯
我们不妨用一下vs测试一下运行结果看看
x86运行结果:
这里或许有同学对于前面为什么要加上00而产生疑惑?我们就简单讲一下吧~
我们发现前面加了个00,前面的地方加了个
00
,打印的时候00
是用%p
,%p
前面即使前面是0
它也会打印出来的,不会省略的。
因为我们这里是x86
环境,一个指针要打够32个bit
位,我们之前这篇博客:C语言的操作符讲解(上)讲过32
个bit
位等于8
个十六进制位,所以打印的时候,前面会加上00
。
然后我们又知道一个十六进制位是等于4
个二进制位,所以8
个十六进制位就等于32
个二进制位。
▶️3.3 题目3:💯
#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;
}
大家可以先思考一下这道题的输出结果是什么,一会博主会进行讲解~
▶️3.3.1 题目3讲解:💯
如下图所示:
解读题目: 这道题可能很多人以为这个二维数组的初始化的值如上图所示。
但其实这并不是的,因为我们发现这个地方有个小圆括号,小圆括号括起来就叫逗号表达式。
它的具体用法如上图所示~
虽说我们看着这个数组初始化了一堆数字,但其实它就初始化了3个数字。具体如下图所示:
解答题目: 我们接着往下看,发现这个p
是个整型指针。
然后a[0]
是二维数组的首行的数组名。那这里的数组名有代表数组首元素的地址的话,也就是元素为1的地址,所以这写a[0]
,实际上就是a[0][0]
的地址,所以p就是妥妥的指向1的地址。
然后接着看下面那个输出结果为p[0]
。 这个p[0]
==*(p+0)
,这两个是等价的。 而因为这里的p+0
是没加的,还是指向1
的地址,我们对其进行解应用操作,访问就是里面数组元素1
,因此它的输出结果就为1
。
▶️3.3.2 vs测试结果:💯
我们不妨用一下vs测试一下运行结果,看看我们分析的是否正确吧~
这里我们就以x64环境演示吧:
通过运行结果发现,我们分析的是没错的~
▶️3.4 题目4:💯
//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
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;
}
大家可以先思考一下这道题的输出结果是什么,一会博主会进行讲解~
▶️3.4.1 题目4讲解:💯
如下图所示:
画图分析: 我们知道这个二维数组是五行五列的,因此我们我们就把它画出来。并把二维数组的每行分别以a[0]
-a[4]
这样标出来。
接着往下看,我们发现p
是一个指针,它指向一个四个整型元素的数组。
接着往下看: 我们发现它是把a
的首元素的地址交给p
,p
也就指向a[0][0]
的地址。
接着看下图:
从上图中: 或许有同学有疑问为什么p[4][2]
和a[4][2]
的地址分别放在这两处? 接下来我将细细讲解~
1.首先那个a[4][2]
的地址其实是二维数组的第五行第三个元素的地址。具体的指向位置就是上图蓝色填充部分。
2.同样地: 我们知道p
是一个指针,指向一个四个整型元素的数组,所以对p+1
,就跳过四个整型元素嘛。
如果我们把指针变量p
想象成二维数组的一行,那p[4][2]
指向哪里呢?
如果我们按照刚刚那个想法,p
是指向一个数组的话,+1
跳过一行,再+1
跳过一行,再+1
跳过一行,再+1
跳过一行。
就相当于它跳了4行,指向第五行第三个元素的位置。具体的指向位置就是上图蓝色填充部分。
如下图所示:
1.接着往下看,既然我们已经知道这两个地址的指向,那这里&p[4][2]
- &a[4][2]
本质上就是考察我们指针-指针的结果是多少。
我们之前这篇博客:C语言指针详解(一)超详细~
介绍过指针-指针得到的是指针之间的元素个数,这里我们发现它们之间的元素个数为4个,但是从图中,我们也可以看出来这个p[4][2]
的地址是小于a[4][2]
的地址。
所以这个如果我们以%d的形式打印出来的话,输出结果为-4。
2.需要注意的是: 这里以%d
的形式打印和以%p
的形式打印是截然不同的。为什么呢?
因为我们要知道,-4
它要存到内存存的是补码,内存里面只以%p
的形式打印我们认为存的是地址,地址是不存在原反补的概念。地址可以理解成一个无符号数的。
所以内存中存的是补码就直接当成地址被翻译出来了,但如果%d
打印的话是打印出它的原码出来。
可能有同学忘记了-4的补码是怎么写了,没关系,我们直接看下图~
如果内存中存放的是它的补码,大家想象一下,那我以%p
的形式打印的时候,我们就认为它
内存里面存放的是地址,这个是不需要求它的原码出来的,%p
就是认为它是地址,直接把它打印出来就可以的。
又因为%p
格式化字符打印一个指针时,它会以十六进制的形式打印指针的值。
也就是说我们把-4的补码转换为十六进制打印出来。具体转换参考下图:
所以最终它的输出结果为FFFFFFFC
。因此以%p
打印出来的结果为FFFFFFFC
。
而如果是%d
打印的话,就把补码还原成原码,就是-4嘛。
▶️3.4.2 vs测试结果:💯
我们不妨用vs测试一下运行结果,看看我们分析的是否正确。
从图中,我们发现vs输出结果跟我们分析的结果是一样的,因此是没毛病的~
▶️3.4.2 VS警告:💯
如下图所示:
可能有同学看到vs有警告我们来解释一下:
a
这个数组名他表示首元素地址的话,它是第一行这个地址,是五个整型的这个数组。如果强行赋给p
的话,两边的类型是不是有差异的。所以编译器这里报出一个警告,但是如果你强行赋就赋过去呗对吧,a
作为地址会被强制地址p
的这个类型,你使用p
,就用p
的这个视角去走,p
就一次加4
个整型,就是这么一个道理啊。
总结: 我们用不同的指针在走的时候,你看,你拿a在访问的时候,一行五个元素,一行五个元素,但如果这里是一个数组指针指向四个元素的话,它加1是跳过四个元素,是不是这样的逻辑呀~
▶️3.5 题目5:💯
#include <stdio.h>
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;
}
大家可以先思考一下这道题的输出结果是什么,一会博主会进行讲解~
▶️3.5.1 题目5讲解:💯
如下图所示:
分析题目: 我们根据发现aa
数组是一个二行五列的二维数组,*我们也将它的图给画出来。具体看上图。
接着我们往下看,&aa
是取出整个二维数组的地址,那&aa
指向的是第一行首元素的地址。
那aa+1
它就把整个二维数组都跳过去了,也就是指向10
的地址后面的地址,跳过去之后,然后强转为整型指针int *
再把它赋给ptr1
,说明ptr1
也是指向那个位置的。
那ptr1-1
呢?它是个整型指针,向前挪动一个整型,是不是指向10
的地址。那对其进行解引用,访问的是不是10的元素。
所以*(ptr-1)
的结果就是10。
接着往下看: 我们发现aa
是数组首元素的地址,首元素地址就是第一行的地址,也就是aa[0][0]
的地址。
那aa+1
就跳过一行了,因为是数组名是数组首元素的地址,也就是第一行的地址,第一行的地址+1
就是第二行的地址。
然后第二行的地址解引用,是不是拿到第二行了,其实就相当于第二行的数组名。这里的 * (aa+1)
==a[1]
,a[1]
不就相当于拿到第二行吗?相当于拿到第二行的数组名。
所以这个地方a[1]
或者 * (aa+1)
得到的
虽然是数组名,但它没有sizeof
,又没有单独&,又没有单独放在sizeof
内部,所以数组名表示首元素地址,是首元素地址代表的是第二行第一个元素的地址,因为aa[1]
是第二行的地址,那数组名代表首元素的地址,就是aa[1][0]
的地址,赋给ptr
,ptr
就是指向6的地址。
需要注意的是:* (aa+1)
旁边强制类型转换为整型指针,这个是没有意义的,因为它本身就是首元素地址。
这个地方强制类型转换就是迷惑你的,因为两边类型一样了,所以它这个强制类型转换是没有意义的。
接着往后看,那个ptr2
作为一个整型指针,向前挪动一个整型,指向的是5的地址,解引用,访问的就是5了,所以它最终的输出结果为5。
▶️3.5.2 vs测试结果:💯
我们不妨用vs测试一下运行结果,看看我们分析的是否正确。
这里我们以x64环境来演示一下:
总结:我们发现这一道题其实也不是很难,重点还是要对数组名的理解,唯有把这些知识点理解透彻了,我们解这种类型的题才能迎刃而解。
▶️3.6 题目6:💯
//#include <stdio.h>
//
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);//输出结果是什么?
return 0;
}
大家可以先思考一下这道题的输出结果是什么,一会博主会进行讲解~
▶️3.6.1 题目6讲解:💯
如下图所示:
分析题目:1. 从上图,这里面我们放了几个字符串,数组a的每个元素是char *
啊,说明这是一个字符指针的数组啊!
我们要知道字符串作为表达式,它的值是不是首字符的地址啊,所以这里给work,at,alibaba这三个字符串的时候,那我们这里是不是把work
的w
地址存到里面去,at
的a
地址存到里面去,alibaba
的a
地址存到里面去,是这个道理吧。
需要注意的是: 这是一个char*
的数组,它的每个元素都是char*
的,所以它才能存w的地址,a
的地址,a
的地址,a是数组名,数组名表示数组首元素的地址,也就是存的是w
的地址。
那pa
就用char **
的地址来接收啊,所以pa
存的是字符指针数组w
首元素的地址。
接着往下看,pa++
就相当于跳过一个字符指针数组的元素,指向的是a
的地址是不是,对其进行解引用拿到char *的元素为a
。并以%s
打印的话,最终它的输出结果为at
。
▶️3.6.2 vs测试结果:💯
我们不妨用vs测试一下运行结果,看看我们分析的是否正确。
总结: 这道题本质上就是考察字符指针数组以及二级指针的相关用法,大家要把这种题理解透彻才行。
▶️3.7 题目7:💯
#include <stdio.h>
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);//1.输出结果是什么
printf("%s\n", *--*++cpp+3);//2.输出结果是什么
printf("%s\n", *cpp[-2]+3);//3.输出结果是什么?
printf("%s\n", cpp[-1][-1]+1);//4.输出结果是什么?
return 0;
}
这一道题可能比较难,所以大家不用做,仔细听一下博主是怎么分析这道题的。
▶️3.7.1 题目7讲解:💯
题目分析:在讲解这4道题目之前,我首先将这三行代码通过注释和画图的方式解析一下~
注释:
char *c[] = {"ENTER","NEW","POINT","FIRST"}; /*这个c字符指针数组本质上存的是字符串中首字符的地址,比如:它分别指向数组中这四个字符串中的首字符地址。 比如这个c指向字符串中ENTER的首字符地址E,指向字符串中NEW的首字符地址N,指向字符串POINT的首字符地址P,指向字符串中FIRST的首字符地址F。 另外这个数组它的每个元素是一个char*的内容*/ char**cp[] = {c+3,c+2,c+1,c}; /*接下来往下看,它又给了个数组,数组中的每个元素分别是c+3,c+2,c+1,c。这个数组的每个元素是char **, 而这些数组元素c的首元素地址是char *的地址,是一级指针的地址,那它的类型是不是char **啊,所以这里我们用二级指针地>>址来接收它。*/ char***cpp = cp; //接着我们看,这里数组名代表首元素的地址,就是char **的地址,那我们就要拿个三级指针变量char ***来接收它,这个指针变量叫cpp,它的类型是char ***,它里面放着是cp,cp是这个cp数组的数组名,数组名表示数组首元素的地址,所以它也是指向c+3元素的地址,
根据我们上面三行代码写的注释,那我们也能把它对应的指针所指向的内存图给画出来。
如下:
大家可以先看一下博主画的这个指针所指向的内存图,自己尝试理解消化一下。
好了,如果大家已经理解博主上面画的图,那我们就要对这四道表达式进行计算了,那博主就依次解答这四道题吧。
1.我们先看第一道题,它的是以%s
的形式来输出**++cpp
的值的。那我们知道++
这个运算符优先级是比**
的运算符要高的。
因此这个表达式会优先算++
,再算**
。那我们再看:++cpp
这个++
是不是有副作用的。比如看下面这个例子~
从上面的例子我们可以知道,++a
的意思就相当于a=a+1
,它是会让a变化的,那++cpp呢?我们继续看图:
从上图,我们更能直观地看出:
1.cpp
原本放着这个这个地址,假设起始地址为0x0012ff40
,如果我们对它++
,cpp++
,也就是cpp+1
,也就是跳过一个char**
元素的大小,也就是把c+3
这个元素跳过去了,也就是指向c+2
元素的地址。
所以++cpp
就让cp
不再指向c+3
首元素的地址了,而是指向第二个元素c+2
的地址了。也就是指向下面的空间去了。当我们++
完之后,先解引用一层,我们通过解引用,找到的是c+2
的元素。
然后我们再解引用,通过对c+2
解引用找到的是char*
的值,它里面存的是p
的地址。然后p
的地址我们以%s
的形式打印,最终它的输出结果就为point
。
再次强调一下: 这一次程序走的过程中++cpp
确实是让cpp
变了,cpp
的值就不再指向cp
数组首元素的地址(c+3)
了,而是指向cp
首元素地址跳过一个元素的地址,也就是指向c+2
元素的地址。
因此我们下次我们用cpp
的话,要从c+2
这个地址开始。
2.首先,我们先解读一下这个表达式*--*++cpp+3
。
当我们再次执行这个表达式的时候,我依然是先执行++cpp
,因为+
优先级比较低,所以这个表达式运算顺序是++
先算,++
旁边的那个*
先算,--
先算,--
左边的那个*
先算,最后才是+
先算。
然后,我们再仔细分析这道题,我们先给大家一个画个图给大家看看这个表达式的运算逻辑,然后再进行讲解~
我们看图中绿色框框的部分: cpp
原本指向c+2
的地址,那++cpp
,这一次指向也就相当于断了,就相当于跳过一个元素,指向的是c+1的地址。那++
之后解引用找到的是c+1的元素。然后进行--
的操作,--
就是让这里的值-1
,这里面本身就是放c+1
,
我-1
之后这里就变成c
了,如果这里变成c
之后,刚刚这种指向就不存在了,因为它现在是c
的元素,我们通过对c
解引用找到的是char*
的值,也就是它里面存放的是E
的地址。
而E
的地址+3
,我们知道+0
开始指向E
,+1
开始指向N
,+2
指向T
,+3
指向E,因为我们刚刚拿到是E的地址啊,那E
的地址+3
是不是跳过3
个元素指向第二个E
,那从E
这里以%s
的形式打印出来了,打印出来的值是不是ER
。
总结: 有没有发现这些题还蛮坑的,前面错了,后面也跟着错。
因为前面错了是会影响后面的,++
或--
操作都会让指针的指向的内容有所改变。所以前面做错后面都会做错,因此我们计算这种题要细心一点才行。
3.这里可能有些同学对于这个表达式:*cpp[-2]+3
有点懵,我们来给大家解读一下。
如下图所示:
从图中,我们可以直观地看出来,这个cpp[-2]
不是相当于*(cpp-2)
,又因为前面还有一个*
,再加3
。
所以这个cpp[-2]+3
这行代码可以转换成:**(cpp-2)+3。
这两个表达式本质上是一样的。
另外,我们通过作图的方式把它这个表达式运算的逻辑搞了出来,具体如下:
从上图: 我们知道cpp
原本是指向c+1
元素的地址,-1
指向c+2
的地址,-2
指向的是c+3
的地址。
它指向的是cp
数组中首元素的地址,得到的就是c+3
的地址。
需要注意的是 :这个cpp-2
那个cpp
指向的对象是不变的,只是说它这个表达式得到的是c+3的地址,那之后解引用一次,通过c+3
的地址,拿到的是不是c+3
的内容?
然后前面又有一个*符号,再解引用一次,找到的是不是char*
的值,这里面刚好是FIRST
的内容,里面存放的是F的地址。
然后后面+3
,就是跳过3
个元素,是不是刚好指向里面S的地址,如果以%s
的形式打印的话,最终的输出结果是ST。
4.这里的cpp[-1][-1]
,这里可能有同学对于这个表达式也是懵懵懂懂的,因此我们要把这个表达式转换成解引用的形式先。
如下图:
首先呢,我们先把第一个[-1]
写成*(cpp-1)
,然后第二个[-1]
就是整体-1
之后再解引用。也就是写成*(*(cpp-1))
,然后再+1
,就是写成*(*(cpp-1))+1
的形式。
那这个表达式的运算逻辑是怎么样呢?接下来我将以作图的方式来细细讲解一下。
这里我们再次强调一下: 刚刚cpp[-2]
的时候,cpp
这个动作是没变的,cpp
还是指向(c+1)
的地址。
从图中绿色箭头以及圆圈所示: (cpp-1)
产生的是这个表达式的地址,这个表达式指向的就是c+2
的地址,这里面的地址先解引用,拿到它里面的值c+2
。里面存放的是c
数组中的第三个元素char *
的地址,那它-1
,拿到的是不是c的数组中第二个元素char * 的地址,然后我们在外层再进行解引用操作,拿到的是不是第二个元素char *
的值,拿到它里面的值是N的地址啊。
它指向N的地址,那+1是不是相当于跳过一个元素,它指向E,所以如果以%s
的形式打印出来就是EW。
▶️3.7.2 vs测试结果:💯
好了,分析了那么多了~
不妨我们用VS测试一下,看看运行结果是否跟我们分析的一样吧。
X64运行环境:
我们发现VS的运行结果跟我们分析的是一样的,没有任何的问题。
总结: 通过我们这道题,我们发现这个题代码是环环相扣的,考得都是指针的运算,指针进行解引用操作的相关运算。
▶️4.总结💯
好了,现在博主已经把所有指针的笔试题讲完了。
👇👇让我们再次回顾这次博客讲了什么吧。
1.二维数组中的每个元素都是连续存放的。 二维数组的数组起始可以看作是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。
2.二维数组相关笔试题和讲解
3.指针运算题和讲解
另外,至此为止:我们就把指针的知识点以及相关题目全部讲完了,大家如果有遗忘的知识点,可以翻看博主以前的博客,里面有对指针知识点和题目进行详细的讲解。
最后,如果大家觉得博主这次博客有讲得不好或者不清楚的地方,欢迎私信或者评论区指出。
** 如果觉得博主讲得不错,对你学习指针方面的知识有帮助。**
** 可以给博主一个小小的关注,一键三连吗,谢谢大家!!! **
版权声明:本文为博主作者:Keven-zhou原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/m0_63564767/article/details/135258159