C/C++语言基础--指针三大专题详解3,完结篇(包括指针做函数参数,函数指针,回调函数,左右法则分析复杂指针等)

news2025/2/23 11:10:32

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • 指针是C/C++的灵魂,和内存地址相关联,运行的时候速度快,但是同时也有很多细节和规范要注意的,毕竟内存泄漏是很恐怖的
  • 指针打算分三篇文章进行讲解,本专题是三,完结篇,介绍了指针做函数参数,函数指针,回调函数,左右法则解决复杂指针等问题
  • 专题一:指针三大专题详解1(包含常见错误,代码均可运行)
  • 专题二:指针三大专题详解2(指针与数组关系,动态内存分配,代码均可)
  • 制作不易,欢迎收藏+点赞+关注,本人会持续更新

文章目录

  • 指针高级
    • 指针做函数参数
    • 指针做函数返回值
    • 函数指针
      • 函数指针定义
      • 函数指针使用
      • 案例
        • 计算器
        • 函数指针作为转换表
    • typedef
    • 回调函数
  • 如何看懂复杂的指针
    • 右左法则
    • 案例走起
        • 1.int (\*p[5])(int\*)
        • 2. int (\*fun)(int \*p,int (\*pf)(int \*))
        • 3. int (\*(\*fun)[5])(int \*p)
        • 4. int (\*(\*fun)(int \*p))[5]
        • 5. int(\*(\*fun())())()
    • 总结
      • **指针变量有两种类型**
        • 指针变量的类型
        • 指针变量指向的对象的类型
      • **注意事项:**

指针高级

指针做函数参数

学习函数的时候,讲了函数的参数都是值拷贝,在函数里面改变形参的值,实参并不会发生改变。

在这里插入图片描述

如果想要通过形参改变实参的值,就需要传入指针了。

在这里插入图片描述

注意:虽然指针能在函数里面改变实参的值,但是函数传参还是值拷贝。不过指针虽然是值拷贝,但是却指向的同一片内存空间。

在这里插入图片描述

指针做函数返回值

返回指针的函数,也叫作指针函数。

和普通函数一样,只是返回值类型不同而已,先看一下下面这个函数,非常熟悉对不!

int fun(int x,int y);

接下来看另外一个函数声明

int* fun(int x,int y);

这样一对比,发现所谓的指针函数也没什么特别的。

注意:

  • 不要返回临时变量的地址
  • 可以返回动态申请的空间的地址
  • 可以返回静态变量和全局变量的地址

函数指针

如果在程序中定义了一个函数,那么在运行时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

函数指针定义

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

  • “函数返回值类型”表示该指针变量所指向函数的 返回值类型;

  • “函数参数列表”表示该指针变量所指向函数的参数列表。

那么怎么判断一个指针变量是指向变量的指针,还是指向函数的指针变量呢?

  • 看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。

  • 函数指针没有++和 --运算

函数指针使用

定义一个实现两个数相加的函数。

int add(int a,int b)
{
    return a+b;
}
int main()
{
    int (*pfun)(int,int) = add;
    int res = pfun(5,3);
    printf("res:%d\n",res);
    
    return 0;
}

在给函数指针pfun赋值时,可以直接用add赋值,也可以用&add赋值,效果是一样的。

在使用函数指针时,同样也有两种方式,1,pfun(5,3); 2,(*pfun)(5,3)

案例

计算器

用函数指针实现一个简单的计算器,支持+、-、*、/、%

//plus sub multi divide mod		//加 减 乘 除 取余

当功能太多时,switch语句太长,因此不是一种好的编程风格。好的设计理念应该是把具体的操作和和选择操作的代码分开。

函数指针作为转换表

转换表就是一个函数指针数组。

#include<stdio.h>
#include<math.h>

// 转换表
// 转换表 step1:
//(1.1)声明 转台转移函数
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
double hypotenuse(double, double);
//(1.2)声明并初始化一个函数指针数组    pfunc:数组   数组元素:函数指针  返回值:double型数据
double(*pfunc[])(double, double) = { add, sub, mul, div, hypotenuse };//5个转移状态

//状态转移函数的实现
double add(double a, double b){	return a + b;}
double sub(double a, double b){ return a - b; }
double mul(double a, double b){ return a * b; }
double div(double a, double b){ return a / b; }
double hypotenuse(double a, double b){ return sqrt(pow(a, 2) + pow(b, 2)); }

