C/C++:函数指针

news2024/11/16 4:34:26

欢迎来到 破晓的历程的 博客

⛺️不负时光,不负己✈️

文章目录

    • 引言
    • 函数指针的概念
    • 函数指针的实现
      • C语言实现
      • C++实现
    • 函数指针的应用

引言

我们之前学过各种各样指针,今天我们来讨论一下函数指针
我们先分析C和C++不同定义函数指针的方式,然后进一步探讨函数指针的应用。

函数指针的概念

函数指针是一种特殊的指针,它指向的不是变量地址,而是函数的地址。在C和C++等语言中,函数指针允许程序在运行时通过指针调用函数,这提供了编程上的灵活性和动态性。使用函数指针,你可以将函数作为参数传递给其他函数,或者从函数中返回函数地址,从而实现回调(callback)机制、函数表(函数数组)等功能。有了函数指针,我们就可以高效的调用该函数。

函数指针的实现

C语言实现

在C语言中,函数被视为存储在内存中的一段可执行代码,每个函数都有一个唯一的地址。函数指针是一个指针变量,它存储了一个函数的地址。你可以将函数指针用来调用函数,就像你可以使用普通指针来访问变量一样。一般地,我们认为:函数名就是该函数的地址
函数指针的一般声明形式如下:

return_type (*pointer_name)(parameter_type1, parameter_type2, ...);

说明一下

  • return_type:函数指针对应的函数返回值类型。
  • pointer:函数指针的名称
  • parameter_type1:函数要传入的参数类型。

函数指针看起来比较复杂,待到调用该函数时,可以通过函数指针找到函数在内存中的位置,进而完成调用。

函数指针的定义和初始化
要声明和初始化函数指针,首先需要知道要指向的函数的签名(返回类型和参数类型)。然后,你可以声明一个函数指针变量,并将其初始化为指向特定函数的地址。
以下是一个简单的示例:

int add(int a, int b)
{
	return a + b;

}
int main()
{
	//定义一个返回值为int,参数为int,int类型的函数的函数指针
	int(*ptr)(int, int);
	
	ptr = add;//函数名是函数地址,将函数指针指向add函数

	int ret=ptr(10, 20);//然后进行调用
	printf("%d", ret);
}


以下是另外一个C语言指针实现的方式
typedef return_type(*pointer_name)(parameter_type1, parameter_type2, ...);
  • return_type:函数指针对应的函数返回值类型。
  • pointer:函数指针的名称
  • parameter_type1:函数要传入的参数类型。

听起来这和刚刚的实现方式没有什么区别,接下来我们看一个示例

int add(int a, int b)
{
	return a + b;

}
int main()
{
	
	//定义一个返回值为int,参数为int,int类型的函数的函数指针类型
	typedef int(*ptr)(int, int);
	//定义一个函数指针对象
	ptr p1;
    //函数名是函数地址,将函数指针对象指向add函数
	p1 = add;
	//然后进行调用
	int ret=p1(10, 20);
	printf("%d", ret);
	
}

如上两种方式有什么区别呢?

在C语言中,typedef void(*ptr)(int,int);void(*ptr)(int,int); 这两行代码在本质上是有区别的,尽管它们看起来相似,但它们的用途和效果完全不同。

  1. typedef void(*ptr)(int,int);

    这行代码定义了一个新的类型别名ptr,这个别名是一个指向函数的指针类型,该函数接受两个int类型的参数并返回void。使用typedef的目的是为了简化后续的代码,使得在需要声明这种类型的指针时,可以直接使用ptr而不是每次都写出完整的函数指针类型。

    例如,之后你可以这样声明一个变量:

    ptr myFunctionPointer;
    

    这里myFunctionPointer就是一个指向函数的指针,这个函数接受两个int参数并返回void

  2. void(*ptr)(int,int);

    这行代码本身并不定义一个新的类型别名,而是直接声明了一个名为ptr的变量,这个变量是一个指向函数的指针,该函数接受两个int类型的参数并返回void。这里没有使用typedef,所以ptr只是一个具体的变量名,而不是一个可以复用的类型别名。

    如果你只写了这一行代码,那么ptr就是这个特定类型的唯一变量名,你不能再用ptr来声明其他同类型的变量,除非你在另一个作用域内重新声明(这通常不是一个好主意,因为它会导致混淆)。

