从C向C++26——C++11(3)

server/2024/11/11 5:35:42/

一.可调用对象

1.可调用对象

可调用对象分为四类:

  • 是一个函数指针

    int print(int a, double b)
    {cout << a << b << endl;return 0;
    }
    // 定义函数指针
    int (*func)(int, double) = print;
    //使用using起别名来定义函数指针,注意把using起的别名当作一个类,使用时通过构造一个实例对象指向相应的函数
    using func_ptr = int (*)(int, double)

    知识点1:函数指针的格式,返回值类型 (*指针名)(参数类型列表)

    知识点2:使用uisngtypedef起别名

  • 是一个具有operator()成员函数的类对象(仿函数)

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;struct Test
    {// ()操作符重载void operator()(string msg){cout << "msg: " << msg << endl;}
    };int main(void)
    {Test t;t("我是要成为海贼王的男人!!!");	// 仿函数return 0;
    }

    知识点1:重载操作符()(仿函数)

  • 是一个可被转换为函数指针的类对象

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;using func_ptr = void(*)(int, string);
    struct Test
    {static void print(int a, string b){cout << "name: " << b << ", age: " << a << endl;}// 将类对象转换为函数指针operator func_ptr(){return print;}
    };int main(void)
    {Test t;// 对象转换为函数指针, 并调用t(19, "Monkey D. Luffy");return 0;
    }
    

    知识点1:operator不仅可以重载操作符,还可以表示类型转化的意思,这时候其语法为

    operator 目标类型() const;
    

    因为这里的目标类型是一个函数指针,所以返回一个函数。

    注意点1:这里并不是重载()而构成的仿函数注意区分。

  • 是一个类成员函数指针或者类成员指针

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;struct Test
    {void print(int a, string b){cout << "name: " << b << ", age: " << a << endl;}int m_num;
    };int main(void)
    {// 定义类成员函数指针指向类成员函数void (Test::*func_ptr)(int, string) = &Test::print;           //这里的&可有可无,因为函数名本身就是地址// 类成员指针指向类成员变量int Test::*obj_ptr = &Test::m_num;Test t;// 通过类成员函数指针调用类成员函数(t.*func_ptr)(19, "Monkey D. Luffy");// 通过类成员指针初始化类成员变量t.*obj_ptr = 1;cout << "number is: " << t.m_num << endl;return 0;
    }
    

    知识点1:类成员函数指针或者类成员指针

2.包装器

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

std::function必须要包含一个叫做functional的头文件,可调用对象包装器使用语法如下:

#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;

详细用法:

#include <iostream>
#include <functional>
using namespace std;int add(int a, int b)
{cout << a << " + " << b << " = " << a + b << endl;return a + b;
}class T1
{
public:static int sub(int a, int b){cout << a << " - " << b << " = " << a - b << endl;return a - b;}
};class T2
{
public:int operator()(int a, int b){cout << a << " * " << b << " = " << a * b << endl;return a * b;}
};int main(void)
{// 绑定一个普通函数function<int(int, int)> f1 = add;// 绑定以静态类成员函数function<int(int, int)> f2 = T1::sub;// 绑定一个仿函数T2 t;function<int(int, int)> f3 = t;// 函数调用f1(9, 3);f2(9, 3);f3(9, 3);return 0;
}

在上面的类对象转化为函数指针后,也可以利用包装器进行封装。

3.回调函数

#include <iostream>
#include <functional>
using namespace std;class A
{
public:// 构造函数参数是一个包装器对象A(const function<void()>& f) : callback(f){}void notify(){callback(); // 调用通过构造函数得到的函数指针}
private:function<void()> callback;
};class B
{
public:void operator()(){cout << "我是要成为海贼王的男人!!!" << endl;}
};
int main(void)
{B b;A a(b); // 仿函数通过包装器对象进行包装a.notify();return 0;
}

callback 是一个 std::function<void()> 类型的对象,这意味着它可以存储任何没有参数且返回类型为 void 的可调用对象。在类 A 的构造函数中,callback 被初始化为传入的 function<void()> 参数,这样它就可以在之后被调用。

