Modern C++ 函数发展:从函数指针到匿名函数

news2024/9/28 1:17:30

函数作为最小的代码单元,在C++这个大杂烩中,可以跟很多特性结合,较为复杂,本文讲解C++中函数是如何一步步演变的。

函数

跟C中类似,将一部分代码封装起来,方便进行复用。

#include <iostream>

int add(int a, int b){ //一个最简短的函数
	return a+b;
}

上述只是在进行两个int操作时的实现,如果想要再新增int和float操作呢?难道要用switch?我们可以进行函数重载:

C++中的函数重载(Function Overloading)是一种允许在相同作用域内定义多个同名函数的能力,只要这些函数的参数列表(参数的类型、数量或顺序)不同即可。函数重载是编译时多态的一种形式,它使得编译器可以根据调用时提供的参数类型和数量来确定应该调用哪个函数。

#include <iostream>

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

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

int main(){
	int a = 0;
	int b = 1;
	float c = 2.0;
	std::cout << add(a,b) << add(b,c) <<std::endl;
	return 0;

C++跟C一样,为了更好利用函数,也有函数指针的概念。函数指针,本质还是指针,只不过这个指针指向一个函数。当我们想要进一步对代码进行封装,想要实现一个函数对应多个操作时,我们可以选择给函数加入一个Flag,根据flag进行switch,但是这样并不美观,且当新增操作时,需要在原函数中进行修改,使得代码不容易维护,这时我们就可以使用函数指针,为函数传入一个函数指针,该指针指向实际执行的函数。

#include <iostream>

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

int subtract(int a, int b){
    return a - b;
}

enum class Operation{
    ADD,
    SUBTRACT
};

int my_operator1(int a, int b, Operation operation){ //不容易维护,新增加一个操作就要修改函数
    switch(operation){
        case Operation::ADD:
            return add(a, b);
        case Operation::SUBTRACT:
            return subtract(a, b);
    }
}

int my_operation(int a, int b, int (*operation)(int, int)){
    return operation(a, b);
}

int main(){
    int a = 5;
    int b = 3;
    int result;
    int (*func_ptr)(int, int) = add;

    result = my_operation(a, b, func_ptr);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

函数模板

C++98就有的特性。上面我们只使用了int输入,如果我们想要一个函数可以接收多个输入,那就需要函数模板登场了。

#include <iostream>

template <typename T>
T add(T a, T b){
    return a + b;
}

template <typename T>
T subtract(T a, T b){
    return a - b;
}

template <typename T>
T my_operation(T a, T b, T (*operation)(T, T)){
    return operation(a, b);
}

int main(){
    int a = 5;
    int b = 3;
    int result;
    int (*func_ptr)(int, int) = add;

    result = my_operation(a, b, func_ptr);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

如果我们又突然想要为一种输入执行特定操作怎么办呢?例如我们不想让调用者使用double类型,是不是又要switch了呢?为了解决这个问题,可以使用模板特化操作:

C++中模板特化分为全特化和偏特化,但是只有类模板才支持偏特化,函数只支持全特化。所谓偏特化,即:在有多个模板参数时,允许为一个特殊模板参数进行特殊实现,而其他参数不受限制,全特化就是指:必须指定全部模板参数为指定参数的特化。
例如:两个模板参数T和U,偏特化指可以实现一个<int, U>的特殊实现,而全特化只能为<int,int>做一个特殊实现。

#include <iostream>

template <typename T>
T add(T a, T b){
    return a + b;
}

template <typename T>
T subtract(T a, T b){
    return a - b;
}

template <typename T>
T my_operation(T a, T b, T (*operation)(T, T)){
    return operation(a, b);
}

template<> //模板全特化
double my_operation<double>(double a, double b, double (*operation)(double, double)){
    std::cout <<" suggest to use float instead of double" << std::endl;
    return operation(a, b);
}
// 模板函数也可以进行重载,
//如果不加<double>,编译器会将其理解为进行了函数重载,也可以正常编译运行。

int main(){
    int a = 5;
    int b = 3;
    int result;
    int (*func_ptr)(int, int) = add;

    result = my_operation(a, b, func_ptr);
    std::cout << "Result: " << result << std::endl;

    double c = 5.5;
    double (*func_ptr2)(double, double) = add;
    double result2 = my_operation(c, c, func_ptr2);
    return 0;
}

模板编程在STL库中应用非常多,容器大多都使用了类模板,模板特化在STL库中也有应用,vector<bool>就应用了模板特化,可以尝试一下,会发现sizeof(vector<T>)输出都是24大小,但是对于vector<bool>的结果就不一样,这是因为vector<bool>使用了模板特化,实现跟其他的vector不一样。

#include <iostream>
#include <vector>

int main(){
    std::vector<bool> test_bool(30);
    std::vector<int> test_int(30);
    std::vector<float> test_float(30);

    std::cout << "Size of test_bool: " << sizeof(test_bool) << std::endl;
    std::cout << "Size of test_int: " << sizeof(test_int) << std::endl;
    std::cout << "Size of test_float: " << sizeof(test_float) << std::endl;
    return 0;
}

结果:
在这里插入图片描述

其他的vector是24是因为vector的内部实现是三个指针,一个指向数据存储区域的开始,一个指向数据存储区域的末尾,一个指向末尾元素的下一个空位。64位电脑上,一个指针是8字节,24是三个指针的大小。

如果是我们自己调用,我们知道add函数的输入需要必须能够进行+操作,但是如果是更复杂的函数,要求输入能够进行其他更为复杂的操作呢?如何确保调用者知道需要符合什么样条件的模板参数呢?C++20引入了概念concepts特性和requires表达式,能够定义模板参数需要满足的条件。

在C++20以前,可以用SFINAE技术来实现差不多的效果,具体后续用到再学。
除了上述提到的,C++模板还有变长模板参数特性,非类型模板参数特性等等,用到再学。

仿函数

前面我们提到了C++中的函数重载,C++中还提供了操作符重载,那么我们是否可以把()操作符重载一下呢?答案是可以的,那重载了()操作符的类,是不是也能像调用函数一样,直接ClassA()呢?答案是可以的,这就是仿函数:

C++中的仿函数(Functor)是一种特殊的对象,它允许对象模仿函数的行为。仿函数通常重载了函数调用运算符operator(),使得该对象可以像函数一样被调用。

仿函数的作用有很多:

  • 可以保存状态信息:之前我们提到函数是最小的代码单元,实际上,但是之前的函数只是一段代码的复用,不包括状态信息,对于一些特殊的函数,我们需要其包括状态信息,例如深度学习中的某些算子,其中需要存储一些如阈值之类的状态信息,就可以使用仿函数来实现。
#include <iostream>
#include <vector>
#include <math.h>

class BatchNorm{
    public:
        BatchNorm(float epsilon=1e-5,float gamma=0.1,float beta=0.1) : 
            epsilon(epsilon), gamma(gamma),beta(beta)
            {}

        std::vector<float> operator()(std::vector<float> a);
    private:
        float epsilon;
        float gamma;
        float beta;
};

std::vector<float> BatchNorm::operator()(std::vector<float> a){
    std::vector<float> result;
    int sum = 0,square_sum = 0;
    int mean = 0,var=0;
    for(auto i : a){
        sum += i;
    }
    mean = sum / a.size();
    for(auto i : a){
        square_sum += (i - mean) * (i - mean);
    }
    var = square_sum / a.size();

    for(auto i : a){
        result.push_back(beta*(i - mean) / sqrt(var + epsilon) + gamma);
    }
    return result;
}

int main(){
    std::vector<float> a = {1,2,3,4,5};
    BatchNorm batch_norm;
    std::vector<float> result = batch_norm(a);
    for(auto i : result){
        std::cout << i << std::endl;
    }
    return 0;
}

lambda匿名函数

lambda匿名函数是C++11中新增的特性,其实现是一个匿名仿函数,lambda只不过是一个语法糖。
lambda语法如下:

//语法:
//[ 捕获 ] <模板形参>(可选)(C++20) ( 形参 ) 说明符(可选) 异常说明 attr -> ret requires(可选)(C++20) { 函数体 }	

lambda的例子:

#include <iostream>

int main(){
    int a = 5;
    int b = 3;
    constexpr int c = 4;
    const int e = 6; //常量不需要捕获,可以直接使用
    static int d = 7; //静态变量不需要捕获,可以直接使用,且可以修改

    // 这种用auto的方式定义lambda表达式,是C++14的特性,被称为泛型lambda表达式
    auto add = [](int a, int b) -> int{
        return a + b;
    };
    int result = add(a, b);
    std::cout << "Result: " << result << std::endl;

    // 默认捕获方式是[=],表示以值的方式捕获外部变量,如果要以引用的方式捕获外部变量,可以使用[&]
    // 下面等同于[&a, b],表示以引用的方式捕获a,以值的方式捕获b
    [&, b]() mutable {
        a++; //a是以引用的方式捕获的,所以可以修改
        b++; //有了mutable关键字,就可以修改值捕获的变量,否则表达式是一个右值,无法修改
    }();
    std::cout << "a: " << a << std::endl;
    std::cout << "b: " << b << std::endl; //值捕获的变量,即使有mutable,也只是在lambda表达式内部修改,外部不会改变

    [=]() { //默认是以值的方式捕获外部变量,没有添加mutable,不能修改否则会报错
        // a++;
        std::cout << "a: " << a << std::endl;
        std::cout << "b: " << b << std::endl;
    }();

    return 0;
}

没有任何捕获的,单纯被用作匿名函数。如果有捕获,那lambda就成了闭包,带有自己的状态信息。
之前写过一篇关于lambda和闭包的文章:Modern C++中的闭包与匿名函数

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

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

相关文章

【Nuxt】404 页面 和 嵌套路由

404 页面 只需要一个动态参数组件的页面即可&#xff08;slug 也可以是其他字符串&#xff09;&#xff0c;比如&#xff1a; 此时包含三个页面组件&#xff1a;/about, /about/about-fdsfsdf, /about/fdsf/fdsfs/fsdfsfsdf/…(404) 。 具体 404 路由页面可以写成某个路径下的…

【正点原子i.MX93开发板试用连载体验】中文提示词的训练

本文首发于电子发烧友论坛&#xff1a;【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! 好久没有更新了&#xff0c;今天再来更新一下。 我们用前面提到的录音工具录制了自己的中文语音&#…

销量激增难解奇瑞焦虑:新能源短板与加班文化引争议

尽管今年上半年奇瑞汽车以惊人的48.4%同比增长率&#xff0c;力压比亚迪、吉利等竞争对手&#xff0c;成为国内汽车销量增速最快的企业&#xff0c;但这光鲜的增长数字背后&#xff0c;却隐藏着难以忽视的焦虑与困境。 奇瑞汽车的销量飞跃&#xff0c;主要得益于燃油车市场的强…

短信平台营销:解锁市场潜力的金钥匙

在瞬息万变的营销领域&#xff0c;短信平台以其直接、高效、覆盖面广的特性&#xff0c;成为众多企业争相探索的蓝海。面对中国近9亿的手机用户群体&#xff0c;这一庞大的市场潜力无疑为企业开启了无限商机的大门。 一、塑造品牌&#xff0c;提升知名度 短信平台营销是…

ProvenCore经过形式化验证的TEE OS,最高级别的安全认证CC EAL 7

这是一个闭源的TEE OS&#xff0c;但其ATF组件已提交到upstream。 实现安全功能需要硬件的高级抽象&#xff0c;通常由操作系统 (OS) 提供。由于安全功能的正确性取决于这些高级抽象的正确性&#xff0c;因此实现这些功能的操作系统应该没有可利用的漏洞。 ProvenRun 开发了 …

JavaEE---Spring MVC(3)

11.获取session 法1> 传统方式 初始状态下session为空,那么我们就需要手动设置session 2>不传统方式 但是!!!我们发现报错了! 解决方式: 设置session之后可以发现代码的session就被查到了 12.获取header 1>传统方式 这里我们规定只返回浏览器的信息 2>不…

【CodinGame】趣味算法(教学用) CLASH OF CODE -20240804

文章目录 正文写在最后END 正文 import math import sys# Auto-generated code below aims at helping you parse # the standard input according to the problem statement.n int(input()) j 1 h 0for i in range(1, n 1):j * ih iprint(j) print(h)import sys import m…

推荐一款界面优雅、功能强大的 .NET + Vue 权限管理系统

目录 前言 项目简介 项目特点 项目预览 项目演示 1、系统登录 2、系统首页 3、系统页面 4、插件示例 5、移动端 项目地址 总结 前言 今天推荐一款用 .NET 和 Vue3 实现的开源权限管理系统。它的界面清爽干净&#xff0c;功能强大&#xff0c;还具备灵活的角色权限分配…

人工智能在病理组学领域的最新研究进展|顶刊速递·24-08-05

小罗碎碎念 本期推文主题&#xff1a;人工智能在病理组学领域的最新进展 今天的推文主要涉及三个癌种——结直肠癌、肾乳头状细胞癌、上皮性卵巢癌。既有淋巴结转移的风险预测模型&#xff0c;也有结合了测序数据的多模态病理AI模型。 重点关注一下第六篇文献&#xff0c;由西…

本地化AI语音聊天voicechat2;改进版的Whisper模型,速度快50%;多代理创建儿童图画书

✨ 1: voicechat2 voicechat2是一款使用WebSockets进行快速、本地化AI语音聊天的软件。 Voicechat2 是一个快速、完全本地化的AI语音聊天系统&#xff0c;采用WebSockets技术。它运行在高性能硬件上&#xff0c;例如7900级的AMD RDNA3显卡或4090显卡&#xff0c;可以实现低至…

最全面的Python重点知识汇总,建议收藏!

Py2 VS Py3 print成为了函数&#xff0c;python2是关键字 不再有unicode对象&#xff0c;默认str就是unicode python3除号返回浮点数 没有了long类型 xrange不存在&#xff0c;range替代了xrange 可以使用中文定义函数名变量名 高级解包 和*解包 限定关键字参数 *后的变…

【区块链+医疗健康】国家儿童医学中心互联网 + 肾脏专科联盟服务平台 | FISCO BCOS应用案例

医疗资源结构的失衡在儿科领域尤为突出&#xff0c;供需矛盾突出。由于肾脏病等疑难病的特殊性&#xff0c;加之儿童疾病诊断的 复杂性&#xff0c;其诊治过程可谓“难上加难”。一些基层儿童医院由于缺乏专业的医疗团队、特殊的辅助检查手段以及 基因测序等前沿技术的支撑&…

代码随想录算法训练营第二十四天| 455.分发饼干, 376. 摆动序列 , 53. 最大子序和

今天是贪心算法学习的第一天&#xff0c;主要的学习内容有&#xff1a;贪心算法的理论基础&#xff0c;以及如何通过局部最优解推导全局最优解。 对于贪心的理论基础&#xff0c;贪心算法并没有固定的模板和套路&#xff0c;对于贪心算法的题目其实就是一种模拟题&#xff0c;…

从常春藤精英到阶下囚:加密货币行业丑闻频发,责任在「名校情节」?

撰文&#xff1a;Muyao Shen&#xff0c;彭博社 编译&#xff1a;Yangz&#xff0c;Techub News 似乎每当加密货币行业出现丑闻时&#xff0c;涉案者大都有着常春藤联盟大学或其他名校学位。本文简单整理了加密货币行业的「脏衣篓」&#xff0c;翻出了好多名校 T 恤。 就在本周…

UE5 UC++流式请求 || Varest流式调用Coze(但一次性回复,不太推荐)

文章仅供参考。C调用的是公司后台的接口&#xff0c;博主未尝试用C调用Coze的接口&#xff0c;需要补充更多的代码。 ———— C #include "HttpModule.h" #include "HttpManager.h" #include "Http.h"DECLARE_DYNAMIC_MULTICAST_DELEGATE_O…

星座运势网源码/星座屋接口/星座配对网站PHP程序带采集

星座运势网源码/星座屋接口/星座配对网站PHP程序带采集 演示站&#xff1a; https://xz.wengu8.com/ 程序说明&#xff1a; 1、前端模板PC手机端自适应。 2、每日运势/当月/当年星座运势调用星座屋API接口&#xff0c;每天只采集一次接口&#xff0c;后保存到本地调用本地…

ThreeJs控制模型骨骼实现数字人

之前章节有讲过ThreeJs加载pmd模型和vmd动作文件&#xff0c;实现动画人物根据vmd中的动作跳舞&#xff0c;不过缺点是只能按照文件中指定动作跳舞&#xff0c;如果要让一个模型做出多种动作的话&#xff0c;就需要做很多个动作文件&#xff0c;如果动作文件很多&#xff0c;加…

算力共享:forward_to_next_shard,process_prompt推断之间的链接map_partitions_to_shards

目录 forward_to_next_shard 参数 函数逻辑 _process_prompt StandardNode get_current_shard map_partitions_to_shards forward_to_next_shard 这段代码定义了一个名为 forward_to_next_shard 的异步函数,它是设计用于在分布式模型或数据处理系统中的节点(或称为“分…

SpringBoot基本原理,轻松应对面试官 - 第522篇

《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 《国内最全的Spring Boot系列之七》 Noisee AI中…

C语言自定义类型联合体与枚举超详解

文章目录 1. 联合体1. 1 联合体类型的声明1. 2 联合体的特点1. 3 相同成员的结构体和联合体对比1. 4 联合体大小的计算1. 5 联合体的练习 2. 枚举2. 1 枚举类型的声明2. 2 枚举类型的优点2. 3 枚举类型的使用2. 4 枚举类型的实际使用 1. 联合体 1. 1 联合体类型的声明 像结构…