总结来说,typedef用于定义类型别名,使得代码更加简洁和可重用;而直接声明变量(如void(*ptr)(int,int);)则只是创建了一个具体的变量实例,没有定义新的类型别名。在实际编程中,使用typedef来定义函数指针类型别名是一种更常见和推荐的做法。

C++实现

在C++中,std::function是C++11及以后版本中引入的一个模板类,它提供了一种通用的方式来存储、复制和调用任何可调用实体(Callable),比如函数、Lambda表达式、函数对象、绑定表达式(通过std::bind创建的)以及指向成员函数和指向数据成员的指针。

当你看到这样的代码:

using func = std::function<void()>;

这里定义了一个类型别名func,它是std::function的一个特化版本,专门用于存储和调用没有参数且返回类型为void的可调用实体。

示例

下面是一个简单的示例,展示了如何使用std::function<void()>(通过类型别名func)来存储和调用不同的可调用实体:

#include <iostream>
#include <functional>

// 定义一个类型别名
using func = std::function<void()>;

// 一个普通的函数
void printHello() {
    std::cout << "Hello, World!" << std::endl;
}

// 一个Lambda表达式
auto printLambda = []() {
    std::cout << "Lambda says hello!" << std::endl;
};

// 一个函数对象
struct PrintFunctor {
    void operator()() const {
        std::cout << "Functor says hello!" << std::endl;
    }
};

int main() {
    // 创建一个func类型的变量,并存储一个函数指针
    func f1 = printHello;
    f1(); // 调用printHello

    // 创建一个func类型的变量,并存储一个Lambda表达式
    func f2 = printLambda;
    f2(); // 调用Lambda

    // 创建一个func类型的变量,并存储一个函数对象
    func f3 = PrintFunctor();
    f3(); // 调用函数对象的operator()

    return 0;
}

在这个示例中,func类型被用来存储和调用三种不同类型的可调用实体:一个普通函数、一个Lambda表达式以及一个函数对象。这展示了std::function的灵活性和强大功能。

函数指针的应用

构建函数表

#include <iostream>  

// 定义几个函数  
void func1() { std::cout << "Function 1" << std::endl; }
void func2() { std::cout << "Function 2" << std::endl; }
void func3() { std::cout << "Function 3" << std::endl; }

// 函数指针数组(函数表)  
void (*functions[])() = { func1, func2, func3 };

int main() {
    // 通过索引调用函数  
    functions[1](); // 调用func2  
    return 0;
}

回调函数

#include <iostream>  
#include <algorithm> // 用于std::sort  

// 定义一个回调函数类型  
typedef void (*Callback)(int);

// 一个简单的函数  
void printNumber(int n) {
    std::cout << n << std::endl;
}

// 使用回调函数的函数  作为参数的形式传入
void processNumbers(int numbers[], int size, Callback cb) {
    for (int i = 0; i < size; ++i) {
        cb(numbers[i]);
    }
}

int main() {
    int numbers[] = { 1, 2, 3, 4, 5 };
    processNumbers(numbers, 5, printNumber); // 传递回调函数  
    return 0;
}

实现多态

#include <stdio.h>  
  
// 定义几种行为的函数  
void animalSoundCat() { printf("Meow\n"); }  
void animalSoundDog() { printf("Woof\n"); }  
  
// 结构体,包含函数指针  
typedef struct {  
    void (*makeSound)();  
} Animal;  
  
// 创建并初始化动物  
void createAnimal(Animal *animal, void (*soundFunc)()) {  
    animal->makeSound = soundFunc;  
}  
  