std::function 的这种能力使得它非常适合用于回调机制,因为它提供了一种统一的方式来处理不同的可调用对象。在您的例子中,B 类定义了一个重载了 () 运算符的成员函数,这使得 B 的对象可以像函数一样被调用,成为一个仿函数(functor)。当您创建 A 类的对象 a 并将 B 类的对象 b 作为参数传递给 A 的构造函数时,b 被自动转换为 std::function<void()> 类型,并在 A 类的 notify 成员函数中被调用。

4.绑定器

std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。通俗来讲,它主要有两大作用:

  • 将可调用对象与其参数一起绑定成一个仿函数。
  • 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。
// 绑定非类成员函数/变量
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);

注意这里bind函数得到的是一个可调用对象的包装器类型。

绑定器函数使用例子如下:

  • 基础:绑定后作为仿函数使用
#include <iostream>
#include <functional>
using namespace std;void output(int x, int y)
{cout << x << " " << y << endl;
}int main(void)
{// 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数bind(output, 1, 2)();bind(output, placeholders::_1, 2)(10);bind(output, 2, placeholders::_1)(10);// error, 调用时没有第二个参数// bind(output, 2, placeholders::_2)(10);// 调用时第一个参数10被吞掉了,没有被使用bind(output, 2, placeholders::_2)(10, 20);bind(output, placeholders::_1, placeholders::_2)(10, 20);bind(output, placeholders::_2, placeholders::_1)(10, 20);return 0;
}
  • 绑定全局函数

    #include <iostream>
    #include <functional>
    using namespace std;void callFunc(int x, const function<void(int)>& f)
    {if (x % 2 == 0){f(x);}
    }void output(int x)
    {cout << x << " ";
    }void output_add(int x)
    {cout << x + 10 << " ";
    }int main(void)
    {// 使用绑定器绑定可调用对象和参数auto f1 = bind(output, placeholders::_1);for (int i = 0; i < 10; ++i){callFunc(i, f1);}cout << endl;auto f2 = bind(output_add, placeholders::_1);for (int i = 0; i < 10; ++i){callFunc(i, f2);}cout << endl;return 0;
    }
    
  • 绑定成员函数(变量)

    #include <iostream>
    #include <functional>
    using namespace std;class Test
    {
    public:void output(int x, int y){cout << "x: " << x << ", y: " << y << endl;}int m_number = 100;
    };int main(void)
    {Test t;// 绑定类成员函数function<void(int, int)> f1 = bind(&Test::output, &t, placeholders::_1, placeholders::_2);// 绑定类成员变量(公共)function<int&(void)> f2 = bind(&Test::m_number, &t);// 调用f1(520, 1314);f2() = 2333;cout << "t.m_number: " << t.m_number << endl;return 0;
    }
    

二.模板补充

前提:已经了解基础的函数模板和类模板

1.嵌套模板

#include <iostream>using namespace std;template<class T>
class Data
{
public:Data(T data) :data(data) {}
private:T data;
};template<class Ty>
class MM
{
public:MM(Ty data):data(data){}void print(){cout << data << endl;}
private:Ty data;
};int main()
{MM<Data<string>> girl(Data<string>("string"));girl.print();return 0;
}

核心知识点:

  • 友元函数一般在类外定义,类内声明
  • 友元函数如果是一个模板函数,那么不仅类外定义时需要使用templaye修饰,而且类内声明时也需要使用templaye修饰
  • 每使用一个类(只要是类模板)都要配合<>使用
  • 嵌套时,最重要的一个问题时缺乏与之匹配的操作符(==》运算符重载)

上述代码肯定是错的,因为<<并没有进行重载

#include <iostream>using namespace std;template<class T>
class Data
{
public:Data(T data) :data(data) {}template <class T>friend ostream& operator<<(ostream& out, const Data<T>& object);
private:T data;
};template<class Ty>
class MM
{
public:MM(Ty data):data(data){}void print(){cout << data << endl;}
private:Ty data;
};template <class T>
ostream& operator<<(ostream& out, const Data<T>& object)
{out << object.data;return out;
}int main()
{MM<Data<string>> girl(Data<string>("string"));girl.print();return 0;
}

2.可变参的模板函数

与原来的模板函数相比,它有一个折叠参数:template <class...Args>