void test()
{
	//转换表 step2:调用 函数指针数组
	int n = sizeof(pfunc) / sizeof(pfunc[0]);//转移表中 包含的元素个数(状态转移函数个数)
	for (int i = 0; i < n; ++i){
		printf("%.2lf\n",pfunc[i](3, 4));
	}
}
int main()
{
	test();
	return 0;
}

typedef

一,使用typedef为现有类型创建别名,给变量定义一个易于记忆且意义明确的新名字。

  • 类型过长,用typedef可以简化一下
typedef unsigned int UInt32
  • 还可以定义数组类型
typedef int IntArray[10];
IntArray arr;				//相当于int arr[10]

二、使用typedef简化一些比较复杂的类型声明。

例如:

typedef int (*CompareCallBack)(int,int);

上述声明引入了PFUN类型作为函数指针的同义字,该函数有两个类型分别为int、int、char参数,以及一个类型为int的返回值。通常,当某个函数的参数是一个回调函数时,可能会用到typedef简化声明。
例如,承接上面的示例,我们再列举下列示例:

int callBackTest(int a,int b,CompareCallBack cmp);

callBackTest函数的参数有一个CompareCallBack类型的回调函数。在这个示例中,如果不用typedef,callBackTest函数声明如下:

int callBackTest(int a,int b,int (*cmp)(int,int));

从上面两条函数声明可以看出,不使用typedef的情况下,callBackTest函数的声明复杂得多,不利于代码的理解,并且增加的出错风险。

所以,在某些复杂的类型声明中,使用typedef进行声明的简化是很有必要的。

回调函数

首先要明确的一点是,函数也**可以作为函数的参数来传递。**当做函数参数传入的函数,称之为 回调函数。

挑战:实现一个与类型无关的查找函数。下期将公布答案。

如何看懂复杂的指针

指针大家都学过了,简单的指针相信大家都不放在眼里,就不再赘述,但是复杂的你能理解吗?能理解指针就学的差不多了,至于如何运用只要你看懂指针就知道应该给它赋什么值,怎么用。

  • 首先咱们一起来看看这个: int (*fun)(int *p)
    • 首先需要分析这个是不是一个指针,如果是,是什么指针?如果不是,那是什么?
      1. 根据(*fun)可知,fun是一个指针
      2. 然后看fun的后面是一个函数参数列表,可以确定是一个指向函数的指针
      3. 指向的函数的返回值是什么类型呢,再回头看看最前面发现是一个int
      4. 最后我们可以根据这个函数指针写出对应的函数

结果如下:

int foo(int *p)
{
    reutrn 0;
}

右左法则

上面我们分析了一个函数指针,那结果是如何得出来的呢?全靠经验吗,NO,其实是有方法的。

这个方法叫做右左法则

  • 右左法则不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的。

  • 右左法则使用:

      1. 首先从最里面的圆括号(应该是标识符)看起,然后往右看,再往左看;
      2. 每当遇到圆括号时,就应该调转阅读方向;
      3. 一旦解析完圆括号里面所有东西,就跳出圆括号;
      4. 重复这个过程知道整个声明解析完毕。
        在这里插入图片描述

案例走起

1.int (*p[5])(int*)

分析:

  1. 首先看p,p结合[]形成数组,然后往左看,发现数组里面存储的是指针,即指针数组
  2. 然后在跳出(),向右看,看到int*,说明这是一个函数指针参数列表,表示指针数组里面存储的是函数指针
  3. 跳出圆括号,往左看,发现函数指针的返回类型是int
  4. 案例如下:
int func1(int* a);
int func2(int* a);
int func3(int* a);
int func4(int* a);
int func5(int* a);

int (*p[5])(int*) = {func1, func2, func3, func4, func5};
2. int (*fun)(int *p,int (*pf)(int *))

解析:

  1. fun与*结合形成指针;
  2. 往后看是一个参数列表,说明是一个函数指针,但是参数里面还有一个函数指针(也称为回调函数)
  3. 往前看可以确定函数指针的返回类型
  4. 案例如下:
int p(int* a);

int f(int* a, int (*pf)(int *));

int (*func)(int*, int (*pf)(int*)) = f;
3. int (*(*fun)[5])(int *p)