int main() {  
    Animal cat, dog;  
    createAnimal(&cat, animalSoundCat);  
    createAnimal(&dog, animalSoundDog);  
  
    cat.makeSound(); // 输出: Meow  
    dog.makeSound(); // 输出: Woof  
    return 0;  
}

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

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

相关文章

uniapp去除顶部标题栏

相信很多同学和我一样&#xff0c;刚学uniapp的时候想去除自带的这个标题栏不知道如何去除&#x1f92a; 其实很简单&#xff0c;只需两个步骤即可彻底除掉&#xff0c;首先找到项目文件夹下的pages.json路由文件点开&#xff0c;在这个文件里可以看到你创建的所有页面&#x…

git修改提交名字

大家在使用git的时候&#xff0c;有的时候可能不是使用自己的账号&#xff0c;或者说账号的信息不符合自己的预期&#xff0c;具体表现在什么地方呢&#xff1f;在提交代码的时候&#xff0c;名字不是自己的&#xff0c;或者是名字不是自己想要的。 下面就是如何查看和修改。 …

视频智能分析平台LntonAIServer视频质量诊断功能花屏、抖动、遮挡等检测

LntonAIServer新增了视频质量诊断功能&#xff0c;该功能专注于提升视频监控系统的稳定性和可用性&#xff0c;主要通过自动化检测来识别视频流中常见的质量问题&#xff0c;比如花屏、抖动、遮挡等问题。这些问题是影响视频监控效果的主要因素之一&#xff0c;而自动化的检测能…

K8S介绍及Kubeadm方式安装K8S(前期工作)

1.K8S介绍 1.Kubernetes简介 Kubernetes 缩写&#xff1a;K8S&#xff0c;k 和 s 之间有八个字符&#xff0c;所以因此得名。 Kubernetes 由 google 的 Brog 系统作为原型&#xff0c;后经 Go 语言延用 Brog 的思路重写&#xff0c;并捐献给 CNCF 基金会开源。 Kubernetes …

ts函数的参数加一个_是什么意思

先说一下总结&#xff0c;在TypeScript&#xff08;TS&#xff09;和Vue 3项目中&#xff0c;给函数的参数加一个下划线&#xff08;_&#xff09;前缀通常是一种约定或习惯&#xff0c;用来表示该参数在当前函数体内是故意未使用的&#xff0c;需要注意的是&#xff0c;这种做…

电脑开机出现no operation system found错误原因分析及解决方法

最近有网友问我电脑一启动提示&#xff1a;no operation system found&#xff0c;这个提示意思是未找到操作系统。并且出现bios能认别硬盘&#xff0c;快捷启动时找不到硬盘&#xff0c;出现该提示的原因有很多&#xff0c;下面我们来详细分析一下开机出现no operation system…

我的世界桃花源官网源码 游戏官网

我的世界桃花源官网源码 游戏官网 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89714345 更多资源下载&#xff1a;关注我。

【python因果推断库6】使用 pymc 模型的工具变量建模 (IV)1

目录 使用 pymc 模型的工具变量建模 (IV) 使用 pymc 模型的工具变量建模 (IV) 这份笔记展示了一个使用工具变量模型&#xff08;Instrumental Variable, IV&#xff09;的例子。我们将会遵循 Acemoglu, Johnson 和 Robinson (2001) 的一个案例研究&#xff0c;该研究尝试解开…

MemLong: 长文本的新记忆大师,可将上下文长度从4k提升到80k!

这篇文章介绍了一个名为MemLong的模型&#xff0c;它通过使用外部检索器来增强长文本建模的能力。MemLong结合了一个不可微的检索-记忆模块和一个部分可训练的解码器-仅语言模型&#xff0c;并引入了一种细粒度、可控的检索注意力机制&#xff0c;利用语义级别的相关块。在多个…

SpringBoot后端快速搭建

SpringBoot 开发环境构建 首先创建一个maven项目 在pom.xml文件中添加以下依赖 <!-- 依赖的父级工程 --> < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > &l…

本地Gitblit使用