展开折叠参数的过程:

  • 1:递归的方式展开参数包

    #include <iostream>template <class T>
    void print(const T& t) {std::cout << t << std::endl;
    }// 递归模板函数
    template <class T, class... Args>
    void printdata(const T& first, const Args&... rest) {print(first); // 打印第一个参数printdata(rest...); // 递归调用,处理剩余的参数
    }// 递归终止条件
    template <>
    void printdata() {// 当没有参数时,递归终止
    }int main() {printdata(1, 2.3, "hello");return 0;
    }
    

    在 C++ 中,当您看到一个函数模板的参数列表中包含 const T& first, const Args&... rest,这表示该函数模板接受至少一个参数,并且可以接受任意数量的额外参数。这里的 const T& first 是第一个参数,它是一个常量引用,而 const Args&... rest 是一个参数包,它包含剩余的所有参数,这些参数也是常量引用。

  • 2:列表的方式展开参数包

    #include <iostream>using namespace std;template<class T>
    void print(T data)
    {cout << data << "\t";
    }template <class...Args>                                       //涉及到逗号表达式
    void printdata(Args...args)
    {int array[] = { (print(args),0)... };                    //对args的每一个参数都执行print函数,每一次都返回0把它存储在array中//其中{}是初始化列表符号,重要的是理解内部的逗号表达式
    }int main()
    {printdata(1, "hello word", 2.2f, 'A');return 0;
    }

简单来谈一下我的理解:class...Args代表模板参数包(类),而Args...args实际上就相当于argsArgs的一个实例化,完全可以Args...x来实例一个叫x的函数参数包。

这里,(print(args), 0) 是一个逗号表达式,它首先对每个 args 参数调用 print 函数,然后返回 0。这个表达式被用在初始化列表中,列表中的 ... 是初始化列表展开运算符,它将模式 (print(args), 0) 应用到参数包 args 中的每个参数上。结果是创建了一个数组,其每个元素都是 0,数组的大小与 args 参数包中的参数数量相同。

3.逗号表达式

逗号表达式(Comma operator)是一个二元运算符,它用来序列化多个表达式。逗号表达式的值是它的右操作数的值。当逗号表达式作为语句出现时,它的左操作数会被计算,但它的结果会被丢弃,然后计算右操作数,并返回其结果。

expression1, expression2

理解:会从左到右执行每个语句,但是取最后一个语句expression2作为其返回值。

因此,{ (print(args), 0)... } 实际上是将 (print(args), 0) 这个逗号表达式应用到参数包 args 中的每个参数上,并为数组 array 初始化一个元素为 0 的序列。这个序列的长度与 args 参数包中的参数数量相同。...表示应用到数据包上。

4.tuple元组

在C++11后,标准库新添加了一种数据容器——tuple元组,tuple 的用法类似于 pair,但它可以包含超过两个元素。你可以创建和访问元组,以及使用 std::get 函数模板来获取元组中的元素。此外,tuple 提供了一些辅助函数,如 std::make_tuple 来创建元组,std::tie 来解包元组,以及 std::forward_as_tuple 来创建一个元组,其元素是通过完美转发得到的。

下面是一个简单的 tuple 用法示例:

#include <iostream>
#include <tuple>
#include <string>int main() {// 创建一个元组std::tuple<int, std::string, double> tup(42, "Hello", 3.14);// 访问元组中的元素std::cout << "Integer: " << std::get<0>(tup) << std::endl;std::cout << "String: " << std::get<1>(tup) << std::endl;std::cout << "Double: " << std::get<2>(tup) << std::endl;// 访问元组中的第一个元素(整数)int i = std::get<0>(tup);std::cout << "Integer: " << i << std::endl;// 使用 std::tie 解包元组int i;std::string s;double d;std::tie(i, s, d) = tup;std::cout << "Unpacked Integer: " << i << std::endl;std::cout << "Unpacked String: " << s << std::endl;std::cout << "Unpacked Double: " << d << std::endl;return 0;
}

6.特化模板

在 C++ 中,template <> 是用来特化一个模板的语法。当您看到一个模板声明后面跟着 template <>,这表示您正在为一个特定的模板实例提供一个专门的实现,这个实现会覆盖默认的模板实例化。

