带你刷笔试关的小怪|详解指针习题和面试题【C语言/指针/进阶】

简介: 带你刷笔试关的小怪|详解指针习题和面试题【C语言/指针/进阶】

前言

通过前面的学习,已经对各类指针有所了解,要想掌握C的利器——指针,做题能让我们快速掌握指针的用法。下面将详解指针习题(作为复习)和指针面试题(重点)。

友情链接:详解指针【上】详解指针【中】详解指针【下】(时间紧的话就看上篇吧,思路解析是最详细的)

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

复习回顾

  1. sizeof()计算的是对象所占内存的大小(单位:字节),而与它指向的东西无关。即sizeof(指针)就是指针的大小,和它指向的东西的大小无关
  2. 数组名一般指首元素地址,它表示为整个数组有两种情况:① sizeof(数组名)、② &数组名。对于第一种情况,括号内只能是数组名才表示整个数组。

一维数组

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//4*4=16 ①
printf("%d\n",sizeof(a+0));//4/8(32/64位)
//a+0是第一个元素的地址,地址大小是4/8字节
printf("%d\n",sizeof(*a));//4
//a是首元素地址,*a是第一个元素的大小
printf("%d\n",sizeof(a+1));//4/8(32/64位)
//同二
printf("%d\n",sizeof(a[1]));//4
//同三
printf("%d\n",sizeof(&a));//4/8 ② 不要认为是16
//*****&数组名也是一个地址,地址大小是4/8字节*****//
printf("%d\n",sizeof(*&a));//16 ①
//对地址解引用,得到整个地址,*和&在一起有抵消的效果
//此时*&a和a等价,本语句和第一条等价
printf("%d\n",sizeof(&a+1));//4/8 ② 不要认为是16
//*****&a是数组地址,对地址加1,跳过一个数组,还是地址,只是不指向a数组了
//地址大小是4/8字节*****//
printf("%d\n",sizeof(&a[0]));//4/8 下标为0的元素的地址
printf("%d\n",sizeof(&a[0]+1));//4/8 下标为2的元素的地址

字符数组

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr+0));//4/8 下标为0的地址
printf("%d\n", sizeof(*arr));//1 首元素的大小
printf("%d\n", sizeof(arr[1]));//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 下标为1的元素的地址
char arr[] = {'a','b','c','d','e','f'};// a, b, c, d, e ,f
printf("%d\n", strlen(arr));//随机值 因为strlen()遇到'\0'才停止
printf("%d\n", strlen(arr+0));//同上
printf("%d\n", strlen(*arr));//*arr是'a',strlen()认为
//它的ASCII值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));//随机值 地址跳过了一个元素

弄懂了前面的代码,试着分析一下吧:

//字符串常量以字符数组的形式存放在内存中
char arr[] = "abcdef";//a,b,c,d,e,f,\0
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr+0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(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
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//6
printf("%d\n", strlen(*arr));//错误
printf("%d\n", strlen(arr[1]));//错误
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr+1));//随机值 跳过了整个数组,跳过了'\0'
printf("%d\n", strlen(&arr[0]+1));//5 从第二个元素开始

体会

  1. 操作符sizeof()只和它的对象的内存大小有关,和对象指向的东西无关
  2. 库函数strlen(),求字符串长度只能将地址传入,统计'\0'之前的元素个数
//字符串常量以字符数组的形式存放在内存中
char *p = "abcdef";//指针变量p
printf("%d\n", sizeof(p));//4/8 指针变量大小
printf("%d\n", sizeof(p+1));//4/8 'b'的地址
printf("%d\n", sizeof(*p));//1 'a'
printf("%d\n", sizeof(p[0]));//1 'a'
printf("%d\n", sizeof(&p));//4/8 指针变量p的地址
printf("%d\n", sizeof(&p+1));//4/8 跳过一个数组的地址
printf("%d\n", sizeof(&p[0]+1));//4/8 'b'的地址
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p+1));//5
printf("%d\n", strlen(*p));//错误
printf("%d\n", strlen(p[0]));//错误
printf("%d\n", strlen(&p));//**随机值 指针变量p的地址,和字符串常量无关**
printf("%d\n", strlen(&p+1));//随机值
printf("%d\n", strlen(&p[0]+1));//5

