指针的进阶--从入门到入土

news2025/1/12 23:16:03

指针--变态加强版

  • 前言
  • 面试题
  • 字符指针
  • 指针数组
  • 数组指针
  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针
  • 面试题解析
  • 结语

前言

掌握指针这块知识的重要性不言而喻,当你觉得自己已经差不多掌握指针的时候,不妨看看下面8道面试题(题目从简单到困难,读者根据自己水平选择题目难度),答案在文末公布,如果你都做对了,那说明你对指针这一块知识的掌握还是非常不错的,当然了,如果你的表现还欠那么一点点,不妨看看这篇文章对指针的详解。

面试题

问下面8道面试题分别输出什么?

1.

int main()
{
	 int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	 int *p;
	 p = a[0];
	 printf( "%d", p[0]);
	 return 0;
}

2.

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.

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

4.

int main()
{
	 int a[5][5];
	 int(*p)[4];
	 p = (int(*)[4])a;
	 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	 return 0;
}

5.

struct Test
{
	 int Num;
	 char *pcName;
	 short sDate;
	 char cha[2];
	 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	 printf("%p\n", p + 0x1);
	 printf("%p\n", (unsigned long)p + 0x1);
	 printf("%p\n", (unsigned int*)p + 0x1);
	 return 0;
}

6.

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;
}//这里采用小端存储

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

字符指针

字符指针是一种使用起来相对比较简单的指针,这里讲一讲其比较特殊的一种使用方法

int main()
{
	char*p="hello world!";
	return 0;
}

这是把字符串常量 “hello world!” 的首字符 ‘ h ’ 的地址存到指针变量 p 中,通过p就可以找到该字符串的首字符,继而找到整个字符串,而该字符串则是存到代码区中去了。
在这里插入图片描述

指针数组

指针数组是存放指针的数组,与我们平时使用的数组没有多大区别,只不过指针数组的元素是指针而已。

char* arr1[3];//一级字符指针数组
int* arr2[3];//一级整型指针数组
int** arr3[3];//二级整型指针数组

在这里插入图片描述

数组指针

数组指针是指向数组的指针。如指向一个大小为10个整型的数组的指针为:

int (*ptr)[10];

ptr先与*结合,表明ptr是指针变量,接着指向大小为10个整型的数组,如果没有括号则是定义指针数组。接下来我们可以看看以下代码表示什么意思?

int (*ptr[3])[5];

ptr先与[3]结合,表明ptr是数组名,接着与*结合,表明数组元素为指针类型,最后与[5]结合,表明这3个指针都是指向大小为5个整型的数组。
在这里插入图片描述

其实ptr就相当于指向一个三维数组,该数组有3行1列且高为5,即ptr[3][1][5]。

这里补充一下数组名的理解:
数组名都是表示首元素的地址,但有2个例外
1.sizeof(数组名)
此时数组名表示整个数组的地址,sizeof计算的是整个数组的大小,单位是字节。
2.&数组名
这里的数组名表示的是整个数组的地址,因而取地址符取出的是整个数组的地址。
对于数组 int arr[5] 来说,arr与&arr表示的地址是一样的,但他们的数据类型不一样,导致他们在+1时跳跃的距离不一样,如arr+1,地址跳跃了4(一个int类型的大小),而&arr+1则跳跃了20(4*5=20)。

函数指针

函数指针是指向函数的指针,其形式为:

函数返回值类型  (*函数指针变量名)(参数类型);

例:

//定义一个函数
int add(int x,int y)
{
	return x+y;
}

int main(void)
{
	int (*p)(int,int);//定义函数指针
	
	p=add;//1
	p=&add;//2
	//1和2这两种赋值方式没有什么区别
	
	int result=0;
	result=p(3,5);//3
	result=(*p)(3,5);//4
	//3和4这两种使用方式也没有什么区别

	return 0;
}

函数指针看上去是比较容易上手的,接下来我们看看两段有意思的代码:
1.

(*(void(*)())0)();

该代码是调用0地址处的函数。
2.

void (*signal(int,void(*)(int)))(int);

该代码是signal函数的声明,相当于

void (*fun)(int);
fun signal(int,fun);

感兴趣的读者可以自行分析原因,这里不过多赘述。

函数指针数组

函数指针数组是存放函数指针的数组,其定义方式为:

函数返回类型 (*数组名[元素个数])(函数参数列表);

函数指针数组比较大的一个用途就是在转移表上。
如在两位整数的加减乘除运算中,要编写4个函数,输入1调用加函数,输入2调用减函数,以此类推,那我们就要用很多个if语句或case来选择调用哪个函数,但当我们使用函数指针数组时,代码就会大大简化。只需要一个intput接收输入的值,接着调用下标为intput的函数指针指向的函数就行了。

int (*p[4])(int int);
...//数组赋值
scanf("%d",&input);
int result=(*(p[input]))(1,7);//p为函数指针数组名,返回结果由result接收
//自己要安排好哪个函数在数组的什么位置

指向函数指针数组的指针