首先创建一个本地的gitblit的服务&#xff0c;创建流程如下&#xff1a; 【GitBlit】Windows搭建Git服务器详细教程_搭建gitblit服务-CSDN博客 GitBlit的使用教程-CSDN博客 创建好一个仓库后&#xff0c;分配好用户权限&#xff0c;再将项目拉下来&#xff0c;这里是再visua…

第二证券:什么是券商理财,券商理财有风险吗?

券商理财是指证券公司发行的理财产品&#xff0c;证券公司简称为券商&#xff0c;证券公司集结出资者资产主张建立的资产处理升值类产品便是券商理财产品。 券商理财产品中主要有质押式报价回购事务、收益凭证、券商资产处理计划三种。 1、质押式报价回购事务 是证券公司将契…

云计算和传统IT相比,有哪些优势?

云计算相比于传统的IT基础设施&#xff0c;具有以下一些显著的优势&#xff1a; 成本效益&#xff1a; 云计算通常采用按需付费模式&#xff0c;用户只需为实际使用的资源支付费用&#xff0c;避免了高昂的前期硬件投资和维护成本。 弹性计费方式使得企业可以根据业务需求灵活调…

如何做好API安全

在数字化时代&#xff0c;API&#xff08;应用程序接口&#xff09;已成为企业间、应用程序间乃至整个数字生态系统中数据交换与功能集成的核心&#xff0c;可 帮助跨多个设备互连多个应用程序或软件系统&#xff0c;定义它们可以发出的调用或请求的种类、调用的方式、应使用的…

C#复习封装_运算符重载

知识点一 基本概念 知识点二 基本语法 知识点三 实例 知识点四&#xff1a;使用 知识点五&#xff1a;可重载和不可重载的运算符 可重载运算符 算数运算符 #region 算数运算符//注意 符号需要两个参数还是一个参数public static Point operator -(Point p1,Point p2){retur…

【Flutter】Flutter安装和配置(mac)

1、准备工作 升级Macos系统为最新系统安装最新的Xcode电脑上面需要安装brew https://brew.sh/安装chrome浏览器&#xff08;开发web用&#xff09; 2.、下载flutter https://docs.flutter.dev/release/archive?tabmacos 大家网页后&#xff0c;选择对应的版本【Tips&#x…

VXLAN 为何采用UDP

VXLAN 简介 VXLAN是一种网络虚拟化技术&#xff0c;它通过在UDP数据包中封装MAC地址和IP信息&#xff0c;使得二层网络&#xff08;如以太网&#xff09;能够跨越三层网络&#xff08;如IP网络&#xff09;进行扩展。这种封装方式不仅支持TCP流量的传输&#xff0c;还能有效处…

Charles - 夜神模拟器证书安装App抓包-charles监控手机出现unknown 已解决

1.Openssl安装 http://slproweb.com/products/Win32OpenSSL.html exe下载安装后进行配置 新建系统变量OPENSSL_HOME&#xff0c;变量值设为(绝对路径)软件安装目录下的bin 直接浏览 编辑用户变量path&#xff0c;新建%OPENSSL_HOME%&#xff0c;最后点击确定 查看openssl版本&a…

读懂以太坊源码(4)-详细解析节点配置文件geth.toml

要读懂以太坊源码&#xff0c;先熟悉配置文件的每个配置项也是非常有必要的&#xff0c;以下代码是以太坊主网配置文件(geth.toml)的完整内容&#xff0c;后面是对每个配置项的说明&#xff1a; [Eth] NetworkId 0 SyncMode "snap" EthDiscoveryURLs [] SnapDisc…

Redis的持久化机制RDB与AOF

RDB RDB 是将 Redis 的内存中的数据定期保存到磁盘上&#xff0c;以防止数据在 Redis 进程异常退出或服务器断电等情况下丢失。 RDB 的优点是&#xff1a;快照文件小、恢复速度快&#xff0c;适合做备份和灾难恢复。 RDB 的缺点是&#xff1a;定期更新可能会丢数据&#xff0…