二维数组

将二维数组看成若干一维数组组成

int a[3][4] = {0};
printf("%d\n",sizeof(a));//48 整个数组
printf("%d\n",sizeof(a[0][0]));//4/8 元素大小
printf("%d\n",sizeof(a[0]));//16 第一行的所有元素
printf("%d\n",sizeof(a[0]+1));//4/8 **a[0]作为第一行的数组名,不符合①的形式**
**//也没有取它的地址,所以a[0]是第一行首元素地址,+1就是第二行元素的地址**
printf("%d\n",sizeof(*(a[0]+1)));//4/8 第一行第二个元素
printf("%d\n",sizeof(a+1));//4/8 **第二行的地址**
printf("%d\n",sizeof(*(a+1)));//16 **第二行的元素**
printf("%d\n",sizeof(&a[0]+1));//4/8 第二行的地址 // &和[]有抵消作用
printf("%d\n",sizeof(*(&a[0]+1)));//16 **第二行的元素**
printf("%d\n",sizeof(*a));//16 第一行
printf("%d\n",sizeof(a[3]));//16

sizeof(a[3]) 的理解(这里数组越界了)

对于sizeof()操作符,只是求对象所占内存的大小,并没有访问它的内存,而是根据类型得出结果的。也就是说,sizeof()求的是对象的数据类型的大小。如:

int a = 0;
int sz = sizeof(a);
int SZ = sizeof(int);
//这两种写法是等价的

对于sizeof(a[3])a[3]的数据类型是int[4],所以即使越界了(其实访问内存才算越界,这里只是强调),也不影响sizeof()计算它的大小。这和strlen()是不一样的,后者有访问内存,编译器可能会报错。

10. 指针笔试题

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

解读

  1. 对于指针变量ptr,若不强制类型转换,那么它的类型是 int(*)[5],若要对它进行加减整数操作,单位长度是一整个数组的长度,ptr = (int *)(&a + 1),首先跳过一整个数组,然后强转,强转不会影响指针指向的位置。
  2. 强制类型转换为(int *)后,访问内存的权限是一个元素的长度

笔试题2

//考点:指针加整数的意义
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;
}

解读

  1. p+0x100000 =20 + 0x100000 = 0x100014
  2. (unsigned long)p,将p强转为长整型,长整型+1,就是单纯+1
  3. (unsigned int*)p,强转为指针,指针+1,+4或+8
  4. 考点:指针类型决定访问内存的权限

笔试题3

int main()
{
int a[4] = { 11, 22, 33, 44 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}

解读

  1. 对于ptr1ptr1首先存了&a+1的地址,指向数组最后一个元素后的地址,强转为(int*)型,ptr[-1]等价于*(ptr1 - 1 )
  2. 对于ptr2a是首元素地址,(int)a表示将首元素地址强制类型转换为int型数据,然后加一;再对它强制类型转换为(int*)型,又变成一个地址了,再对它进行解引用时,访问的字节数是从它开始往后总共四个字节

结果

笔试题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;
}

解读

  1. 这里的初始化语句有猫腻,需要睁大眼睛!!
  2. 实际上数组存放的是{1,3},{5,0},{0,0},为什么呢
  3. 因为最外层大括号里的圆括号,圆括号里的是逗号,它们是逗号表达式,最后一个才为它的值。
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//等价于
int a[3][2] = { 1, 3, 5 };//未初始化完全,自动补0
//等价于
int a[3][2] = { {1, 3}, {5, 0}, {0, 0} };
  1. p指向第一行第一个元素,结果为1

笔试题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] = {10,9,8,7,6,5,4,3,2,1};
  int *ptr1 = (int *)(&aa + 1);
  int *ptr2 = (int *)(*(aa + 1));
  printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
  return 0;
}

体会

