指针练习题进阶版【2】(内有精美逐题图示哦)

引言

在进行指针题目的练习之前,我们再先来回顾一下以前学习的关于数组和指针的知识:

数组名的含义

数组名是首元素地址。但是在两种情况下数组名代表整个数组:1、数组名单独放在sizeof后的括号内,表示计算整个数组的大小;2、数组名单独放在&后面,表示取出整个数组的地址。

数组指针

数组指针的类型是该指针指向的数组类型加上*。例如数组int nums[4]的类型是int [4],指向该数组的数组指针的类型就是int(*)[4]。

详细讲解可参考博客:数组指针与函数指针(让你从此爱上指针)

指针

指针类型决定着指针的步长(指针加减1时所移动的距离)和指针解引用是所能够访问到的内存的大小。
指针的大小与指针类型无关。在32位机器上是4字节,在64位机器上是8字节。

可大致可参考博客:初识C语言

指针笔试题

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

数组a中有五个元素,每个元素都是int型的。
数组名a单独放在&后,表示整个数组。取出的是整个数组的地址,是一个int(*)[5]的数组指针。
&a+1就向后移动一个int[5]型数组的大小也就是4*5=20个字节,指向的是数组后的一块大小为20个字节的空间。
再给数组指针&a+1强制类型转换为int*型,并赋给一个整型指针ptr。指向是数组后的一块大小为一个整型也就是4字节的空间。

打印时:
*(a+1)中,a是首元素地址,类型是int*。a+1向后移动一个int型的大小即指向数组第二个元素。再解引用就是数组第二个元素2;
*(prt-1)中,ptr是一个指向数组后一个int型大小的空间的指针,类型是int*。ptr-1向前移动一个int型的大小即指向数组最后一个元素。再解引用就是数组最后一个元素5;
图示

运行结果

2

//程序的结果是什么
//由于还没学习结构体,这里告知结构体的大小是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;
}

这道题创建了一个结构体类型struct Test,并创建了一个指向这个结构体类型的结构体指针,类型为struct Test*。并假设这个指针变量的值为0x100000。

打印时:
p是一个struct Test*型的结构体指针,p加1时指针向后移动一个struct Test结构体的字节数,即20字节。故p+1就是p的值加20,用十六进制打印就是0x100014;
将指针p强制类型转换为unsigned long,也就是一个无符号的长整型,表示的就是0x10000这个十六进制数。给这个十六进制数加一就是0x100001;
将结构体指针p强制类型转换为unsigned int*,即整型指针。p加1时指针向后移动一个int的字节数,即20字节。故p+1就是p的值加4,用十六进制打印就是0x100004
图示
由于编译器运行时结构体指针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;
}

a是一个数组,数组中有4个int型的数据,数组的类型是int [4]。

语句一中:
数组名单独放在&后,表示整个数组。&a就是整个数组的地址,类型是int(*)[4]。&a+1向后移动一个int[4]类型数组的长度,即4*4=16个字节,指向的是数组a后的一块大小为16字节的空间。
再给这个数组指针强制类型转换为int* ,赋值给一个整型指针ptr1。这个整形指针指向的就是数组a后的一块大小为一个int即4字节的空间。

语句二中:
数组名a表示首元素地址,将这个地址强制类型转换为int,就表示一个整数即首元素地址的值。给这个值加1就相当于下一个字节的地址的值。
再把(int)a + 1强制类型转换为int*,并赋值给一个整型指针ptr2。这个整形指针指向的就是数组首元素地址向后偏移一个字节所指向的一块大小为一个int,即4字节的空间。

打印时:
ptr1[-1]就相当于*(ptr-1)。ptr是一个整型指针,指向的是数组后面的一块大小为4的空间。ptr-1指向的就是数组的最后一个元素。再解引用就得到了数组的最后一个元素4。打印。
*ptr2打印的就是这个整形指针指向的就是数组首元素地址向后偏移一个字节所指向的一块大小为4字节的空间里的内容。在小端字节序存储下(高位放在高地址处,低位放在低地址处)整型数组a的每一个元素都是倒着放到内存中的。即01 00 00 00 02 00 00 00(十六进制存储) …取出时再倒着取出。数组首元素地址向后偏移一个字节所指向的一块大小为4字节的空间里的内容就是00 00 00 02。将这个数据再倒着取出就是20 00 00 00(十六进制)。再按十六进制打印就是20000000。
图示

运行结果

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

