【C语言】指针(4):深入理解指针

目录

​编辑

一、回调函数

二、qsort使用举例

2.1 使用qsort排序整型数据

2.2 使用qsort排序结构体数据

三、qsort的模拟实现

四、NULL、\0、0、'0'、null、NUL的区别

五、C99中的变长数组

一、回调函数


      函数指针是将函数的地址取出来,再通过函数地址去调用,那为什么不直接用函数名调用呢??原因是因为函数指针可以用来实现回调函数,而回调函数有自己的应用场景。

      回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。

#include <stdio.h>
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(" 0:exit \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;
}

以上这段代码中,我们发现case部分的代码总是重复出现,这段代码只有调用函数的逻辑有差异(但是函数的返回类型和形参是一样的),其他输入输出操作都是冗余的,那么这个时候我们可以把调用的函数地址以参数的形式传去,用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实就是使用的回调函数功能。

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;
}
void calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("输入操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main()
{int input = 1;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:calc(add);break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

   回调函数不是由该函数的实现方直接调⽤,⽽是在特定的事件或条 件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

      怎么理解上面这段话呢?我们可以发现回调函数并非直接调用的,而是当需要进行某种运算时(特定需求的发生),根据需求将函数地址传给pf,然后在calc(另外一方)函数中通过pf(间接调用)来调用这个函数。

二、qsort使用举例


前面学习的冒泡排序,只能排序整形数据,那我们如何完成其他数据的排序呢?就得用到qsort

qsort是一个库函数,可以完成任意数据的排序,我们首先通过cplusplus的网站来了解qsort,qsort的头文件是stdlib.h,下面我们能来分析他的形参类型。

1.第一个形参void*base是一个void*类型的指针(因为该数组可能是任意类型,所以只有void*才可以接收任意类型的数据的地址),base指向要排序的数组的第一个元素位置。

2.第二个形参size_t num是一个无符号整型,num指向的是待排序数组中的元素个数。(只要知道元素的个数才能确定比较的次数。)

3.第三个形参size_t size是一个无符号整型,size指向数组中元素的大小(单位是字节,因为qsort完成任何类型的排列,所以对象可能是结构体也可能是整型,需要具体传入去 运算)。

4.第四个形参int (*compar)(const void*,const void*));,compar是一个函数指针,返回类型是int类型,两个形参的类型是void*类型。该函数指针指向的函数是用来比较数组中两个元素的方法。这个方法是根据我们的需求(比较整型或者比较结构体数据),去构造一个函数用来比较,构造的函数返回类型和形参类型必须一致。

qsort通过返回值来判断p1和p2的大小,当返回值>0,说明p1大于p2,返回值=0,说明p1=p2,返回值<0,说明p1<p2。

了解了qsort,下面利用qsort来实现排序。

2.1 使用qsort排序整型数据

int int_cmp(const void* p1, const void* p2)//整型的比较方法
{return(*(int*)p1 - *(int*)p2);//void*类型的指针必须强转后才可以进行运算。
}
int main()
{int arr[] = { 9,8,7,6,5,4,0,2,1,3 };int num = sizeof(arr) / sizeof(arr[0]);//确定数组的个数int size = sizeof(int);//确定数组的每个元素占用字节大小qsort(arr, num, size, int_cmp);for (int i = 0; i < num; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

运行结果:0 1 2 3 4 5 6 7 8 9  

注意事项:

1.qsort的使用必须包含头文件stdlib.h

2创建比较方法int_cmp函数时要注意该函数返回的结果必须是>0,=0,<0;

3.int_cmp传入的是void*类型的指针,必须强转成int*类型再解引用才可以进行运算。

4.如果想要完成逆序,将int_cmp的代码return(*(int*)p1 - *(int*)p2)中的p1和p2交换即可。

2.2 使用qsort排序结构体数据

struct Stu//学生
{char name[20];//名字int age;//年龄
};
//创建用年龄比较的方法
int cmp_stu_by_age(const void* p1, const void* p2)
{return((struct Stu*)p1)->age - ((struct Stu*)p2)->age;//也可以写成(*(struct stu*)p1).age-(*(struct stu*)p2).age //结构体变量.成员名  或者   结构体指针->成员名
}
//创建用名字比较的方法
int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);//strcmp函数是专门用来比较字符串大小的//字符串的比较方法:从左到右的顺序逐个比较两个字符串的字符,直到遇到第一个不同的字符,然乎根据字符的ascii值来确定两个字符串的大小关系。
}
//创建一个打印数组函数
void prinf(struct Stu s[], int num)
{for (int i = 0; i < num; i++){printf("第%d个同学的名字是%s,年龄是%d\n", i + 1, s[i].name, s[i].age);}
}
int main()
{struct Stu s[] = { {"zhangsan",20},{"lisi",30},{"wangwu",15} };int num = sizeof(s) / sizeof(s[0]);//元素个数int size = sizeof(struct Stu);//学生类型的大小printf("比较前\n");prinf(s, num);printf("通过年龄比较后\n");qsort(s, num, size, cmp_stu_by_age);prinf(s, num);printf("通过名字比较后\n");qsort(s, num, size, cmp_stu_by_name);prinf(s, num);
}

