【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.有序数组的平方_哔哩…

什么是 CSRF 攻击?

概念 CSRF 攻击指的是跨站请求伪造攻击&#xff0c;攻击者诱导用户进入一个第三方网站&#xff0c;然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态&#xff0c;那么攻击者就可以利用这个登录状态&#xff0c;绕过后台的用户验证&#xff0c;冒充用…

【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 …

HTTP 请求方法详解

HTTP 请求方法详解 请求方法 请求方法&#xff08;Request Methods&#xff09;是在 HTTP 请求中用于指定对目标资源执行的操作类型。每个请求都需要指定一个请求方法&#xff0c;以告知服务器要执行的操作。 以下是一些常见的 HTTP 请求方法及其主要用途&#xff1a; GET&…

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

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

vue-pc端Message 消息提示防抖处理-短时间内只触发一次

前言 element提供的的message消息提示用确实方便直接代码就搞定。但是在特定的场景并不适用 点击某一个点位提示用户点击了或者websocket推送提示用户来信息了&#xff08;这种提示用户场景&#xff09; 如果有很多推送信息来&#xff0c;或者用户一直点击。这是屏幕会一直弹…

Java各个版本的switch表达式

文章目录 传统switch声明语句的弊端&#xff1a;JDK12中预览特性&#xff1a;JDK13**JDK17的预览特性&#xff1a;switch的模式匹配** 传统switch声明语句的弊端&#xff1a; 匹配是自上而下的&#xff0c;如果忘记写break&#xff0c;后面的case语句不论匹配与否都会执行&…

篇十五:模板方法模式:固定算法的步骤

篇十五&#xff1a;"模板方法模式&#xff1a;固定算法的步骤" 设计模式是软件开发中的重要知识&#xff0c;模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为型设计模式&#xff0c;用于定义一个算法的骨架&#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…

MySQL 手机选号(AABB、ABCD、DCBA、AAA),SQL SERVER 手机选号(AABB、ABCD、DCBA、AAA),通过规则查询靓号

先上SQL SERVER&#xff1a; create table plat_uidlist(Uidd varchar(15) , Areaid int , State int)insert into plat_uidlist values(2335435 ,8 ,0 ) insert into plat_uidlist values(2335436 ,8 ,1 ) insert into plat_uidlist values(2335437 ,2 ,2 ) insert into plat…

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

【并发编程】线程池多线程异步去分页调用其他服务接口获取海量数据

文章目录 场景&#xff1a;解决方案 场景&#xff1a; 前段时间在做一个数据同步工具&#xff0c;其中一个服务的任务是调用A服务的接口&#xff0c;将数据库中指定数据请求过来&#xff0c;交给kafka去判断哪些数据是需要新增&#xff0c;哪些数据是需要修改的。 刚开始的设…

【Docker】配置指定大小的磁盘空间

背景 测试磁盘满时程序的运行情况 问题 如何使用 docker 来模拟磁盘满的情况 解决方法 创建指定大小的数据卷 volumedocker volume create --driver local --opt typetmpfs --opt devicetmpfs --opt osize50M my_volumn创建 docker 时&#xff0c;使用该数据卷docker run …

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

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