其定义形式为

函数返回类型 (**指针变量名)[所指向的函数指针数组的元素个数])(参数列表)
void (*pfun1[5])(int);//定义函数指针数组
void (*(*ppfun2)[5])(int)=&pfun1;//定义指向函数指针数组的指针并指向pfun1

倘如读者了解了数组指针和函数指针,理解这个应该不成问题。

面试题解析

面试题答案

  1.       1
    
  2.   	  2,5
    
  3.    	  10,5
    
  4.    	  fffffffc,-4
    
  5.    	  100020
       	  100001
       	  100004
    
  6.    	  4,2000000
    
  7.    	  at
    
  8.       POINT
       	  ER
          ST
          EW
    

分析:
1.

int main()
{
	 int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	 int *p;
	 p = a[0];
	 printf( "%d", p[0]);
	 return 0;
}

在这里插入图片描述

2.

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.

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

*(ptr1 - 1)的计算方法和第2题一样,这里就不讲了

在这里插入图片描述

4.

int main()
{
	 int a[5][5];
	 int(*p)[4];
	 p =(int(*)[4])a;
	 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	 return 0;
}

在这里插入图片描述

5.

struct Test
{
	 int Num;
	 char *pcName;
	 short sDate;
	 char cha[2];
	 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	 printf("%p\n", p + 0x1);
	 printf("%p\n", (unsigned long)p + 0x1);
	 printf("%p\n", (unsigned int*)p + 0x1);
	 return 0;
}

在64位机器下,这里结构体Test的大小经过计算是32个字节(64位机器下,指针的大小是8个字节),如果结构体的大小不会计算的,可以去看看我的另一篇关于如何计算结构体大小的文章
亦星编程
0x1是10进制的1
p是结构体指针,指向的是一个Test结构体的地址,p+0x1跳过一个结构体类型的大小,即要加32,最后以16进制打印就是0x100020

(unsigned long)p + 0x1中p被强制类型转化为无符号长整型(已经不是指针了,而是一个整数),整数0x100000加1就是0x100001,最后以16进制打印就是100001
unsigned int*)p + 0x1中,p被强制转换为无符号整型指针,p指向的是一个无符号整数的地址,加1跳过4个字节(一个无符号整型的大小),最后以16进制打印就是0x100004。

6.

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+1跳过整个数组,接着转化int*指针,故ptr[-1]就是4
在这里插入图片描述

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

在这里插入图片描述

结语

其实就是指向内存某个地方的地址,尽管该地址处的数据不变,但由于指针类型不一样,读取数据的方式就不同,结果就不一样,如整型指针解引用会在该地址处向后读取4个字节的数据,而字符型指针解引用只会在该地址处向后读取一个字节的数据。以上就是本文的所有内容,如果有什么不对的地方,恳请指正,当然了,如果你觉得本文对你有帮助,不妨动动小手给博主一个点赞收藏加关注吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/870136.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三级城市展示省市区树

展示效果 数据库展示 业务代码 /*** 省市区树*/VLicenseApiOperation("查询经纬度")ApiImplicitParam(name "FnCity", value "省市区树", dataType "FnCity")GetMapping("/districtlist")public AjaxResult districtlist…

STM32F429IGT6使用CubeMX配置IIC通信(AT2402芯片)

1、硬件电路 写地址:0xA0 读地址:0xA1 存储容量:256Byte 2、设置RCC,选择高速外部时钟HSE,时钟设置为180MHz 3、配置IIC 4、生成工程配置 5、部分代码 #define IIC_WRITE_ADDR 0xA0 // IIC写地址 #define IIC_READ_ADDR 0xA1 …

数据结构----哈夫曼树

这里写目录标题 基本概念引子基本概念各种路径长度各种带权路径长度结点的带权路径长度树的带权路径长度哈夫曼树 哈夫曼树的构造理论基础构造思想总结 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录…

exec族函数

本节学习exec族函数,并大量参考了以下链接: linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)_云英的博客-CSDN博客 exec族函数函数的作用 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程…

从零构建深度学习推理框架-8 卷积算子实现

其实这一次课还蛮好理解的&#xff1a; 首先将kernel展平&#xff1a; for (uint32_t g 0; g < groups; g) {std::vector<arma::fmat> kernel_matrix_arr(kernel_count_group);arma::fmat kernel_matrix_c(1, row_len * input_c_group);for (uint32_t k 0; k < k…

【leetcode】前缀和

内容摘抄自&#xff1a; 小而美的算法技巧&#xff1a;前缀和数组 | labuladong 的算法小抄 一维数组的前缀和 看这个 preSum 数组&#xff0c;若想求索引区间 [1, 4] 内的所有元素之和&#xff0c; 就可以通过 preSum[5] - preSum[1] 得出。 class NumArray {private:// 前缀…

软件测试基础篇——Docker