(写在最后)对指针加减整数操作时,应该首先想到指针的类型

  1. 这题和笔试题3很像,只不过这里是二维数组
  2. &aa+1,跳过一整个数组,ptr1指向最后一个元素后的内存,强转为(int *)再减一,向前移动一个元素的长度,即指向最后一个元素
  3. aa是第一行首元素地址,它代表一整行,aa+1跳过一行指向第二行;*(aa + 1)使之转化为指向列的指针,强转为(int *)再减一,往前移动一个元素的长度,即指向前一行的最后一个元素

笔试题7

#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

解读

  1. 常量字符串,在内存中是以字符数组的形式储存的,而数组名(即数组首元素地址)存放的是第一个字符的地址,也就是说,a这个数组是一个指针数组,存放着三个常量字符串的首元素地址,即"w""a""a"的地址。

  1. 而二级指针变量pa存放着a的地址,a的地址是指针数组的首元素地址,也就是常量字符串"work"的地址,加一则指向"at"

(#)笔试题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;
}

解读

  1. 先看图,cpp存放着cp的首元素地址,而它指向常量字符串"FIRST""F"的地址

  2. 对于**++cpp++的优先级更高,且cpp指向cp数组的首元素,自加1则指向下一个元素c+2,第一层解引用得到数组c"P"地址,第二层解引用得到字符串"POINT"
  3. 对于*++cpp,先自增/自减,然后是解引用,最后是+3,此时不要忘记了cpp在上一个语句已经发生了变化(下面的语句也是一样的),已经指向数组cp的第二个元素了。首先是*++cppcpp先自增1,指向数组cp的第三个元素c+1,然后解引用得到它的值,c+1,下面用c+1作为*++cpp的结果处理
  4. 对于*--(c+1):先自减1,即*c,得到数组c第一个元素的值,即常量字符串"ENTER""E"的地址,对它+3(注意此时还是在对地址操作),指针向后移动三个单位长度(单位长度由指针类型决定),即指向"E"的地址,打印结果为:ER

  5. 对于*cpp[-2]+3cpp首先与[ ]结合,等价于*(*(cpp-2))+3。思路同上,注意进行运算时,先看看这个变量是指针还是普通变量,指针的类型如何?试着分析一下吧。

  6. 对于cpp[-1][-1]+1,先转化为指针形式,它等价于(*(cpp-1)-1)+1,此处和第五点的不同之处是这里有对数组cp的元素的值运算。

    结果如下

结语

指针并没有人们所说的那么难,最重要的是理解指针是如何运作的。以及各类指针的共同点和区别,它的类型决定了什么?在对指针进行加减运算时,是否要注意指针的类型?会当凌绝顶,一览众山小。未知对我们来说都是有难度的,只要学会并理解了它,我们以后才能更好地使用它。

如果这篇文章对你有帮助的话,请给作者一些鼓励吧~

欢迎读者指正!

