【C语言】指针的进阶2

指针进阶

  • 函数指针数组
  • 指向函数指针数组的指针
  • 回调函数
  • 指针和数组经典题目的解析

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int* arr[10];//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。那下面哪一个是函数指针数组呢?

int (*parr1[10])();
int* parr2[10]();
int (*)() parr3[10];

答案是:parr1。 parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表。
请看下面的一个简单计算器的实例:

int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("========================\n");printf(" 1:add     2:sub \n");printf(" 3:mul     4:div \n");printf("========================\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("请输入操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("请输入操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("请输入操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("请输入操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

那么用函数指针数组怎么实现呢?请看下面的代码:

int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;//函数指针数组int(*p[5])(int x, int y) = { NULL, add, sub, mul, div }; //转移表while (input){printf("======================\n");printf(" 1:add    2:sub \n");printf(" 3:mul    4:div \n");printf("======================\n");printf("请选择:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("请输入操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);}elseprintf("输入有误\n");printf("ret = %d\n", ret);}return 0;
}

指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针 。我们来看下面的代码:

	int (*pf)(int, int);//函数指针int (*pfArr[5])(int, int);//函数指针数组//&pfArr函数指针数组的地址,p就是指向函数指针数组的指针int (*(*p)[5])(int, int) = &pfArr;

解析:p先和 * 结合说明是一个指针,之后与[]结合,说明是一个数组指针,再与*结合说明用一个指针指向了数组指针,之后又指向了一个函数的地址,该函数有两个int类型参数,返回值是int。

总而言之,指向函数指针数组的指针就是在函数指针数组的基础上,再加一个 * 表示一个指针去指向它。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在这里插入图片描述
首先演示一下qsort函数的使用:
在这里插入图片描述
在这里插入图片描述

#include <stdlib.h>//qsort需要引入头文件
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;qsort(arr, sz, sizeof (int), int_cmp);for (i = 0; i< sz; i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

使用回调函数,模拟实现qsort(采用冒泡的方式)

int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void swap(void* p1, void* p2, int size)
{int i = 0;for (i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{int i = 0;int j = 0;for (i = 0; i < count - 1; i++){for (j = 0; j < count - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0){swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };//char *arr[] = {"aaaa","dddd","cccc","bbbb"};int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);bubble(arr, sz, sizeof(int), int_cmp);for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

void* 的指针是无具体类型的指针,它可以接收任意类型的地址,这种类型的指针是不能直接进行解引用操作,也不能直接进行指针运算。

测试qsort排序结构体数据

struct Stu
{char name[10];int age;
};
//按年龄排序
int cmp_stu_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
//按姓名排序
int cmp_stu_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{struct Stu arr[] = { {"zhangsan", 17}, {"lisi", 18},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);int i = 0;for (i = 0; i < sz; i++) {printf("%d\n", arr[i].age);}return 0;
}

指针和数组经典题目的解析

数组名是数组首元素的地址但有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址。
请看下面的题目:

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//4*4=16字节
printf("%d\n",sizeof(a+0));//数组名a是数组首元素地址,a+0还是首地址,地址大小为4/8字节
printf("%d\n",sizeof(*a));//数组名a是数组首元素地址,*a就是首元素,大小为4字节
printf("%d\n",sizeof(a+1));//数组名a是数组首元素地址,a+1就是第二个元素的地址,大小为4/8字节
printf("%d\n",sizeof(a[1]));//数组第二个元素,大小为4字节
printf("%d\n",sizeof(&a));//&a是数组的地址,数组的地址也是地址大小为4/8字节
printf("%d\n",sizeof(*&a));//*和&相互抵消,所以*&a相当于a,所以大小为16个字节
printf("%d\n",sizeof(&a+1));//&a是整个数组的地址,&a+1就是跳过整个数组,但结果任然是一个地址,大小为4/8字节
printf("%d\n",sizeof(&a[0]));//表示首元素地址,大小为4/8个字节
printf("%d\n",sizeof(&a[0]+1));//表示第二个元素的地址,大小为4/8个字节
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//arr表示整个数组,计算的是整个数组的大小,总共6个字节
printf("%d\n", sizeof(arr+0));//arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址就是4/8个字节
printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]就是数组第二个元素,大小是1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,但是数组的地址也是地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过整个数组后的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//表示第二个元素的地址,是4/8个字节printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找直到找到\0,所以结果就是随机值
printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值
printf("%d\n", strlen(*arr));//error
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参,就会从97这个地址开始统计字符串长度,这就非法访问内存了
printf("%d\n", strlen(arr[1]));//error 原因同上
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计
printf("%d\n", strlen(&arr+1));//原因同上也是随机值
printf("%d\n", strlen(&arr[0]+1));//&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));//arr + 0是首元素的地址,大小1个字节
printf("%d\n", sizeof(*arr));//*arr其实就是首元素,大小1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr+1));//&arr + 1是跳过一个数组的地址,结果仍然是地址大小为4/8个字节
printf("%d\n", sizeof(&arr[0]+1));//&arr[0] + 1是第二个元素的地址 大小为4/8个字节printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//6
printf("%d\n", strlen(*arr));//error
printf("%d\n", strlen(arr[1]));//error
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr+1));//跳过整个数组向后数,后面是未知的所以结果是随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素往后数,长度为5
char* p = "abcdef";
printf("%d\n", sizeof(p));//p是一个指针变量大小就是4/8个字节
printf("%d\n", sizeof(p+1));//p+1是'b'的地址,是地址大小就是4/8个字节
printf("%d\n", sizeof(*p));//*p 就是'a',大小就是1个字节
printf("%d\n", sizeof(p[0]));//p[0]--> *(p+0) --> *p 大小1个字节
printf("%d\n", sizeof(&p));//&p --> char** 大小4/8个字节
printf("%d\n", sizeof(&p+1));//直接跳到字符串后面的,实际还是地址,大小4/8个字节
printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址,大小4/8个字节printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p+1));//5
printf("%d\n", strlen(*p));//error
printf("%d\n", strlen(p[0]));//error
printf("%d\n", strlen(&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));//3*4*4 = 48
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//a[0]是第一行这个一维数组的数组名,数组名算是单独放在sizeof内部了,计算的是整个数组的大小,大小是16个字节
printf("%d\n",sizeof(a[0]+1));//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&,a[0]表示数组首元素的地址,也就是a[0][0]的地址,所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节
printf("%d\n",sizeof(*(a[0]+1)));//计算的是第一行第2个元素的大小,为4个字节
printf("%d\n",sizeof(a+1));//a是数组首元素的地址,是第一行的地址,a+1就是第二行的地址,是地址大小就是4/8个字节  (它的类型是int(*)[4])
printf("%d\n",sizeof(*(a+1)));//*(a+1) --> a[1] -> sizeof(*(a+1))->sizeof(a[1]) 计算的是第二行的大小,就是16个字节
printf("%d\n",sizeof(&a[0]+1));//&a[0]是第一行的地址,&a[0]+1 是第二行的地址,是地址大小就是4/8个字节
printf("%d\n",sizeof(*(&a[0]+1)));//计算的是第二行的大小,16个字节
printf("%d\n",sizeof(*a));//a是数组首元素的地址,就是第一行的地址,*a 就是第一行,*a --> *(a+0) --> a[0],大小为16个字节
printf("%d\n",sizeof(a[3]));//第三行的大小,16个字节

总结:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

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

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

相关文章

无涯教程-Perl - getpwnam函数

描述 此函数基于EXPR指定的用户名,从/etc/passwd文件提取的列表context中返回字段列表。通常这样使用- ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) getpwnam($user); 在标量context中,返回数字用户ID。如果尝试访问整个/etc/passwd文件,则应使用getpwent…

Lecoode有序数组的平方977

题目建议&#xff1a; 本题关键在于理解双指针思想 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a; 双指针法经典题目 | LeetCode&#xff1a;977.有序数组的平方_哔哩…

【Linux】内核宏定义解释postcore_initcall,arch_initcall,subsys_initcall

postcore_initcall postcore_initcall(pcibus_class_init) 是一个宏&#xff0c;用于在Linux内核初始化过程中注册一个后期初始化函数。 这个宏的含义如下&#xff1a; postcore_initcall 是一个宏定义&#xff0c;用于指定注册的函数在内核初始化的哪个阶段执行。 pcibus_cl…

Spring Gateway+Security+OAuth2+RBAC 实现SSO统一认证平台

背景&#xff1a;新项目准备用SSO来整合之前多个项目的登录和权限&#xff0c;同时引入网关来做后续的服务限流之类的操作&#xff0c;所以搭建了下面这个系统雏形。 关键词&#xff1a;Spring Gateway, Spring Security, JWT, OAuth2, Nacos, Redis, Danymic datasource, Jav…

nginx代理服务、网关配置

一、nginx安装在服务器&#xff0c;本机运行服务&#xff0c;如何使用远程nginx代理本机服务&#xff1f; 打开nginx配置文件&#xff0c;位置&#xff1a;/usr/local/nginx/conf/nginx.conf&#xff0c;在http模块中添加以下server代码段&#xff1a; http {server {listen …

Stable Diffusion - 人物坐姿 (Sitting) 的提示词组合 与 LoRA 和 Embeddings 配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132201960 拍摄人物坐姿时&#xff0c;需要注意&#xff1a; 选择一个舒适和自然的坐姿&#xff0c;符合个性和心情。可以坐在椅子、沙发、长凳、…

ubuntu安装docker-compose

1.官方安装链接 访问&#xff1a;https://docs.docker.com/compose/install/standalone/ 链接&#xff0c;可以看到如下页面&#xff0c;使用下面圈起来的命令即可 2.安装 使用该命令进行安装&#xff0c;很慢&#xff0c;一直卡着不动&#xff0c;原因是从github中下载&am…

路由导航守卫中document.title = to.meta.title的作用以及路由跳转修改页面title

目录 &#x1f53d; document.title to.meta.title的作用 &#x1f53d; Vue路由跳转时如何更改页面title &#x1f53d; document.title to.meta.title的作用 路由导航守卫如下&#xff1a; router.beforeEach(async (to, from, next) > {document.title to.meta.ti…

css中的var函数

css中的var函数 假设我们在css文件存在多个相同颜色值&#xff0c;当css文件越来越大的时候&#xff0c;想要改颜色就要手动在每个旧颜色上修改&#xff0c;这样维护工作非常难进行。 但是我们可以使用变量来存储值&#xff0c;这样可以在整个css样式表中重复使用&#xff0c…

HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制概述

ArkUI通过自定义组件的build()函数和builder装饰器中的声明式UI描述语句构建相应的UI。 在声明式描述语句中开发者除了使用系统组件外&#xff0c;还可以使用渲染控制语句来辅助UI的构建&#xff0c;这些渲染控制语句包括控制组件是否显示的条件渲染语句&#xff0c;基于数组数…

【区块链】Go 实现简单区块链

本文主要利用 Go 语言对区块链模型进行了简单的实现&#xff0c;通过 GoLand 创建链式结构和一个简单的 http server&#xff0c;对外暴露读写接口&#xff0c;运行 rpc 并以地址访问形式向区块链发送数据和读取数据。 简单区块链的实现大致步骤分为&#xff1a; &#xff08;…

JS逆向系列之猿人学爬虫第14题-备而后动-勿使有变

文章目录 题目地址参数分析参考jspython 调用往期逆向文章推荐题目地址 https://match.yuanrenxue.cn/match/14题目难度标的是困难,主要难在js混淆部分。 参数分析 初始抓包有无限debugger反调试,可以直接hook 函数构造器过掉无限debugger Function.prototype.__construc…

Mirror网络库 | 说明

此篇为上文&#xff0c;下篇&#xff1a;Mirror网络库 | 实战 一、介绍 基于UNET&#xff0c;从2014年经过9年实战测试&#xff1b;服务器和客户端是一个项目&#xff1b;使用NetworkBehaviour而不是MonoBehaviour&#xff0c;还有NetworkServer和NetworkClient&#xff1b;Mi…

pdf怎么压缩到1m?这样做压缩率高!

PDF是目前使用率比较高的一种文档格式&#xff0c;因为它具有很高的安全性&#xff0c;还易于传输等&#xff0c;但有时候当文件体积过大时&#xff0c;会给我们带来不便&#xff0c;这时候简单的解决方法就是将其压缩变小。 想要将PDF文件压缩到1M&#xff0c;也要根据具体的情…

ASP.NET Core中间件记录管道图和内置中间件

管道记录 下图显示了 ASP.NET Core MVC 和 Razor Pages 应用程序的完整请求处理管道 中间件组件在文件中添加的顺序Program.cs定义了请求时调用中间件组件的顺序以及响应的相反顺序。该顺序对于安全性、性能和功能至关重要。 内置中间件记录 内置中间件原文翻译MiddlewareDe…

微服务 云原生:基于 Gogs + Drone 实现 CI/CD 自动化

一般构建部署 以一个简单的前后端项目来说&#xff0c;分别编写前后端的 Dockerfile 文件并构建镜像&#xff0c;然后编写 docker-compose.yml 构建部署&#xff0c;启动运行。每次代码变更后都需重新手动打包、构建、推送。 一个简单的例子&#xff1a; 前端&#xff1a; 项…

【力扣每日一题】2023.8.7 反转字符串

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个字符数组形式的字符串&#xff0c;让我们直接原地修改反转字符串&#xff0c;不必返回。 给出的条件是使用O(1)的额外空间…

python爬虫相关

目录 初识爬虫 爬虫分类 网络爬虫原理 爬虫基本工作流程 搜索引擎获取新网站的url robots.txt HTHP协议 Resquests模块 前言&#xff1a; 安装 普通请求 会话请求 response的常用方法 简单案例 aiohttp模块 使用前安装模块 具体案例 数据解析 re解析 bs4…

AutoDL服务器的镜像版本太高,配置python3.7 tensorflow1.15版本的框架的步骤

1.选择一个实例&#xff0c;进入后端界面 2. 更新bashrc中的环境变量 conda init bash && source /root/.bashrc查看虚拟环境 conda info --envs可以看到此时有一个base的虚拟环境 但是它的python版本为3.8.10&#xff0c;无法安装tensorflow1.15,所以我们要创建一个…

Ctfshow web入门 SSTI 模板注入篇 web361-web372 详细题解 全

CTFshow SSTI web361 笔记分享 一、代码块 变量块 {{}} 用于将表达式打印到模板输出 注释块 {##} 注释 控制块 {%%} 可以声明变量&#xff0c;也可以执行语句 {% for i in .__class__.__mro__[1].__subclasses__() %}{% if i.__name___wrap_close %}{% print i.__init__.…