1、docker技术概述 docker描述&#xff1a;docker是一项虚拟化的容器技术&#xff08;类似于虚拟机&#xff09;&#xff0c;docker技术给使用者提供一个平台&#xff0c;在该平台上可以利用提供的容器&#xff0c;对每一个应用程序进行单独的封装隔离&#xff0c;每一个应用程…

记录--用css画扇形菜单

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 1、效果图 用手机录屏再用小程序转换的gif&#xff0c;可能精度上有点欠缺。 2、实现过程 1、观察及思考 开始编码前我们首先观察展开后的结构&#xff1a;两个四分之一的圆加三个圆形菜单项。 文章名…

阿里云服务器部署Drupal网站教程基于CentOS系统

阿里云百科分享如何在CentOS 7操作系统的ECS实例上搭建Drupal电子商务网站。Drupal是使用PHP语言编写的开源内容管理框架&#xff08;CMF&#xff09;&#xff0c;它由内容管理系统&#xff08;CMS&#xff09;和PHP开发框架&#xff08;Framework&#xff09;共同构成。它用于…

【博客692】grafana如何解决step动态变化时可能出现range duration小于step

grafana如何解决step动态变化时可能出现range duration小于step 1、grafana中的step和resolution grafana中的 “step” grafana本身是没有提供step参数的&#xff0c;因为仪表盘根据查询数据区间以及仪表盘线条宽度等&#xff0c;对于不同查询&#xff0c;相同的step并不能…

编译redis-5.0.9报错zmalloc.h:50:31: 致命错误:jemalloc/jemalloc.h:没有那个文件或目录问题解决

上图 解决&#xff1a; make && make install MALLOClibc原因: 原因是jemalloc重载了Linux下的ANSI C的malloc和free函数。

RedisDesktopManage

RDM 简介下载安装 简介 RedisDesktopManager&#xff08;RDM&#xff09;是一个开源的跨平台图形界面工具&#xff0c;用于管理和操作 Redis 数据库。它提供了一个用户友好的界面&#xff0c;使用户能够轻松地连接、浏览、查询和修改 Redis 数据&#xff0c;而无需使用命令行界…

bug解决:AssertionError: No inf checks were recorded for this optimizer.

这真的是最恶心的一个error&#xff08;比网络回传找哪层没有传播到还要恶心&#xff01;&#xff09;&#xff0c;找了好久的问题所在之处&#xff0c;最后偶然发现了这篇文章&#xff1a; 解决pytorch半精度amp训练nan问题 - 知乎 然后发现自己用的混合精度训练&#xff0c;发…

CGLIB原理简析-前篇

CGLIB&#xff08;Code Generation Library&#xff09;是实现动态代理的一种方案。动态代理的内容一般都包含三个部分&#xff1a;① 代理类的生成&#xff1b;② 代理类的实例化&#xff1b;③ 代理类的使用。 本文作为CGLIB文章的前篇&#xff0c;将通过与使用者直接相关入手…

Java AWT Swing(图形化界面编程)(一)

目录 1.简介 2.Java中的图像化界面----Awt与Swing 一、AWT编程 1.简介 2.AWT的继承体系 3.container容器 3.1container继承体系 3.2.常见API 3.3容器演示一 3.4容器演示二 3.5容器演示三 1.简介: 通常情况下&#xff0c;java语言一般是用来开发后台程序的&#xff0…

mysql数据库第十二课------mysql语句的拔高2------飞高高

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

图论——最短路算法

引入&#xff1a; 如上图&#xff0c;已知图G。 问节点1到节点3的最短距离。 可心算而出为d[1,2]d[2,3]112,比d[1,3]要小。 求最短路径算法&#xff1a; 1.Floyd(弗洛伊德) 是一种基于三角形不等式的多源最短路径算法。边权可以为负数 表现为a[i,j]a[j,k]<a[i,k]。 …

【好好练习ACM-DAY1】

网站主要参照洛谷 牛客 力扣 cf等 题单列表 - 洛谷 题单链接 今天的刷题内容如上 在进入专题学习之前 先看补充知识 数组能开多大 C/C数组的大小最大能有多大&#xff1f;_c数组大小_JoannaJuanCV的博客-CSDN博客 全局&#xff1a;int 2000*2000 6个0可 局部&#xf…

Ubuntu删除大量磁盘文件时,内存cache降free升而创造的漂亮曲线

文章目录 前言图形本尊产生的原因总结 前言 最近在做压力测试&#xff0c;对磁盘、CPU、内存等指标比较敏感&#xff0c;因为产生了大量的日志文件需要定期删除&#xff0c;于是写了个定时清理磁盘的脚本&#xff0c;今天早上发现内存曲线居然产生了一个漂亮的图形&#xff0c…

递归、搜索与回溯算法

一.递归 &#xff08;1&#xff09;汉诺塔问题 当n2时&#xff0c;要将A中最下面盘子上方的盘子放到B上&#xff0c;最下面盘子放到C上&#xff0c;再将B上的盘子通过A放到C即可&#xff1b; 当n3时&#xff0c;要将A中最下面盘子上方的盘子放到B上&#xff0c;最下面盘子放到…