运行结果:

比较前
第1个同学的名字是zhangsan,年龄是20
第2个同学的名字是lisi,年龄是30
第3个同学的名字是wangwu,年龄是15
通过年龄比较后
第1个同学的名字是wangwu,年龄是15
第2个同学的名字是zhangsan,年龄是20
第3个同学的名字是lisi,年龄是30
通过名字比较后
第1个同学的名字是lisi,年龄是30
第2个同学的名字是wangwu,年龄是15
第3个同学的名字是zhangsan,年龄是20 

注意事项:

1.要访问结构体成员的两个方法:结构体变量.成员名    结构体指针->成员名

2.strcmp是专门用来比较字符串的大小的,并且它的返回值也恰好和qsort一样,所以可以直接去调用。字符串的比较方法:从左到右的顺序逐个比较两个字符串的字符,直到遇到第一个不同的字符,然乎根据字符的ascii值来确定两个字符串的大小关系。

3.结构体类型相较于整型类型,不能直接用+-<>等运算符,因为结构体中的成员属性可能有多个,直接比较编译器无法判断根据哪一个成员属性来比较。

 

三、qsort的模拟实现


       qsort展现的是不同数据类型的快速排序,在学习qsort之前,我只知道冒泡排序,而冒泡排序只能排序整型类型,那么我们可以通过会回调函数的方法,来改造冒泡排序,使其成为可以排序任意数据类型的排序方法。

     在模拟实现前,我们要比较qsort和冒泡排序,两者的数据类型不一样,所以我们对他的改造需要体现在两个方面。

1.由于数据类型不同,所以比较的方法必须改造。

2.由于不同数据类型占用字节大小不同,在利用指针偏移量操作的时候会有差异,所以交换的方法也必须改造。

3.由于数据类型不同,创建比较方法和交换方法时传入的两个参数必须是void*类型

4.模拟实现qsort,就要保证改造的排序函数bubble的返回类型和形参都要保持一致