特化模板通常用于以下几种情况:

  1. 递归终止条件:在处理可变参数模板时,您可能需要一个递归终止条件来结束递归。这通常是通过为一个没有参数的模板提供一个特化来实现。例如:
template <class... Args>
void printdata(Args... args) {// 处理参数printdata(args...); // 递归调用
}template <>
void printdata() {// 递归终止条件,没有更多参数可处理
}

在这个例子中,printdata 是一个可变参数模板函数,它递归地调用自身来处理参数。当没有更多的参数需要处理时,它会调用一个特化的 printdata 版本,这个版本没有参数,作为递归终止条件。

2.特定类型的特殊行为:有时您可能需要为特定类型提供特殊的行为。例如,您可能需要一个模板函数来处理任何类型的数组,但对于 std::string 类型,您想要特殊处理。您可以这样做:

template <class T>
void processArray(T& arr) {// 默认处理
}template <>
void processArray<std::string>(std::string& arr) {// 特殊处理 std::string
}

在这个例子中,processArray 是一个模板函数,它有一个针对 std::string 类型的特化版本,这个版本会提供特殊的行为。


http://www.ppmy.cn/server/100144.html

相关文章

基于商业化glm大模型接口的pdf目录提取实验

鬼知道这个实验是怎么变成今天这个样子的。希望大家踊跃参加比赛&#xff0c;这样我的压力会更小。不然就要自己充钱了。这是参赛地址&#xff1a;智谱AI开放平台 世界上有很多PDF文档。这些PDF中有些非常标准化&#xff0c;可以从源码中直接解析出章节信息&#xff1b;而有些…

HAProxy 负载均衡原理深度解析

目录 一、引言 二、HAProxy 简介 三、工作模式 3.1、四层负载均衡&#xff08;Layer 4&#xff09; 3.2、七层负载均衡&#xff08;Layer 7&#xff09; 3.3、区别和选择 四、 负载均衡算法 4.1、静态负载均衡算法 4.2、动态负载均衡算法 4.3、其他算法 4.4、选择算…

springboot使用WebSocket

1、、创建springboot项目&#xff0c;勾选Spring web&#xff0c;并导包 当前springboot选择的是2.6.13版本&#xff0c;jdk1.8尽量选2.几的springboot <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-we…

Video视频抽帧和WebCodecs API视频抽帧介绍

目录 mp4Box抽帧 ffmpeg抽帧 video元素抽帧 WebCodecs 核心API 视频文件是一个容器&#xff0c;里面有很多不同的轨道信息。如&#xff1a;图像、声音、字幕等。而视频图像信息又是由一系列图片序列帧的集合。如10秒时长的视频&#xff0c;假设每秒30帧。那大概有300条图像…

relativePath

Maven中的parent.relativePath元素用于指定父POM的相对路径。当你看到这样的错误信息时&#xff0c;它意味着Maven期望在某个位置找到一个父POM&#xff0c;但实际上找到的是另一个POM。 在你的情况下&#xff0c;错误信息表明com.cubemall:cubemall-product:0.0.1-SNAPSHOT项…

网络如何发送一个数据包

网络如何发送一个数据包 网络消息发送就是点一点屏幕。 骚瑞&#xff0c;这一点都不好笑。&#xff08;小品就是我的本质惹&#xff09; 之前我就是会被这个问题搞的不安宁。是怎么知道对方的IP地址的呢&#xff1f;怎么知道对方的MAC呢&#xff1f;世界上计算机有那么多&…

Element UI左侧导航栏写法(递归组件实现)

1、左侧导航栏组件使用的是Element Ui的导航栏组件&#xff0c;思路&#xff1a;首先判断导航栏数据是否存在children&#xff0c;以此来实现一级菜单与多级菜单的渲染&#xff0c;然后使用递归组件实现多级菜单的子菜单渲染&#xff0c;注意使用递归组件需将菜单栏数据在父组件…

ES环境搭建、ES安装

文章目录 简介与环境搭建全文检索倒排索引ElasticSearchWindows安装ES下载配置JDK环境启动ES服务 centos7安装ES下载ElasticSearch创建es用户配置JDK环境配置ElasticSearch配置JVM参数启动ElasticSearch服务常见启动报错 客户端Kibana安装下载修改Kibana.yml运行Kibana访问 ES安…