目录
相关文章
|
4天前
|
算法 C语言
【C语言程序设计——循环程序设计】求解最大公约数(头歌实践教学平台习题)【合集】
采用欧几里得算法(EuclideanAlgorithm)求解两个正整数的最大公约数。的最大公约数,然后检查最大公约数是否大于1。如果是,就返回1,表示。根据提示,在右侧编辑器Begin--End之间的区域内补充必要的代码。作为新的参数传递进去。这个递归过程会不断进行,直到。有除1以外的公约数;变为0,此时就找到了最大公约数。开始你的任务吧,祝你成功!是否为0,如果是,那么。就是最大公约数,直接返回。
46 18
|
4天前
|
Serverless C语言
【C语言程序设计——循环程序设计】利用循环求数值 x 的平方根(头歌实践教学平台习题)【合集】
根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码,求解出数值x的平方根;运用迭代公式,编写一个循环程序,求解出数值x的平方根。注意:不能直接用平方根公式/函数求解本题!开始你的任务吧,祝你成功!​ 相关知识 求平方根的迭代公式 绝对值函数fabs() 循环语句 一、求平方根的迭代公式 1.原理 在C语言中,求一个数的平方根可以使用牛顿迭代法。对于方程(为要求平方根的数),设是的第n次近似值,牛顿迭代公式为。 其基本思想是从一个初始近似值开始,通过不断迭代这个公式,使得越来越接近。
36 18
|
4天前
|
C语言
【C语言程序设计——循环程序设计】统计海军鸣放礼炮声数量(头歌实践教学平台习题)【合集】
有A、B、C三艘军舰同时开始鸣放礼炮各21响。已知A舰每隔5秒1次,B舰每隔6秒放1次,C舰每隔7秒放1次。编程计算观众总共听到几次礼炮声。根据提示,在右侧编辑器Begin--End之间的区域内补充必要的代码。开始你的任务吧,祝你成功!
36 13
|
4天前
|
存储 安全 C语言
【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
分支的语句,这可能不是预期的行为,这种现象被称为“case穿透”,在某些特定情况下可以利用这一特性来简化代码,但在大多数情况下,需要谨慎使用。编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。根据提示,在右侧编辑器补充代码,计算并输出最终预测的身高。分支下的语句,提示用户输入无效。常量的值必须是唯一的,且在同一个。语句的作用至关重要,如果遗漏。开始你的任务吧,祝你成功!,程序将会继续执行下一个。常量都不匹配,就会执行。来确保程序的正确性。
26 10
|
4天前
|
小程序 C语言
【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】
目录 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果: 任务描述 相关知识 编程编写一个程序,从键盘输入3个变量的值,例如a=5,b=6,c=7,然后将3个变量的值进行交换,使得a=6,b=7,c=5。面积=sqrt(s(s−a)(s−b)(s−c)),s=(a+b+c)/2。使用输入函数获取半径,格式指示符与数据类型一致,实验一下,不一致会如何。根据提示,在右侧编辑器补充代码,计算并输出圆的周长和面积。
24 10
|
4天前
|
存储 C语言
【C语言程序设计——循环程序设计】利用数列的累加和求 sinx(头歌实践教学平台习题)【合集】
项的累加和,一般会使用循环结构,在每次循环中计算出当前项的值(可能基于通项公式或者递推关系),然后累加到一个用于存储累加和的变量中。在C语言中推导数列中的某一项,通常需要依据数列给定的通项公式或者前后项之间的递推关系来实现。例如,对于一个简单的等差数列,其通项公式为。的级数,其每一项之间存在特定的递推关系(后项的分子是其前项的分子乘上。,计算sinx的值,直到最后一项的绝对值小于。为项数),就可以通过代码来计算出指定项的值。对于更复杂的数列,像题目中涉及的用于近似计算。开始你的任务吧,祝你成功!
23 6
|
23小时前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
35 16
|
4天前
|
C语言
【C语言程序设计——循环程序设计】鸡兔同笼问题(头歌实践教学平台习题)【合集】
本教程介绍了循环控制和跳转语句的使用,包括 `for`、`while` 和 `do-while` 循环,以及 `break` 和 `continue` 语句。通过示例代码详细讲解了这些语句的应用场景,并展示了如何使用循环嵌套解决复杂问题,如计算最大公因数和模拟游戏关卡选择。最后,通过鸡兔同笼问题演示了穷举法编程的实际应用。文中还提供了编程要求、测试说明及通关代码,帮助读者掌握相关知识并完成任务。 任务描述:根据给定条件,编写程序计算鸡和兔的数量。鸡有1个头2只脚,兔子有1个头4只脚。
28 5
|
4天前
|
存储 小程序 C语言
【C语言程序设计——文件】文件操作(头歌实践教学平台习题)【合集】
本文介绍了C语言中的文件操作,分为两个关卡。第1关任务是将键盘输入的字符(以#结束)存入`file1.txt`并显示输出;第2关任务是从键盘输入若干行文本(每行不超过80个字符,用-1作为结束标志),写入`file2.txt`后再读取并显示。文中详细讲解了文件的打开、读取(使用`fgetc()`和`fgets()`)、写入(使用`fputc()`和`fputs()`)及关闭操作,并提供了示例代码和测试说明。
21 5
|
4天前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
24 1