解析:

  1. fun与*结合,形成指针;
  2. 往后看发现了一个[5]说明是一个指向数组的指针,注意与案例1的区别。
  3. 再往前看,发现有一个*,说明数组里面存的是指针,即fun是指针数组。
  4. 跳出圆括号往后看,发现了参数列表,说明数组里面存的是函数指针
  5. 再往前看可以确定函数指针的返回类型
  6. 案例如下:
int aa(int* p)
{
    ……
}

void main()
{
    int (*func[5])(int*)
}
4. int (*(*fun)(int *p))[5]

解析:

  1. fun与*结合,形成指针;

  2. 往后看发现了参数列表,说明fun是一个函数指针

  3. 往前看遇到了*说明,函数指针的返回类型是一个指针,是什么指针继续往后解析;

  4. 往后看发现了[5] 说明是一个数组指针,最前面一个int,说明fun这个函数指针的返回类型是一个数组的指针

    类型为int (*)[5]

  5. 案例如下:

int* func1(int *p);
int* func2(int *p);
int* func3(int *p);
int* func4(int *p);
int* func5(int *p);

int (*(*func(int* p)))[5] = {func1, func2, func3, func4, func5};

// 其实这个等价于
int* (*fun[5])(int *p);

// typedef的写法
typedef int (*FUNC)[5];
typedef FUNC (*FUNC1)(int *);   

技巧:typedef写的时候,运用整体思想,一层一层解析。

5. int(*(*fun())())()

解析:

  1. fun与()结合,说明fun是一个函数.
  2. 往前看发现了一个*,说明函数返回类型为指针,什么指针呢?
  3. 往后看发现了参数列表,fun函数返回的是一个函数指针,那这个函数指针的返回类型是什么呢?
  4. 往前看又发现了一个*,说明函数指针返回类型也是一个指针,那这个指针是什么指针呢?
  5. 往后看又发现了一个参数列表,说明是个函数指针,往前看这个函数指针返回的是int类型。
  6. 案例如下:
typedef int (*FUNC)();
typedef FUNC (*FUNC1)();   // 这个时候,还剩下 (*fun())()
typedef FUNC1 (*FUNC2)();  // 这个时候,还剩下 *func()

// 技巧:typedef写的时候,运用整体思想,一层一层解析。

当然这个案例实际中是不会有人使用的,太复杂了,不容易读,但是理解指针还是很好的

总结

实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。**应该用typedef来对声明逐层分解,增强可读性,**提示,在C++的时候增加了应用类型,使得指针更容易读。

指针变量有两种类型

指针变量的类型和指针所指向的对象(如函数指针)的类型

指针变量的类型

只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。

  • int* ptr; //指针的类型是int

  • char* ptr; //指针的类型是char

  • int** ptr; //指针的类型是int**

  • int(*ptr)[3]; //指针的类型是int()[3]

  • int*(*ptr)[4]; //指针的类型是int*(*)[4]

指针变量指向的对象的类型
  • 你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
    • int*ptr; //指针所指向的类型是int
    • char*ptr; //指针所指向的的类型是char
    • int**ptr; //指针所指向的的类型是int*
    • int(*ptr)[3]; //指针所指向的的类型是int()[3]
    • int*(*ptr)[4]; //指针所指向的的类型是int*()[4]

注意事项:

  • 指针变量也是变量,也有存储空间,存的是别的变量的地址。

    • 要注意指针的值,和指向的对象的值得区别,如:int** 与 int* 的区别

    • 普通变量中的内存空间存放的是,数值或字符等。 ----直接存取

    • 指针变量中的内存空间存放的是,另外一个普通变量的地址。----间接存取

  • 连续定义多个指针变量时,容易犯错误,比如:int *p,p1; 只有p是指针变量,p1是整型变量

  • 避免使用为初始化的指针,很多运行错误都是由于这个原因导致的,而且这种错误又不能被编译器检查所以很难被发现,解决方法:初始化为NULL,报错就能很快找到原因,养成习惯。

  • 指针赋值时一定要保证类型匹配,由于指针类型确定指针所指向对象的类型,操作指针是才能知道按什么类型去操作

  • 在用动态分配完内存之后一定要判断是否分配成功,分配成功后才能使用。

  • 在使用完之后一定要释放,释放后必须把指针置为NULL