在这段代码中定义了一个二维数组a[3][2],这个二维数组有三行,每一行有两个元素,类型都是int型的。在初始化这个二维数组时,只用三个逗号表达式初始化了3个元素,其余元素被默认初始化为0。逗号表达式的值是逗号表达式中最后一个表达式的值,所以这个二维数组的元素为1、3、5、0、0、0。
接着又创建了一个int*型的指针变量p。
a[0]就相当于*(a+0),a是二维数组首元素的地址,也就是第一个一维数组的指针,是类型为int(*)[2]的数组指针。对这个数组指针解引用就是第一个一维数组。这个一维数组没有&,也没有单独放在sizeof内部,表示的是首元素的地址,也就是这个一位数组第一个语速的地址,类型是int*。并将其赋给整型指针p。

打印时:
p[0]本质上就是*(p+0)。就是*p,取出整型指针p所指的元素,也就是二维数组中第一个一维数组的首元素1。
图示

运行结果

5

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

这段代码创建了一个二维数组a,有5行5列,共25个int型的元素。
又创建了一个数组指针,这个数组指针的类型是int(*)[4],指向的是一个int [4]型的数组,这个数组有4个int型的元素。
二维数组名a是首元素地址,即第一个一维数组的地址,类型是int(*)[5]。这个数组指针与p的类型不相符,但是都是指针,要强行赋值不影响结果。对于数组指针p,就相当于数组a被分为每行4个元素的二维数组。

打印时:
p[4][2]就相当于*(*(p+4)+2)。p是数组指针,类型是int(*)[4],这个数组指针加4,就是向后移动4个int[4]型数组的长度即4*4*4=64个字节,指向的就是对于p来说的第5个一维数组的指针。解引用,得到的就是这个一维数组。一维数组数组名是首元素地址,类型是int*型,指向第五个一维数组的首元素。这个整型指针加2,向后移动2个int型的大小,即8个字节,指向的是第五个一维数组的第三个元素。再解引用,得到的就是这个整形型变量。
a[4][2]就相当于*(*(a+4)+2)。a是数组指针,类型是int(*)[5],这个数组指针加4,就是向后移动4个int[5]型数组的长度即4*4*5=80个字节,指向的就是对于a来说的第5个一维数组的指针。解引用,得到的就是这个一维数组。一维数组数组名是首元素地址,类型是int*型,指向第五个一维数组的首元素。这个整型指针加2,向后移动2个int型的大小,即8个字节,指向的是第五个一维数组的第三个元素。再解引用,得到的就是这个整形型变量。
图示
一个小的指针减一个大的指针,结果是-指针间隔的元素个数。p[4][2]减a[4][2]的结果就是-4。
-4在内存中以补码的形式存储。-4的原码为:10000000 00000000 00000000 00000100,原码符号位不变其他位按位取反得到反码:11111111 11111111 11111111 11111011,反码加1得到补码:11111111 11111111 11111111 11111100。
%p打印这个补码就是按照十六进制的地址形式直接打印,即FF FF FF FC;
%d打印这个补码需要再化为原码再打印,即-4。
运行结果

6

//程序的结果是什么
#include<stdio.h>
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));//语句2
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}

在这段代码中,首先创建了一个二位数组aa,这个二维数组有2行5列,共10个int型的元素。

语句1:
二维数组名aa单独在&后,表示整个数组。&aa就是整个二维数组的指针,类型为int(*)[2][5]。&aa加1,向后移动的就是一个类型为int(*)[2][5]的二维数组的大小,即4*2*5=40个字节。指向的就是二维数组aa后的一块大小为40字节的空间。
再将其强制类型转换为int*型的整型指针。再赋值给整型指针ptr1。指向的就是二维数组aa后的一块大小为一个int,即4个字节的空间。

语句2:
二维数组名aa表示首元素地址,即二维数组中第一个元素的地址,类型为int(*)[5]。加1,向后移动一个类型为int [5]的一维数组的大小,即4*5=20个字。节,指向的是二维数组中的第二个一维数组。再解引用,就是第二个一维数组(相当于第二个一维数组的数组名)。一维数组名代表其首元素地址,这个地址的类型是int*。将其强转为int*(这里的强转没有什么意义)后赋值给一个整型指针ptr2。

打印时:
ptr1指向的是二维数组aa后的一块大小为一个int,即4个字节的空间。ptr1-1向前移动一个int的大小,即4个字节,指向的就是二维数组中第二个一维数组的最后一个元素。解引用就得到了这个元素10;
ptr2指向的时二维数组aa中第二个一维数组的第一个元素。ptr2-1向前移动一个int的大小,即4个字节,指向的就是二维数组中第一个一维数组的最后一个元素。解引用就得到了这个元素5。
图示
运行结果

7