int int_cmp(const void* p1, const void* p2)//比较方法
{return(*(int*)p1 - *(int*)p2);
}
void swap(void* p1, void* p2, int size)//交换方法,这里要引入size,让swap函数知道交换的数据是什么类型
{for (int i = 0; i < size; i++){char temp = *((char*)p1+ i);//void*类型必须要先强制转化成char*类型*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = temp;//为什么这里要使用字符类型?,因为我们并不知道传入的是什么数据类型,所以用char*(1个字节)来作为单位元,每次交换一个字节,交换次数恰好和size相同}
}
void bubble(void* base, int num, int size, int (*cmp)(const void* p1, const void* p2))
{for (int i = 0; i < num - 1;i++){for (int j = 0; j < num - i - 1; j++){if (int_cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)//不知道是什么数据类型,所以用char*比较好操作,一次只操作一个字节。{swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}//使用前必须强转成char*类型}}
}
int main()
{int arr[] = { 9,8,7,6,5,4,0,2,1,3 };int num = sizeof(arr) / sizeof(arr[0]);//确定数组的个数int size = sizeof(int);//确定数组的每个元素占用字节大小bubble(arr, num, size, int_cmp);for (int i = 0; i < num; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

运行结果:0 1 2 3 4 5 6 7 8 9 

       要注意的是,由于交换方法和比较方法的改造,由于不知道比较的是什么数据类型,所以都强转成char*类型进行操作,因为char*类型操作一次是一个字节,方便计算。这样恰好就是一次交换一个字节,执行size次后就完成整个元素的交换。所以必须传入size。

 

四、NULL、\0、0、'0'、null、NUL的区别


NULL:本质是0,一般用于指针的初始化

\0:\ddd形式的转移字符,本质也是0,在字符串中作为结束标志,ASCII码值为0

0:数字0

'0':字符0,ASCII码值为48

null/NUL:本质就是\0,作为字符串结束标志

五、C99中的变长数组


        在C99标准之前,C语⾔在创建数组的时候,数组大小的指定只能使⽤常量、常量表达式,或者如果我们初始化数据的话,可以省略数组⼤⼩。

int arr1[10];
int arr2[3+5];
int arr3[] = {1,2,3};

 这样的语法限制,让我们创建数组就不够灵活,有时候数组⼤了浪费空间,有时候数组⼜⼩了不够⽤的。 

     C99中给⼀个变⻓数组(variable-length array,简称 VLA)的新特性,允许我们可以使⽤变量指定数组大小。

int n = a+b;
int arr[n];

上⾯⽰例中,数组 arr 就是变⻓数组,因为它的⻓度取决于变量 n 的值,编译器没法事先确定,只有运⾏时才能知道 n 是多少。

   变⻓数组的根本特征,就是数组⻓度只有运⾏时才能确定,所以变⻓数组不能初始化。它的好处是程序员不必在开发时,随意为数组指定⼀个估计的⻓度程序可以在运⾏时为数组分配精确的⻓度。有 ⼀个⽐较迷惑的点,变⻓数组的意思是数组的⼤⼩是可以使⽤变量来指定的,在程序运⾏的时候,根据变量的⼤⼩来指定数组的元素个数,⽽不是说数组的⼤⼩是可变的。数组的大小⼀旦确定就不能再变化了。

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

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

相关文章

untiy 在菜单栏添加自定义按钮 点击按钮弹出一个Unity窗口,并在窗口里添加属性

using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.PostProcessing;public class AutoGenerateWindow : EditorWindow //这是定义一个窗口 {public string subjecttName "科目名字";//科目的名字public GameOb…

url链接地址,#前的参数 和 #后的参数有什么区别

例如 http://localhost:8080/?beforeParams1#/workSchemelist/index?afterParams1 beforeParams 和 afterParams 区别 打印出来可以发现&#xff1a; beforeParams 是 url 的search参数&#xff0c;通过window.location.search获取 afterParams 是 route 的query参数&#…

外贸网站设计的要点

外贸网站设计是一种专门针对国际贸易领域的网站设计&#xff0c;需要考虑到不同国家和文化背景的用户&#xff0c;因此设计过程要更加细致和精准。以下是外贸网站设计的关键要点&#xff1a; 首先&#xff0c;多语言支持是不可或缺的&#xff0c;因为外贸网站的用户可能来自不同…

[Python自动化办公]--从网页登录网易邮箱进行邮件搜索并下载邮件附件

[Python自动化办公]–从网页登录网易邮箱进行邮件搜索并下载邮件附件 使用说明 ​ 本文使用Python的selenium库进行操作邮箱登录、固定名称搜索邮件并下载附件&#xff0c;Python版本&#xff1a;3.9.16, selenium版本&#xff1a;4.19.0&#xff0c;EdgeBrowser版本:126.0.2…

LVS集群及其它的NAT模式

1.lvs集群作用&#xff1a;是linux的内核层面实现负载均衡的软件&#xff1b;将多个后端服务器组成一个高可用、高性能的服务器的集群&#xff0c;通过负载均衡的算法将客户端的请求分发到后端的服务器上&#xff0c;通过这种方式实现高可用和负载均衡。 2.集群和分布式&#…

用户增长 - 私域 - 社群运营自检清单SOP(社群运营30问)

Check List: 1.你的目标用户是谁&#xff1f; 2.你的目标用户有哪些需要立马解决的需求&#xff1f;有哪些长期需求&#xff1f;这些需求的优先级是什么&#xff1f; 3.做社群的目的是什么&#xff1f; 4.你的用户和业务是否适合做社群&#xff1f; 5.你做哪类社群才能更好的帮…

确定适合您需求的负载组

大多数关键任务行业都使用 UPS 和发电机等备用电源在停电期间为其设施提供持续电力。负载组允许您在需要时测试电源&#xff0c;以确保在您最需要的时候提供可靠的电力。 选择正确的负载组对于准确的电源测试至关重要。为了帮助您找到最适合您设施需求的负载组&#xff0c;EAK…

【机器学习】主成分分析(PCA):数据降维的艺术

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 主成分分析&#xff08;PCA&#xff09;&#xff1a;数据降维的艺术引言PCA的基…

技术成神之路:设计模式(四)工厂方法模式

1.定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;而不是通过具体类来实例化对象。工厂方法模式的主要作用是让子类决定实例化哪一个类&#xff0c;从而实现对象创建的延迟到具体子类…

2024年6月国产数据库大事记-墨天轮

本文为墨天轮社区整理的2024年6月国产数据库大事件和重要产品发布消息。 目录 2024年6月国产数据库大事记 TOP102024年6月国产数据库大事记&#xff08;时间线&#xff09;产品/版本发布兼容认证代表厂商大事记厂商活动相关资料 2024年6月国产数据库大事记 TOP10 2024年6月国…

【Python】已解决:SyntaxError invalid syntax

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;SyntaxError invalid syntax 一、分析问题背景 在Python编程中&#xff0c;SyntaxError: invalid syntax是一个常见的错误&#xff0c;它通常表示代码中存在语法…

案例|水上水下一体化测量,为九寨沟精准把脉

​ 九寨沟&#xff0c;被誉为“人间仙境”&#xff0c;其湖群以独特的地理位置和优美的自然景观吸引着世界各地的游客&#xff0c;更是九寨沟生态系统中不可或缺的重要组成部分。因此&#xff0c;精准地掌握湖群的地形数据、水体分布及变化情况&#xff0c;能够揭示水下生态系…

【数据结构与算法基础】算法复杂度

欢迎光顾我的homepage 前言 算法就是定义良好的计算过程&#xff0c;它取一个活一组的值输入&#xff0c;并产生出一个或一组值作为输出。简单来说&#xff0c;算法就是一系列的计算步骤&#xff0c;用来将输入数据转化成输出结果。 一、算法效率 如何去衡量一个算法的好坏&am…

[C++]——同步异步日志系统(3)

同步异步日志系统 一、日志系统框架设计1.1模块划分1.1.1 日志等级模块1.1.2 日志消息模块1.1.3 日志消息格式化模块1.1.4 日志落地模块&#xff08;日志落地的方向是工厂模式&#xff09;1.1.5 日志器模块&#xff08;日志器的生成是建造者模式&#xff09;1.1.6 异步线程模块…

强化学习总结(有具体代码实现)

文章目录 第一部分 强化学习基础第1章 强化学习概述1.1 强化学习概念1.2 强化学习的环境1.3 强化学习的目标1.4 强化学习的数据 第2章 多臂老虎机问题&#xff08;MAB问题&#xff09;2.1 问题描述2.1.1 问题定义2.1.2 形式化描述2.1.3 累积懊悔2.1.4 估计期望奖励 2.2 解决方法…

【机器学习】必会数学知识:一文掌握数据科学核心数学知识点(上),值得收藏~

核心数学知识点 1、引言2、数据科学必会数学知识2.1 线性代数2.2 微积分2.3 概率论2.4 数理统计2.5 随机过程2.6 数据分布2.7 贝叶斯统计2.8 线性回归2.9 逻辑回归2.10 矩阵分解2.11 主成分分析&#xff08;PCA&#xff09;2.12 奇异值分解&#xff08;SVD&#xff09; 3、总结…

【人工智能大语言模型技术发展研究报告 2024】

文末‍有福利&#xff01; 人工智能作为引领新一轮科技产业革命的战略性技术和新质生产力重要驱动力&#xff0c;正在引发经济、社会、文化等领域的变革和重塑&#xff0c;2023 年以来&#xff0c;以 ChatGPT、GPT-4 为代表的大模型技术的出台&#xff0c;因其强大的内容生成及…

提升教师健康,聚焦智慧校园人事系统的职工体检功能

智慧校园人事管理系统内置的职工体检管理&#xff0c;是专为教职员工设计的一项健康管理创新实践&#xff0c;巧妙融合先进信息技术&#xff0c;致力于为教职工提供更加便捷、易懂且持续性的健康检查与管理支持。该服务从多个维度出发&#xff0c;全面呵护教职工的身心健康。 该…

给你的博客加上评论区

一个网站如果有评论功能&#xff0c;可以更好的和读者互动。VuePress 也有很多评论插件&#xff0c;这里简单介绍下&#xff0c;最后介绍本站所使用的 Twikoo。 大部分评论插件都是使用的 Github 或 Gitee 的 issue 功能&#xff0c;也就是用 issue 去存储评论&#xff1b;而 …

脚本实现保留文本中特定字符之后的字符串

#目的背景 原始txt文本如下图 目的是为了去除序号&#xff0c;每行只单独呈现域名 手工删除漫长又麻烦&#xff0c;使用脚本快捷些 代码实现逻辑&#xff1a; 1.使用open函数打开文本&#xff0c;之后用变量lines存储文本的所有行&#xff0c;使用for循环&#xff0c;让变量te…