的值得区别,如:int** 与 int* 的区别

  • 普通变量中的内存空间存放的是,数值或字符等。 ----直接存取

  • 指针变量中的内存空间存放的是,另外一个普通变量的地址。----间接存取

  • 连续定义多个指针变量时,容易犯错误,比如:int *p,p1; 只有p是指针变量,p1是整型变量

  • 避免使用为初始化的指针,很多运行错误都是由于这个原因导致的,而且这种错误又不能被编译器检查所以很难被发现,解决方法:初始化为NULL,报错就能很快找到原因,养成习惯。

  • 指针赋值时一定要保证类型匹配,由于指针类型确定指针所指向对象的类型,操作指针是才能知道按什么类型去操作

  • 在用动态分配完内存之后一定要判断是否分配成功,分配成功后才能使用。

  • 在使用完之后一定要释放,释放后必须把指针置为NULL

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

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

相关文章

LabVIEW多显示器环境下主显示器识别与管理

该程序使用 LabVIEW 图形化编程语言&#xff0c;涉及多显示器环境中主显示器的识别与信息提取。图像显示了两个不同的方法来获取主显示器的信息。 第一部分&#xff1a;方法一——基于显示器位置的主显示器识别 1. 当前监视器识别&#xff1a; 使用“FP.Monitor”属性节点获取…

在AES加密中,设主密钥为“2B 7E 15 16 28 AE D2 A6 AB F7 15 88 09 CF 4F 3C”,试计算迭代第1轮使用的轮密钥。

题解: 1.分析: 第一轮使用的轮密钥是W[4]、W[5]、W[6]、W[7] w[i](4≤i≤43)求法: (1)i不为4的倍数 w[i] = w[i-1] ⊕w[i-4] (2)为4的倍数 w[i]=SubWord(RotWord(w[i-1]))⊕w[i-4]⊕Rcon[i/4] 解释: ①RotWord:将w[i-1]的4个字节循环上移一个字节 ②SubWo…

JVM的类是如何运行的

本文就是讲解 如何将.class文件转换为机器码

U盘中毒文件被隐藏怎么恢复文件?

很多用户都曾经遇到过U盘文件被病毒隐藏的问题&#xff0c;U盘作为拷贝、存储文件最主要的移动存储设备&#xff0c;里面经常存储了重要的文件&#xff0c;如果文件被隐藏了会给用户带来很多麻烦。那么U盘文件被病毒隐藏&#xff0c;应该怎么解决呢&#xff1f;本文列举了有效的…

QT中使用QAxObject类读取xlsx文件内容并显示在ui界面

一、源码 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACEclass MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget *parent nullptr);~MainWindow();pr…

Andrid异步更新UI:Handler(二)深入了解:Message你真的会创建?它是如何子线程和主线程通知?

目录 为什么会有HandlerHandler的原理&#xff0c;以及对象讲解主线程的loop在哪里&#xff0c;为什么主线程loop没有阻塞呢&#xff1f;Looper如何保证唯一Handler为什么会引发内存泄漏呢&#xff1f;Message应该如何创建它&#xff1f; 一、为什么会有Handler 线程分为主线…

软件工程造价师习题练习 18

1.在软件估算过程中&#xff0c;我们主要对项目的规模&#xff0c;质量&#xff0c;进度和成本进行估算 错误 正确 软件估算不对质量进行估算&#xff0c;只对项目的规模&#xff0c;进度&#xff0c;成本进行估算。 答案&#xff1a;错误 2.在使用类比法进行估算时&#xf…

基于单片机的电子指南针设计

摘要&#xff1a;随着科技的进步与发展&#xff0c;尤其是电子技术的推动下社会逐渐向智能化、信息化和人工智能等方面发展。与此同时市场更加需要掌握精确的方位角度信息&#xff0c;服务到生活、产业、出行、航行等各方各面。传统的指南针由于本身的物理特性&#xff0c;抗干…

深度剖析C++string(上篇)