//程序的结果是什么
#include <stdio.h>
int main()
{
	char *a[] = { "work", "at", "alibaba" };
	char**pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

这段代码中,将3个常量字符串存在一个字符指针数组a内。(常量字符串的类型是char*,指向的是常量字符串的首元素)
数组名a是首元素的地址,类型是char**。并赋值给一个二级字符指针pa。
pa自增1,向后偏移一个char*类型的大小,即4或8个字节。指向的是字符指针数组a的第二个元素。

打印时:
对pa解引用,得到的就是字符指针数组的第二个元素,类型时char*。%s打印这个字符指针,即打印第二个常量字符串at。
图示

运行结果

8

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

在这段代码中,字符指针数组c中存放了4个常量字符串。
数组名c是首元素地址,即第一个常量字符串的地址,类型为char**。c+3就是这个二级字符指针向后移动3个char*类型的大小,即3*4=12个字节,指向的是第4个常量字符串;c+2就指向的是滴3个常量字符串;c+1指向的就是第2个常量字符串。
又定义了一个二级字符指针cp数组来存放这些二级字符指针。
cp是二级字符指针数组名,表示的是首元素地址。即一个二级字符指针的地址,即三级字符指针,类型为char***。并将其赋给一个三级字符指针cpp。
图示
语句1:
cpp是三级字符指针,前置++cpp,即先自增1再进行运算。cpp自增后向后移动char**个字节,指向的是二级字符指针数组cp的第二个元素。
*++cpp就是二级字符指针数组cp的第二个元素c+2,类型为char**。指向的是字符指针数组的第三个元素,即常量字符串"POINT"的首元素地址。
对这个二级字符指针再解引用,就得到了常量字符串"POINT"的首元素地址。
%s打印这个常量字符串的首元素地址,即从这个地址指向的元素打印到’\0’停止。即打印POINT
1

语句2:

cpp是三级字符指针,前置++cpp,再自增1。cpp自增后向后再移动char**个字节,指向的是二级字符指针数组cp的第三个元素。
*++cpp就是二级字符指针数组cp的第三个元素c+1,类型为char**。指向的是字符指针数组的第二个元素,即常量字符串"NEW"的首元素地址。
*++cpp再自减1,向前移动一个char*类型大小个字节。指向的是指向的是字符指针数组的第一个元素,即常量字符串"ENTER"的首元素地址。
–*++cpp再解引用,就得到了常量字符串"ENTER"的首元素地址。类型为char*。
这个字符指针再加3,向后移动3个char类型的大小,即3个字节,指向的就是常量字符串"ENTER"的第4个元素的地址。
%s打印这个常量字符串的第4个元素地址,即从这个地址指向的元素打印到’\0’停止。即打印ER;
2

语句3
cpp[-2]本质上就是*(cpp-2)。在前面两个语句中,三级字符指针cpp经过了两次自增,指向的是指向的是二级字符指针数组cp的第三个元素。给这个三级字符指针减2,指向的就是二级字符数组指针cp的第一个元素。再解引用就是二级字符指针的第一个元素c+3,类型是char**,指向的是字符指针c的第四个元素。
再对这个指向的是字符指针c的第四个元素的char**类型的二级字符指针解引用,就是字符指针数组的第四个元素,即常量字符串"FIRST"的首元素地址,类型为char*。
再给这个常量字符串"FIRST"的首元素地址加3,向后移动3个char类型的大小,即3个字节,指向的就是常量字符串"FIRST"的第4个元素的地址。
%s打印这个常量字符串的第4个元素地址,即从这个地址指向的元素打印到’\0’停止。即打印ST;
3

语句4:
cpp[-1][-1]本质上就是*(*(cpp-1)-1)。三级字符指针cpp指向的依旧是指向的是二级字符指针数组cp的第三个元素。给这个三级字符指针减1,指向的就是二级字符数组指针cp的第二个元素。再解引用就是二级字符指针的第二个元素c+2,类型是char**,指向的是字符指针c的第三个元素。
给这个二级字符指针再减1,向前移动一个char*,指向的就是字符指针数组c的第二个元素。再对这个二级字符指针解引用就是字符指针数组c的第二个元素,即常量字符串"NEW"的首元素地址,类型为char*。
再给这个常量字符串"NEW"的首元素地址加1,向后移动1个char类型的大小,即1个字节,指向的就是常量字符串"NEW"的第2个元素的地址。
%s打印这个常量字符串的第2个元素地址,即从这个地址指向的元素打印到’\0’停止。即打印EW;
4
运行结果

总结

到此,指针习题的内容就结束了。

如果对本文有任何问题,欢迎在评论区进行讨论哦

希望与大家共同进步哦

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿qiu不熬夜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值