一.可调用对象
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:使用
uisng和typedef起别名 -
是一个具有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实际上就相当于args是Args的一个实例化,完全可以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 <>,这表示您正在为一个特定的模板实例提供一个专门的实现,这个实现会覆盖默认的模板实例化。
特化模板通常用于以下几种情况:
- 递归终止条件:在处理可变参数模板时,您可能需要一个递归终止条件来结束递归。这通常是通过为一个没有参数的模板提供一个特化来实现。例如:
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 类型的特化版本,这个版本会提供特殊的行为。

被折叠的 条评论
为什么被折叠?