目录 前言 1.C string类 2.string类中的常见构造 3.string类对象的容量操作 4.. string类对象的访问及遍历操作 5. auto和范围for(补充&#xff09; auto关键字 范围for 结束语 前言 C语言我们学习了字符串和字符串的相关函数&#xff0c;在C语言中&#xff0c;字符串是…

【Redis基础1】——使用场景-缓存穿透

目录 1-引言&#xff1a;初始缓存穿透1-1 缓存穿透是什么&#xff1f;(What)1-2 缓存穿透是怎么造成的&#xff1f;(Why) 2- 核心&#xff1a;如何避免缓存穿透&#xff08;How&#xff09;2-1 方案1&#xff1a;缓存空数据2-2 方案2&#xff1a;布隆过滤器2-2-1 布隆过滤器原理…

计算机毕业设计Hive+Hadoop深圳共享单车预测系统 共享单车数据分析可视化大屏 共享单车爬虫 共享单车数据仓库 机器学习 深度学习 PySpark

步骤&#xff1a; 1.Python采集深圳政府公开数据平台的共享单车数据(最大可采集上亿2017-2021数据)&#xff0c;并用百度逆地理编码服务解析经纬度获取位置信息。并将数据上传hdfs&#xff1b; 2.可使用sklearn、卷积神经网络等算法对数据进行分析&#xff0c;对共享单车的订单…

树链剖分学习

1.算法适用&#xff1a;维护树上一段或者某个点的子树权值和&#xff0c;同时支持树上路径大小修改&#xff0c;子树修改 2.实现&#xff1a;将树剖分成一条一条链&#xff0c;重链和轻链。其中&#xff0c;轻链连接非重儿子&#xff0c;重链相反。 3.主体部分&#xff1a; …

Array List 练习(添加手机对象并返回要求的数据)

package ArrayListDemo;import java.util.ArrayList;public class ArrayListDemo7 {public static void main(String[] args) {//1.创建集合对象ArrayList<Phone> list new ArrayList<Phone>();//2.创建手机对象Phone ph1 new Phone("小米",1000);Pho…

06-07 - 文件系统(下)

---- 整理自 王利涛老师 课程 实验环境&#xff1a;宅学部落 www.zhaixue.cc 文章目录 15. 根文件系统15.1 根文件系统的基本概念15.2 挂载根文件系统的三种方式15.3 根文件系统的挂载15.3.1 rootfs 文件系统的概念15.3.2 root 参数分析15.3.3 根目录“ / ” 16. 使用 initrd 作…

智能家居:‌未来生活的崭新篇章

随着科技的飞速发展&#xff0c;‌智能家居正逐渐从概念走向现实&#xff0c;‌为我们的日常生活带来前所未有的便捷与舒适。‌智能家居&#xff0c;‌简而言之&#xff0c;‌就是利用先进的物联网技术、‌人工智能、‌大数据分析等&#xff0c;‌将家中的各种设备连接起来&…

text token通过cross attention来编辑图片

https://zhuanlan.zhihu.com/p/645249016https://zhuanlan.zhihu.com/p/645249016diffusers中的cross-attention实现_diffusers.models.attention.crossattention-CSDN博客文章浏览阅读71次。在models/unets/unet_2d_condition.py中,如何在Unet中将原注意力类替换,Unet中的at…

笔记:Echarts柱状图 实现滚轮条 数据太多

效果&#x1f447;&#x1f447;&#x1f447; 配置&#xff1a;&#x1f447; let option {dataZoom: [{type: "slider",show: true,zoomLock: true,start: 0,end: 20,bottom: 60,height: 10,textStyle: {color: "transparent",fontSize: 9,},fillerColo…

WEB应用服务器 -- Tomcat详解及案例实现

一、Web前端三大核心技术 1.1 HTML HTML&#xff08;HyperText Markup Language&#xff09;超文本标记语言&#xff0c;它不同于一般的编程语言。超文本即超出纯文本的范畴&#xff0c;例如&#xff1a;描述文本颜色、大小、字体等信息&#xff0c;或使用图片、音频、视频等…

Linux 操作系统全览:从文件管理到系统状态掌控

在当今的计算机技术领域&#xff0c;Linux 操作系统以其稳定性、安全性和高度的可定制性而备受青睐。本文将深入探讨 Linux 操作系统的多个重要方面&#xff0c;包括文件管理、文本操作、软件安装、用户和权限管理以及网络和系统状态管理。 一、Linux 文件管理 Linux 的文件系…

哈工深、NUS等联合提出全新信息抽取基准任务:细粒度定位的统一多模态信息抽取...

信息抽取是 NLP 领域长久以来最经典的研究方向之一。信息抽取的研究经历过了多模态抽取阶段&#xff0c;以及大一统的抽取阶段。在大语言模型和多模态时代&#xff0c;信息抽取的发展何去何从&#xff0c;目前社区仍在积极探索中。 近期&#xff0c;由哈工深和新加坡国立大学等…