文章目录
绑定器和函数对象
1.bind1st 和 bind2nd
-
这是STL中 绑定器
分别用于绑定二元函数的第一个或第二个参数 -
c++11 中的 bind绑定器和 function函数对象机制
-
lambda表达式—底层依赖函数对象的机制实现
-
代码:
#include <iostream> #include <vector> #include <functional> #include <algorithm> using namespace std; template<typename Container> void showContainer(Container& con) { typename Container::iterator it = con.begin(); //需要加 typename,编译时并不知道什么类型 for (; it != con.end(); ++it) { cout << *it << " "; } cout << endl; } int main() { vector<int> vec; srand(time(nullptr)); // 随机种子 for (int i = 0; i < 20; ++i) { vec.push_back(rand() % 100 + 1); } showContainer(vec); sort(vec.begin(), vec.end()); showContainer(vec); // greater 二元函数对象 sort(vec.begin(), vec.end(), greater<int>()); showContainer(vec); //把70按顺序 插入到vec中 使用bind1st 70>b auto it1 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70)); if (it1 != vec.end()) { vec.insert(it1, 70); } showContainer(vec); }
2.bind1st的底层实现原理
代码:
#include <iostream>
#include <vector>
#include <functional> // 用于 std::greater, std::less 等
// 自定义的 mybindist 类,模拟 bind1st 的功能
template<typename Compare, typename T>
class _mybindist
{
public:
// 构造函数,接受一个比较函数对象和一个值
_mybindist(Compare comp, T val)
: _comp(comp), _val(val)
{}
// 重载函数调用运算符,使其成为一个函数对象
bool operator()(const T& x) const
{
// 使用绑定的值和传入的参数进行比较
return _comp(x,_val); // 这里要根据使用 greater和less 来调整顺序
}
private:
Compare _comp; // 比较函数对象
T _val; // 绑定的值
};
// 辅助函数,用于创建 mybindist 对象
template<typename Compare, typename T>
_mybindist<Compare, T> mybindist(Compare comp, const T& val)
{
// 返回 mybindist 对象
return _mybindist<Compare, T>(comp, val);
}
// 自定义的 my_find_if 函数,模拟 std::find_if 的功能
template<typename Iterator, typename Predicate>
Iterator my_find_if(Iterator first, Iterator last, Predicate pred)
{
for (; first != last; ++first)
{
if (pred(*first)) // 如果满足条件
{
return first; // 返回当前迭代器
}
}
return last; // 如果没有找到,返回 last
}
int main()
{
// 创建一个整数向量
std::vector<int> vec = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
// 使用 mybindist 创建一个函数对象,检查元素是否大于 70
auto greater_than_70 = mybindist(std::greater<int>(), 70);
// 使用 my_find_if 查找第一个大于 70 的元素
auto it = my_find_if(vec.begin(), vec.end(), greater_than_70);
// 输出结果
if (it != vec.end())
{
std::cout << "First element greater than 70 is: " << *it << std::endl;
}
else
{
std::cout << "No element greater than 70 found." << std::endl;
}
return 0;
}
直接使用函数模板, 好处是 可以进行 类型的 推演
处理能力有限, 只能处理二元函数对象, 多了就不行了
3.函数对象类型function–应用实例
-
c++11的绑定器bind 与 STL中的bind1st 和 bind2nd不同, 能用, 但已被弃用
-
STL中的bind1st 和 bind2nd ---- 本质还是函数对象
-
绑定器, 函数对象, lambda 表达式, 只能使用在一条语句中, 使用了function, 可以解决这个问题
-
补充知识点: 函数类型 和 函数指针类型 区分
函数类型
函数类型指的是函数的签名(signature),包括函数的返回类型和参数类型。函数类型本身并不是一个可以直接使用的对象,而是描述函数的特征。
int add(int a, int b); // 函数类型是 int(int, int) void print(const std::string& str); // 函数类型是 void(const std::string&)
函数类型通常用于模板参数或类型推导中。
std::function<int(int, int)> func = add; // std::function 使用函数类型 int(int, int)
函数指针类型
函数指针类型是指向函数的指针类型。它是一个具体的对象,可以存储函数的地址,并通过指针调用函数。
int (*funcPtr)(int, int); // 函数指针类型是 int(*)(int, int) funcPtr = add; // 将函数 add 的地址赋值给 funcPtr int result = funcPtr(2, 3); // 通过函数指针调用函数
函数指针类型的语法包括:
- 返回类型
- 指针名称(用
*
表示) - 参数类型列表
-
function 应用实例代码:
用函数类型 实例化 function
通过function调用operator()函数的时候, 需要根据函数类型 传入 相应的参数
可以保留 lambda 表达式, 到处都可以使用
最大的作用: 保留可以看到的函数, 成员方法, lambda表达式, 其他函数对象#include <iostream> #include <vector> #include <functional> // 用于 std::greater, std::less 等 using namespace std; void hello1() { cout << "hello world!" << endl; } void hello2(string str) // void(*pfunc)(string) { cout << str << endl; } int sum(int a, int b) { return a + b; } class Test { // 成员方法和普通函数不同, 调用必须 依赖对象,通过函数指针调用 void(Test::*pfunc)(string) public: void hello(string str) { cout << str << endl; } }; int main() { // 从function的类模板定义处,看到希望用一个函数类型实例化function function<void()> func1 = hello1; func1(); // func1.operator()=>hello1() function<void(string)> func2 = hello2; // func2("hello2"); // func2.operator()(string str)=>hello2(string) function<int(int, int)> func3 = sum; cout << func3(4, 5) << endl; // function + lambda function<int(int, int)> func4 = [](int a, int b)->int {return a + b; }; cout << func4(40, 50) << endl; // function+成员方法 // 课里讲的 这个在高版本 编译不了, & 必须跟左值 //function<void(Test*, string)> func5 = &Test::hello; //func5(&Test(), "call Tes::hello!"); // &Test() 临时对象地址 function<void(Test&&, string)> func5 = &Test::hello; func5(Test(), "call Tes::hello!"); // &Test() 临时对象地址 return 0; }
-
特别注意: 成员方法第一个参数, 都是 this指针!!!, 不要忽略, 这不像python, python会标出self, cpp不会标this指针
-
实际的 应用案例之一:
这样的代码 无法闭合, 无法做到 “开-闭原则”, 无法通过添加代码完成修改, 而是需要 修改原本的代码
#include <iostream>
using namespace std;
int main() {
int choice;
cout << "1.查看所有书籍信息" << endl;
cout << "2.借书" << endl;
cout << "3.还书" << endl;
cout << "4.查询书籍" << endl;
cout << "5.注销" << endl;
cout << "---" << endl;
cout << "请选择:";
cin >> choice;
switch (choice) {
case 1:
// 查看所有书籍信息的代码
cout << "查看所有书籍信息" << endl;
break;
case 2:
// 借书的代码
cout << "借书" << endl;
break;
case 3:
// 还书的代码
cout << "还书" << endl;
break;
case 4:
// 查询书籍的代码
cout << "查询书籍" << endl;
break;
case 5:
// 注销的代码
cout << "注销" << endl;
break;
default:
cout << "无效的选择,请重新选择。" << endl;
break;
}
return 0;
}
优化:----function应用
#include <iostream>
#include <functional>
#include <map>
using namespace std;
void doshowbooks(){ cout << "查看所有书籍信息" << endl; }
void doborrow(){ cout <<"借书" << endl; } // ...
int main() {
int choice;
map<int, function<void()>> actionmap;
actionmap.insert({ 1,doshowbooks }); // 低版本使用 .insert(make_pair(1,do))
actionmap.insert({ 2,doborrow});
cout << "1.查看所有书籍信息" << endl;
cout << "2.借书" << endl;
cout << "---" << endl;
cout << "请选择:";
cin >> choice;
auto it = actionmap.find(choice);
if (it == actionmap.end())
{
cout << "no message!" << endl;
}
else
{
it->second(); //注意()
}
return 0;
}
4.模板的完全特例化和部分特例化
有完全特例化 版本, 优先匹配 完全特例化版本
回顾完全特例化
#include <iostream>
#include <cstring> // 用于 strcmp 函数
using namespace std;
// 通用模板函数
template<typename T>
bool compare(T a, T b)
{
return a > b;
}
// 特化版本,用于 const char* 类型
template<>
bool compare<const char*>(const char* a, const char* b)
{
return strcmp(a, b) > 0;
}
int main()
{
// 使用通用模板比较整数
compare(10, 20);
// 使用特化版本比较 C 风格字符串
compare("aaa", "bbb");
return 0;
}
部分特例化
#include <iostream>
using namespace std;
// 通用模板类
template<typename T>
class Vector
{
public:
Vector() { cout << "call Vector template init" << endl; }
};
// 完全特例化版本,针对 char* 类型
template<>
class Vector<char*>
{
public:
Vector() { cout << "call Vector<char*> init" << endl; }
};
// 部分特例化版本,针对所有指针类型
template<typename Ty>
class Vector<Ty*>
{
public:
Vector() { cout << "call Vector<Ty*> init" << endl; }
};
// 部分特例化版本,针对两个参数的函数指针类型
template<typename R, typename A1, typename A2>
class Vector<R(*)(A1, A2)>
{
public:
Vector() { cout << "call Vector<R(*)(A1, A2)> init" << endl; }
};
int sum(int a, int b)
{
return a + b;
}
int main()
{
// 使用通用模板类
Vector<int> intVector; // 调用通用模板构造函数
// 使用完全特例化版本
Vector<char*> charPtrVector; // 调用 char* 特例化构造函数
// 使用部分特例化版本
Vector<int*> intPtrVector; // 调用指针类型的部分特例化构造函数
//函数指针, 也是指针
Vector<int(*)(int, int)> funcPtrVector; // 没有函数指针特例化, 就用匹配的部分特例化
//这是函数类型, 不是函数指针
Vector<int(int, int)> funcVector;
typedef int(*PFUNCPTR)(int, int);
PFUNCPTR func1 = sum;
cout << func1(10, 20) << endl;
return 0;
}
注意区分函数类型和函数指针类型
模板的实参推演
实例–细分:
#include <iostream>
#include <typeinfo> // 用于 typeid 和 type_info
using namespace std;
// 模板函数,用于打印类型信息
template<typename T>
void func(T a)
{
cout << "Type of T: " << typeid(T).name() << endl;
}
// 模板函数,用于打印类型信息
template<typename T>
void func2(T* a)
{
cout << "Type of T: " << typeid(T).name() << endl;
}
template<typename R, typename A1, typename A2>
void func3(R(*a)(A1, A2))
{
cout << "Type of T: " << typeid(R).name() << endl;
cout << "Type of A1: " << typeid(A1).name() << endl;
cout << "Type of A2: " << typeid(A2).name() << endl;
}
class Test
{
public:
int sum(int a, int b) { return a + b; }
};
template<typename R, typename A1, typename A2, typename T>
void func4(R(T::*a)(A1, A2)) // a这种, 存不存在都行, 因为没使用
{
cout << "Type of R: " << typeid(R).name() << endl;
cout << "Type of T: " << typeid(T).name() << endl;
cout << "Type of A1: " << typeid(A1).name() << endl;
cout << "Type of A2: " << typeid(A2).name() << endl;
}
// 普通函数,计算两个整数的和
int sum(int a, int b) {
return a + b;
}
int main()
{
// 调用 func 并传入整数
func(10); // T 推导为 int
// 调用 func 并传入字符串字面量
func("aaa"); // T 推导为 const char*
// 调用 func 并传入函数指针
func(sum); // T 推导为 int (*)(int, int)
func2(sum); // int __cdecl(int,int)
//细分类型
func3(sum);
func(&Test::sum); // int (__thiscall Test::*)(int,int)
func4(&Test::sum);
return 0;
}
5.function实现原理
固定参模板
一般的 自己写的 function 会因为形参个数不同, 导致需要特例化 太多, 如下:
#include <iostream>
#include <string>
using namespace std;
// 模板类,封装函数指针
template<typename R, typename A1>
class myfunction {
public:
using PFUNC = R(*)(A1); // 定义函数指针类型
// 构造函数,接受一个函数指针
myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
// 重载 operator(),使对象可以像函数一样调用
R operator()(A1 arg) {
return _pfunc(arg);
}
private:
PFUNC _pfunc; // 存储函数指针
};
// 示例函数,用于测试
void hello(string str) {
cout << "Hello, " << str << endl;
}
int main() {
// 创建 myfunction 对象,封装 hello 函数
myfunction<void, string> func1(hello);
// 调用 func1,输出 "Hello, hello world!"
func1("hello world!");
return 0;
}
但是,c++11提供了 可变参的模板
可变参模板–>…
typename… A
template<typename R, typename... A>
class myfunction {
public:
using PFUNC = R(*)(A...); // 定义函数指针类型
// 构造函数,接受一个函数指针
myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
// 重载 operator(),使对象可以像函数一样调用
R operator()(A... arg) {
return _pfunc(arg);
}
private:
PFUNC _pfunc; // 存储函数指针
};
6.c++11的bind–头文件 functional
返回的结果 还是 一个函数对象
bind 是 函数模板 – 也就能 自动推演类型了
不要忘记 bind(…)()最后这个括号
1.无参数占位符
#include <iostream>
#include <string>
#include <functional>
using namespace std;
// 示例函数,用于测试
void hello(string str) {
cout << "Hello, " << str << endl;
}
int sum(int a, int b) {
return a + b;
}
class Test
{
// 成员方法和普通函数不同, 调用必须 依赖对象,通过函数指针调用 void(Test::*pfunc)(string)
public:
int sum(int a, int b) {
return a + b;
}
};
int main() {
bind(hello, "hello bind!")();
cout<<bind(sum, 10, 20)()<<endl;
cout << bind(&Test::sum, Test(), 20, 30)() << endl; // 注意绑定Test对象
return 0;
}
2.有参数占位符–placeholders::_
//参数占位符
bind(hello, placeholders::_1)("hello placeholders");
//placeholders 是一个命名空间
using namespace placeholders;
bind(hello, _1)("hello placeholders");
3.绑定器+function
绑定器 出了 语句无法继续使用
function<void(string)> func1 = bind(hello, placeholders::_1);
func1("hello shanxi");
func1("hello shanxi-2");
bind原本是 boost库里的, c++11 放到了 标准库
4.使用bind+function 实现 简单的 线程池
只学过c的线程池, 可能看不明白, 后续会写 c++的多线程 笔记
#include <iostream>
#include <vector>
#include <thread>
#include <functional> // 包含 std::bind
#include <memory> // 包含 std::unique_ptr
using namespace std;
class Thread {
public:
Thread(function<void()> func) : _func(func) {} // function用于接收 bind
thread start() {
return thread(_func);
}
private:
function<void()> _func;
};
class ThreadPool {
public:
// 开启线程池
void startPool(int size) {
for (int i = 0; i < size; ++i) {
_pool.push_back(new Thread(bind(&ThreadPool::runInThread, this, i))); //不使用占位符, 将不用再次传参
}
for (int i = 0; i < size; ++i) {
_handler.push_back(_pool[i]->start());
}
for (thread& t : _handler) {
if (t.joinable()) {
t.join();
}
}
}
// 清理线程池
~ThreadPool() {
for (int i = 0; i < _pool.size(); ++i) {
delete _pool[i];
}
}
private:
vector<Thread*> _pool; // 使用智能指针管理线程对象
vector<thread> _handler; // 存储线程对象
// 线程函数
void runInThread(int id) {
cout << "Thread " << id << " is running!" << endl;
}
};
int main() {
ThreadPool pool;
pool.startPool(10); // 开启一个包含 10 个线程的线程池
return 0;
}
可以使用智能指针, 自动释放
make_unique 是c++14加入的工具函数
在容器中存储 unique_ptr
时,推荐使用 make_unique
容器中 不能使用 new 传给 智能指针
#include <iostream>
#include <vector>
#include <thread>
#include <functional> // 包含 std::bind
#include <memory> // 包含 std::unique_ptr
using namespace std;
class Thread {
public:
Thread(function<void()> func) : _func(func) {} // function用于接收 bind
thread start() {
return thread(_func);
}
private:
function<void()> _func;
};
class ThreadPool {
public:
// 开启线程池
void startPool(int size) {
for (int i = 0; i < size; ++i) {
_pool.push_back(make_unique<Thread>(bind(&ThreadPool::runInThread, this, i))); //不使用占位符, 将不用再次传参
}
for (int i = 0; i < size; ++i) {
_handler.push_back(_pool[i]->start());
}
for (thread& t : _handler) {
if (t.joinable()) {
t.join();
}
}
}
// 清理线程池
~ThreadPool() {
}
private:
vector<unique_ptr<Thread>> _pool; // 使用智能指针管理线程对象
vector<thread> _handler; // 存储线程对象
// 线程函数
void runInThread(int id) {
cout << "Thread " << id << " is running!" << endl;
}
};
int main() {
ThreadPool pool;
pool.startPool(10); // 开启一个包含 10 个线程的线程池
return 0;
}
7.lambda表达式
-
函数对象相较于
lambda
的缺点- 代码冗长:需要定义类,
lambda
只需一行代码。 - 不能直接捕获外部变量:必须手动存储,而
lambda
可自动捕获。 - 不适用于局部作用域:
lambda
可随用随写,函数对象需额外定义类。 - 使用标准算法较繁琐:
lambda
直接传入算法,函数对象需单独定义。 - 不能自动推导类型:函数对象
operator()
需显式指定返回类型,lambda
可自动推导。
- 代码冗长:需要定义类,
-
lambda表达式语法:
[捕获外部变量](形参列表)->返回值 {操作代码}
如果返回值不需要, 可以省略->返回值
[] : 表示 不捕获外部变量
[=] : 以传值的方式 捕获的所有变量
[&] : 以引用的方式 捕获的所有变量
[this] : 捕获外部 this指针
[=, &a] : 以传值的方式 捕获的所有变量, 但是a是引用方式捕获
[a, b] : 以传值的方式 捕获 外部 a,b
[a,&b] : 以传值的方式 捕获 a, 但是b是引用方式捕获 -
mutable 关键字
在 C++ 中,mutable
是一个关键字,用于修饰类的成员变量。它的主要作用是允许在const
成员函数中修改被mutable
修饰的成员变量。mutable
通常用于那些逻辑上不改变对象状态,但技术上需要修改某些成员变量的场景。class Counter { public: Counter() : _count(0) {} void increment() const { ++_count; // 允许在 const 成员函数中修改 _count } int getCount() const { return _count; } private: mutable int _count; // 使用 mutable 修饰 }; int main() { const Counter counter; counter.increment(); // 调用 const 成员函数 std::cout << "Count: " << counter.getCount() << std::endl; // 输出: Count: 1 return 0; }
-
lambda代码示例: — 使用auto
可以回顾一下 STL中使用lambda的点
注意 成员变量是左值引用时, 不在定义出初始化, 而是在构造函数里#include <iostream> using namespace std; template<typename T=void> // 注意这个 void lambda的原理 class TestLambda01 { public: void operator()() { cout << "hello world!" << endl; } }; template<typename T = int> // 注意这个 void lambda的原理 class TestLambda02 { public: TestLambda02(int a, int b):ma(a),mb(b){} void operator()() const{ int tmp = mb; mb = ma; ma = tmp; } private: int ma; int mb; }; template<typename T = int> // 注意这个 void lambda的原理 class TestLambda03 { public: TestLambda03(int &a, int &b) :ma(a), mb(b) {} void operator()() const { int tmp = mb; mb = ma; ma = tmp; } private: int &ma; // C++ 规定引用成员变量必须在构造函数的初始化列表中初始化。 所以这里不需要初始化 int &mb; }; int main() { auto func1 = []()->void { cout << "hello world!" << endl; }; func1(); auto func2 = []() { cout << "hello world!" << endl; }; func2(); int a = 10; int b = 20; //auto func3 = [=]() { // 类似于 TestLambda02 // int tmp = b; // b = a; // lambda 默认是 是使用常方法, 因此不能修改值 // a = tmp; // cout << "hello world!" << endl; }; auto func3 = [=]()mutable { // mutable 表示 可以在常方法中修改成员变量 int tmp = b; b = a; a = tmp; cout << "hello world!" << endl; }; func3(); cout << "a " << a << endl; // 因为是值传递, 外边的没改变 TestLambda01<> t1; //这种太冗余了 t1(); /* TestLambda02<int> t2(20, 30); t2();*/ TestLambda03<int> t3(a, b); t3(); return 0; }
8.lambda表达式应用实践–跨语句–function
需要多多练习 代码使用方式
#include <iostream>
#include <memory>
#include <map>
#include <functional>
#include <queue>
using namespace std;
class Data {
public:
Data(int val1 = 10, int val2 = 10) : ma(val1), mb(val2) {}
int ma;
int mb;
};
// 自定义删除器 这个就有点繁琐了 可以使用function+lambda
void fileDeleter(FILE* file) {
if (file) {
fclose(file);
cout << "File closed." << endl;
}
}
int main() {
// 使用自定义删除器的 unique_ptr
unique_ptr<FILE, decltype(&fileDeleter)> ptr1(fopen("test.txt", "w"), fileDeleter);
if (ptr1) {
fprintf(ptr1.get(), "Hello, World!\n");
}
// 函数映射
map<int, function<int(int, int)>> calculateMap;
calculateMap[1] = [](int a, int b) -> int { return a + b; };
calculateMap[2] = [](int a, int b) -> int { return a - b; };
calculateMap[3] = [](int a, int b) -> int { return a * b; };
calculateMap[4] = [](int a, int b) -> int { return a / b; };
cout << "选择操作 (1: 加, 2: 减, 3: 乘, 4: 除): ";
int choice;
cin >> choice;
if (calculateMap.find(choice) != calculateMap.end()) {
int a, b;
cout << "输入两个整数: ";
cin >> a >> b;
cout << "结果: " << calculateMap[choice](a, b) << endl;
}
else {
cout << "无效的选择。" << endl;
}
// 使用自定义比较函数的优先级队列, 这样更 灵活, 如果写函数, 会很死板
using FUNC = function<bool(Data&, Data&)>;
priority_queue<Data, vector<Data>, FUNC> maxHeap(
[](Data& d1, Data& d2) -> bool {
return d1.ma > d2.ma; // 大顶堆,按 ma 从大到小排序
}
);
// 添加元素
maxHeap.push(Data(5, 20));
maxHeap.push(Data(15, 10));
maxHeap.push(Data(10, 30));
// 输出元素
while (!maxHeap.empty()) {
Data top = maxHeap.top();
cout << "ma: " << top.ma << ", mb: " << top.mb << endl;
maxHeap.pop();
}
return 0;
}