当前位置: 首页 > news >正文

深入探究C++11的核心特性

目录

引言

C++11简介

统一的列表初始化

1. {} 初始化

2. std::initializer_list

变量类型推导

1. auto

2. decltype

3. nullptr

右值引用和移动语义

1. 左值引用和右值引用

2. 左值引用与右值引用比较

3. 右值引用使用场景和意义

 移动赋值与右值引用的深入应用

 1. 移动赋值

2. 右值引用引用左值及其深入使用场景

 可变参数模板

 1. 基本概念与展开方式

  2. STL容器中的 emplace 相关接口函数

  lambda表达式

 1. C++98中的排序示例对比

 2. lambda表达式的语法与示例

 3. 函数对象与lambda表达式对比

 新的类功能

 1. 默认成员函数

  2. 类成员变量初始化

3. 强制生成默认函数的关键字 default 和禁止生成默认函数的关键字 delete 

  4. 继承和多态中的 final 与 override 关键字

完美转发

 1. 模板中的 && 万能引用

  2.  std::forward 完美转发在传参过程中保留对象原生类型属性

包装器(function包装器)

1. 为什么需要function包装器

2. function包装器的使用

 包装器(function包装器)的进一步探讨 

1. 解决模板效率问题

2.  std::function 包装不同类型可调用对象的示例 

 std::bind 函数适配器 

1.  std::bind 的基本概念

2.  std::bind 的使用示例 

在实际问题中的应用——以逆波兰表达式求值为例 

1. 传统解法

2. 使用包装器的解法


引言

C++作为一门强大的编程语言,在不断地进化与发展。C++11标准的推出,为C++带来了诸多令人瞩目的新特性,极大地提升了语言的表达能力与开发效率。今天,就让我们一起深入探索C++11那些实用且有趣的特性。

C++11简介

C++11可谓是历经“十年磨一剑”。早在2003年,C++标准委员会提交了技术勘误表(TC1),C++03逐渐取代C++98成为新的标准,但它主要是对C++98的漏洞进行修复,核心部分改动不大 ,人们常将C++98/03合称为旧标准。而从C++0x(当时不确定最终版本号,所以用x代替)到C++11,才是真正意义上的重大革新,包含了约140个新特性以及对C++03中约600个缺陷的修正。

C++11在系统开发和库开发等领域表现出色,语法更加灵活、简化,稳定性和安全性也得到增强,成为了众多公司实际项目开发中的得力工具。例如,在一些高性能的游戏引擎开发中,C++11的新特性就被广泛应用,优化了代码结构与运行效率。

统一的列表初始化

1. {} 初始化

在C++98中,花括号{}就已被用于数组和结构体元素的初始化。比如:

C++11进一步扩大了大括号初始化列表的应用范围,使其适用于所有内置类型和用户自定义类型。而且使用时等号“=”可加可不加。示例如下:

甚至在创建对象时,也能利用列表初始化来调用构造函数:

2. std::initializer_list

 std::initializer_list 是C++11引入的一个很实用的类型。它一般作为构造函数的参数,让STL容器的初始化更加便捷。

我们可以通过以下代码查看它的类型:

在实际使用中,许多STL容器都增加了以 std::initializer_list 为参数的构造函数。比如:

#include <vector>#include <list>#include <map>#include <string>int main() {std::vector<int> v = { 1, 2, 3, 4 };std::list<int> lt = { 1, 2 };std::map<std::string, std::string> dict = { { "sort", "排序" }, { "insert", "插入" } };v = { 10, 20, 30 };return 0;}

如果想要让自定义的 vector 也支持这种初始化方式,可以参考以下代码:

namespace bit {template<class T>class vector {public:typedef T* iterator;vector(std::initializer_list<T> l) {_start = new T[l.size()];_finish = _start + l.size();_endofstorage = _start + l.size();iterator vit = _start;typename std::initializer_list<T>::iterator lit = l.begin();while (lit != l.end()) {*vit++ = *lit++;}}vector<T>& operator=(std::initializer_list<T> l) {vector<T> tmp(l);std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_endofstorage, tmp._endofstorage);return *this;}private:iterator _start;iterator _finish;iterator _endofstorage;};}

变量类型推导

1. auto

在C++98中, auto 主要用于表示变量是局部自动存储类型,但在局部域中定义变量默认就是自动存储类型,所以 auto 意义不大。C++11对其进行了革新,使其用于自动类型推断。不过使用 auto 时必须进行显式初始化,编译器会根据初始化值来确定变量的类型。

示例代码如下:

#include <iostream>#include <map>#include <string>int main() {int i = 10;auto p = &i; auto pf = strcpy; std::cout << typeid(p).name() << std::endl;std::cout << typeid(pf).name() << std::endl;std::map<std::string, std::string> dict = { { "sort", "排序" }, { "insert", "插入" } };auto it = dict.begin();return 0;}

2. decltype

 decltype 关键字用于将变量的类型声明为表达式指定的类型。以下是一些使用场景:

#include <iostream>template<class T1, class T2>void F(T1 t1, T2 t2) {decltype(t1 * t2) ret;std::cout << typeid(ret).name() << std::endl;}int main() {const int x = 1;double y = 2.2;decltype(x * y) ret; decltype(&x) p; std::cout << typeid(ret).name() << std::endl;std::cout << typeid(p).name() << std::endl;F(1, 'a');return 0;}

3. nullptr

在C++中,之前用 NULL 表示空指针,但 NULL 本质是被定义为字面量0,这可能会导致一些混淆,因为0既可以表示指针常量,又能表示整形常量。C++11引入了 nullptr 来专门表示空指针,让代码更加清晰和安全。

比如在一些条件判断中:

#include <iostream>int main() {int* ptr = nullptr;if (ptr == nullptr) {std::cout << "ptr is nullptr" << std::endl;}return 0;}

右值引用和移动语义

1. 左值引用和右值引用

在C++11之前,我们所学的引用其实是左值引用。左值是可以获取地址且能被赋值的表达式,比如变量、解引用的指针等;左值引用就是给左值取别名。

int main() {int* p = new int(0);int b = 1;const int c = 2;int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;}

C++11新增的右值引用,是给右值取别名。右值是不能取地址的表达式,像字面常量、表达式返回值、函数返回值(非左值引用返回 )等。

int main() {double x = 1.1, y = 2.2;int&& r1 = 10;double&& r2 = x + y;// 编译报错,右值不能出现在赋值符号左边// 10 = 1;// x + y = 1;return 0;}

2. 左值引用与右值引用比较

- 左值引用只能绑定左值,不过 const 左值引用既可以绑定左值,也能绑定右值。

int main() {int a = 10;int& ra1 = a; // int& ra2 = 10; 编译失败,10是右值const int& ra3 = 10;const int& ra4 = a;return 0;}

- 右值引用只能绑定右值,但可以绑定 move 以后的左值。

int main() {int&& r1 = 10;int a = 10;// int&& r2 = a; 编译失败,a是左值int&& r3 = std::move(a);return 0;}

3. 右值引用使用场景和意义

左值引用在作为函数参数和返回值时能提高效率,避免不必要的拷贝。但当函数返回一个局部变量时,由于局部变量出了函数作用域就会销毁,不能用左值引用返回,只能传值返回,这会导致拷贝构造开销。

例如:


#include <iostream>#include <string>std::string to_string(int value) {std::string str;// 处理逻辑return str;}int main() {std::string ret = to_string(1234);return 0;}

这里原本可能会有两次拷贝构造,但新的编译器一般会优化为一次。而右值引用和移动语义可以进一步优化这种情况。以自定义的 string 类为例:

namespace bit {class string {public:typedef char* iterator;string(const char* str = "") : _size(strlen(str)), _capacity(_size) {_str = new char[_capacity + 1];strcpy(_str, str);}string(const string& s) : _str(nullptr) {std::cout << "string(const string& s) -- 深拷贝" << std::endl;string tmp(s._str);std::swap(tmp);}string(string&& s) : _str(nullptr), _size(0), _capacity(0) {std::cout << "string(string&& s) -- 移动语义" << std::endl;std::swap(s);}~string() {delete[] _str;_str = nullptr;}private:char* _str;size_t _size;size_t _capacity;};}

当有移动构造函数时,在函数返回右值时,编译器会优先调用移动构造,避免深拷贝,提高效率。


 
移动赋值与右值引用的深入应用


 
1. 移动赋值


在 bit::string 类中,我们不仅有移动构造,还可以添加移动赋值函数。当我们将 bit::to_string(1234) 返回的右值对象赋值给 ret1 对象时,就会调用移动赋值。示例代码如下:

 
// 移动赋值
string& operator=(string&& s) {cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;
}
int main() {bit::string ret1;ret1 = bit::to_string(1234);return 0;
}


 
运行后,我们会看到调用了一次移动构造和一次移动赋值。编译器会把 str 识别成右值,先调用移动构造生成临时对象,再把临时对象赋值给 ret1 ,这里调用移动赋值。
 


2. 右值引用引用左值及其深入使用场景


虽然右值引用语法上只能引用右值,但在某些场景下,我们可以通过 std::move 函数将左值强制转化为右值引用,从而实现移动语义。 std::move 函数位于 <utility> 头文件中,它并不实际移动任何东西,只是进行类型转换。
例如:
 

int main() {bit::string s1("hello world");bit::string s2(s1);// 这里s1是左值,调用的是拷贝构造bit::string s3(std::move(s1)); // 这里把s1 move处理后,会被当成右值,调用移动构造// 但要注意,s1的资源会被转移给s3,s1被置空return 0;
}


 
 
在STL容器插入接口函数中,也增加了右值引用版本,如 std::list 和 std::vector 的 push_back 函数。以 std::list 为例:
 

void push_back (value_type&& val);
int main() {list<bit::string> lt;bit::string s1("1111");// 这里调用的是拷贝构造lt.push_back(s1);// 下面调用都是移动构造lt.push_back("2222");lt.push_back(std::move(s1));return 0;
}


 
可变参数模板


 
1. 基本概念与展开方式


C++11的可变参数模板允许创建可以接受可变参数的函数模板和类模板,这是对C++98/03中固定数量模板参数的重大改进。
 
- 递归函数方式展开参数包:定义一个基本的可变参数函数模板 ShowList ,通过递归终止函数和展开函数来处理参数包。
 

// Args是一个模板参数包,args是一个函数形参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
template <class...Args>
void ShowList(Args... args) {}
// 递归终止函数
template <class T>
void ShowList(const T& t) {cout << t << endl;
}
// 展开函数
template <class T, class...Args>
void ShowList(T value, Args... args) {cout << value << " ";ShowList(args...);
}
int main() {ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}


 
 
- 逗号表达式展开参数包:这种方式不需要递归终止函数,直接在 expand 函数体中利用逗号表达式展开参数包。
 

template <class T>
void PrintArg(T t) {cout << t << " ";
}
// 展开函数
template <class...Args>
void ShowList(Args... args) {int arr[] = { (PrintArg(args), 0)... };cout << endl;
}
int main() {ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}


 
 
2. STL容器中的 emplace 相关接口函数


 emplace 系列接口支持模板的可变参数,并且是万能引用。以 std::vector 和 std::list 的 emplace_back 为例:
 

template <class... Args>
void emplace_back (Args&&... args);


 
 
它的优势在于可以在容器内部直接构造对象,避免了临时对象的拷贝或移动。比如在 std::list 中插入 std::pair<int, char> 对象:

 
int main() {std::list<std::pair<int, char>> mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(std::make_pair(30, 'c'));mylist.emplace_back(std::make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist) {cout << e.first << ":" << e.second << endl;}return 0;
}


 
 
lambda表达式


 
1. C++98中的排序示例对比


在C++98中,使用 std::sort 对数组排序,如果是内置类型,默认按小于比较升序排列,如需降序,需改变比较规则,引入 greater<int>() 。对于自定义类型 Goods ,还需用户定义比较规则类,如 ComparePriceLess 和 ComparePriceGreater 。
 

#include <algorithm>
#include <functional>
int main() {int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默认按小于比较,排出来结果是升序std::sort(array, array + sizeof(array) / sizeof(array[0]));// 如果需要降序,需要改变元素的比较规则std::sort(array, array + sizeof(array) / sizeof(array[0]), std::greater<int>());return 0;
}
struct Goods {std::string _name; double _price; int _evaluate; Goods(const char* str, double price, int evaluate) : _name(str), _price(price), _evaluate(evaluate) {}
};
struct ComparePriceLess {bool operator()(const Goods& g1, const Goods& g2) {return g1._price < g2._price;}
};
struct ComparePriceGreater {bool operator()(const Goods& g1, const Goods& g2) {return g1._price > g2._price;}
};


 
 
而在C++11中,lambda表达式让这一过程更加简洁。


 
2. lambda表达式的语法与示例


lambda表达式书写格式为: [capture-list] (parameters) mutable -> return-type { statement } 
 
- 各部分说明:
-  [capture-list] :捕捉列表,用于捕捉上下文中的变量供lambda函数使用,捕捉方式有值传递和引用传递等。例如 [=] 表示值传递捕捉所有变量, [&] 表示引用传递捕捉所有变量 。
-  (parameters) :参数列表,与普通函数参数列表一致,可省略。
-  mutable :默认lambda函数是常量函数,使用 mutable 可取消其常量性,此时参数列表不可省略。
-  ->return-type :返回值类型,可省略,由编译器推导。
-  {statement} :函数体,可使用参数和捕获的变量。
- 示例:
 

int main() {// 最简单的lambda表达式,无实际意义[]{};int a = 3, b = 4;// 省略参数列表和返回值类型,返回值类型由编译器推导为int[=]{return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c){b = a + c; };fun1(10);cout << a << " " << b << endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int{return b += a+ c; };cout << fun2(10) << endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;
}


 
3. 函数对象与lambda表达式对比


函数对象(仿函数)是在类中重载了 operator() 运算符的类对象。对比函数对象和lambda表达式:
 

class Rate {
public:Rate(double rate) : _rate(rate) {}double operator()(double money, int year) { return money * _rate * year; }
private:double _rate;
};
int main() {// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lambdaauto r2 = [=](double monty, int year)->double{return monty*rate*year; };r2(10000, 2);return 0;
}


 
从使用方式看,二者类似。函数对象将 rate 作为成员变量,在定义对象时初始化;lambda表达式通过捕获列表直接捕获该变量。底层编译器对lambda表达式的处理方式是按照函数对象的方式,定义lambda表达式会自动生成一个类,重载 operator() 。


 
新的类功能


 
1. 默认成员函数


在C++中,类有6个默认成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、 const 取地址重载。C++11新增移动构造函数和移动赋值运算符重载。
关于移动构造函数和移动赋值运算符重载,有以下规则:
 
- 若未实现移动构造函数,且未实现析构函数、拷贝构造、拷贝赋值重载中的任何一个,编译器会自动生成默认移动构造函数。对于内置类型成员执行逐成员字节拷贝,自定义类型成员看其是否实现移动构造,实现则调用移动构造,未实现则调用拷贝构造。
- 若未实现移动赋值重载函数,且未实现析构函数、拷贝构造、拷贝赋值重载中的任何一个,编译器会自动生成默认移动赋值函数。内置类型成员逐成员字节拷贝,自定义类型成员看其是否实现移动赋值,实现则调用移动赋值,未实现则调用拷贝赋值。
- 若提供了移动构造或移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
示例代码如下:
 

class Person {
public:Person(const char* name = "", int age = 0) : _name(name), _age(age) {}// 未实现移动构造和移动赋值相关函数/*Person(const Person& p):_name(p._name),_age(p._age){}*//*Person& operator=(const Person& p) {if (this != &p) {_name = p._name;_age = p._age;}return *this;}*//*~Person() {}*/
private:bit::string _name;int _age;
};
int main() {Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}


 
 
2. 类成员变量初始化


C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。


3. 强制生成默认函数的关键字 default 和禁止生成默认函数的关键字 delete 


 
-  default :当我们希望使用某个默认函数,但由于自定义了其他函数导致编译器不自动生成时,可使用 default 关键字显式指定生成。比如提供了拷贝构造,想让编译器生成移动构造,可如下定义:
 

class Person {
public:Person(const char* name = "", int age = 0) : _name(name), _age(age) {}Person(const Person& p):_name(p._name),_age(p._age) {}Person(Person&& p) = default;
private:bit::string _name;int _age;
};


 
 
-  delete :在C++11中,若想限制某些默认函数生成,只需在函数声明后加上 =delete ,该函数称为删除函数。例如,禁止拷贝构造函数:
 

class Person {
public:Person(const char* name = "", int age = 0) : _name(name), _age(age) {}Person(const Person& p) = delete;
private:bit::string _name;int _age;
};


 
 
4. 继承和多态中的 final 与 override 关键字


 final 用于修饰虚函数,表示该虚函数不能被派生类重写;修饰类,表示该类不能被继承。 override 用于显式表明派生类中的函数是重写基类的虚函数,若基类不存在对应的虚函数,编译器会报错,从而避免一些隐藏的错误。这部分在继承和多态章节有详细讲解。
 


完美转发


 
1. 模板中的 && 万能引用


在模板中, && 不代表右值引用,而是万能引用,它既能接收左值又能接收右值。但引用类型唯一作用是限制接收的类型,后续使用中都退化成左值。如果希望在传递过程中保持对象原生类型属性(左值或右值),就需要完美转发。
示例代码如下:
 

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) {Fun(t);
}
int main() {PerfectForward(10); int a;PerfectForward(a); PerfectForward(std::move(a)); const int b = 8;PerfectForward(b); PerfectForward(std::move(b)); return 0;
}


 
 
2.  std::forward 完美转发在传参过程中保留对象原生类型属性


 std::forward<T>(t) 在传参过程中能保持 t 的原生类型属性。示例如下:
 

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) {Fun(std::forward<T>(t));
}
int main() {PerfectForward(10); int a;PerfectForward(a); PerfectForward(std::move(a)); const int b = 8;PerfectForward(b); PerfectForward(std::move(b)); return 0;
}


 
 
完美转发在实际中的使用场景,如自定义的 List 类中:
 

template<class T>
struct ListNode {ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};template<class T>
class List {typedef ListNode<T> Node;
public:List() {_head = new Node;_head->_next = _head;_head->_prev = _head;}void PushBack(T&& x) {Insert(_head, std::forward<T>(x));}void PushFront(T&& x) {Insert(_head->_next, std::forward<T>(x));}void Insert(Node* pos, T&& x) {Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = std::forward<T>(x); prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}void Insert(Node* pos, const T& x) {Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = x; prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}~List() {Node* cur = _head->_next;while (cur != _head) {Node* next = cur->_next;delete cur;cur = next;}delete _head;}// 提供迭代器相关接口,方便遍历class iterator {public:iterator(Node* node) : _node(node) {}T& operator*() {return _node->_data;}iterator& operator++() {_node = _node->_next;return *this;}iterator operator++(int) {iterator tmp = *this;_node = _node->_next;return tmp;}iterator& operator--() {_node = _node->_prev;return *this;}iterator operator--(int) {iterator tmp = *this;_node = _node->_prev;return tmp;}bool operator!=(const iterator& it) const {return _node != it._node;}bool operator==(const iterator& it) const {return _node == it._node;}private:Node* _node;};iterator begin() {return iterator(_head->_next);}iterator end() {return iterator(_head);}private:Node* _head;
};

代码说明
 
1. 析构函数( ~List ):用于释放链表中所有节点占用的内存。先从第一个有效节点开始,逐个删除节点,最后删除头节点。

2. 迭代器相关部分

-  iterator 类:定义了链表的迭代器。通过重载各种运算符,使得迭代器可以像指针一样方便地遍历链表。例如,重载 * 运算符用于获取节点数据,重载 ++ 和 -- 运算符用于移动迭代器到下一个或前一个节点,重载 != 和 == 用于比较迭代器是否相等。

-  begin 和 end 函数: begin 函数返回指向链表第一个有效节点的迭代器, end 函数返回指向头节点的迭代器,用于界定链表遍历的范围。

这样,外部代码就可以使用 for 循环等方式方便地遍历链表,例如:
 

List<int> lst;
lst.PushBack(1);
lst.PushBack(2);
lst.PushBack(3);
for (auto it = lst.begin(); it != lst.end(); ++it) {std::cout << *it << " ";
}

包装器(function包装器)

1. 为什么需要function包装器

在C++中,我们常常会遇到需要处理多种可调用类型的情况,比如函数名、函数指针、函数对象(仿函数对象)以及lambda表达式对象等。例如在下面的函数模板 useF 中:

在这个例子中, useF 函数模板实例化了三份,因为编译器需要为不同类型的可调用对象分别生成代码。当可调用类型非常丰富时,这可能会导致模板的效率低下。而 function 包装器可以很好地解决这个问题。

2. function包装器的使用

 std::function 是定义在 <functional> 头文件中的一个类模板,它本质是一个包装器,也叫适配器。其类模板原型如下:

template <class T> function; // undefinedtemplate <class Ret, class... Args>class function<Ret(Args...)>;

其中 Ret 是被调用函数的返回类型, Args... 是被调用函数的形参。

使用示例如下:

 std::function 可以统一包装不同类型的可调用对象,使得代码更加简洁和灵活,提高了代码的可维护性和复用性。


 
包装器(function包装器)的进一步探讨
 


1. 解决模板效率问题


在之前提到的 useF 函数模板示例中,当面对多种可调用类型时,会导致模板实例化多份,影响效率。而 std::function 包装器可以统一处理这些可调用类型。
以如下代码为例:
 

#include <functional>
template<class F, class T>
T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i) {return i / 2;
}
struct Functor {double operator()(double d) {return d / 3;}
};
int main() {// 函数名std::function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;// 函数对象std::function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;// lambda表达式std::function<double(double)> func3 = [](double d)->double{ return d / 4; };cout << useF(func3, 11.11) << endl;return 0;
}


 
 
通过 std::function 将不同类型的可调用对象统一包装, useF 函数模板在处理这些对象时,不再需要为每种类型单独实例化,提高了代码的效率和简洁性。
 


2.  std::function 包装不同类型可调用对象的示例
 

class Plus {
public:static int plusi(int a, int b) {return a + b;}double plusd(double a, double b) {return a + b;}
};
int main() {// 函数名(函数指针)std::function<int(int, int)> func1 = f;cout << func1(1, 2) << endl;// 函数对象std::function<int(int, int)> func2 = Functor();cout << func2(1, 2) << endl;// lambda表达式std::function<int(int, int)> func3 = [](const int a, const int b) { return a + b; };cout << func3(1, 2) << endl;// 类的静态成员函数std::function<int(int, int)> func4 = &Plus::plusi;cout << func4(1, 2) << endl;// 类的非静态成员函数std::function<double(Plus, double, double)> func5 = &Plus::plusd;cout << func5(Plus(), 1.1, 2.2) << endl;return 0;
}


 
 
这里展示了 std::function 包装函数名、函数对象、lambda表达式、类的静态成员函数以及类的非静态成员函数的方式,体现了其强大的通用性和灵活性。
 


 std::bind 函数适配器
 


1.  std::bind 的基本概念


 std::bind 定义在 <functional> 头文件中,是一个函数模板,它如同一个函数包装器(适配器)。它接受一个可调用对象(如函数、函数对象、lambda表达式等),生成一个新的可调用对象来“适应”原对象的参数列表。
一般调用形式为: auto newCallable = bind(callable, arg_list) 。其中, newCallable 本身是一个可调用对象, arg_list 是一个逗号分隔的参数列表,对应给定的 callable 的参数。 arg_list 中的参数可能包含形如 _n 的名字( n 是一个整数),这些参数是“占位符”,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的“位置”。数值 n 表示生成的可调用对象中参数的位置, _1 为 newCallable 的第一个参数, _2 为第二个参数,以此类推。


2.  std::bind 的使用示例
 

#include <functional>
int Plus(int a, int b) {return a + b;
}
class Sub {
public:int sub(int a, int b) {return a - b;}
};
int main() {// 表示绑定函数Plus,参数分别由调用func1的第一、二个参数指定std::function<int(int, int)> func1 = std::bind(Plus, std::placeholders::_1, std::placeholders::_2);// auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);// func2的类型为function<void(int, int, int)> 与func1类型一样// 表示绑定函数Plus的第一、二为:1、2auto func2 = std::bind(Plus, 1, 2);cout << func1(1, 2) << endl;cout << func2() << endl;Sub s;// 绑定成员函数std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, std::placeholders::_1, std::placeholders::_2);// 参数调换顺序std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, std::placeholders::_2, std::placeholders::_1);cout << func3(1, 2) << endl;cout << func4(1, 2) << endl;return 0;
}


 
在这个示例中,通过 std::bind 对函数 Plus 和类 Sub 的成员函数 sub 进行了不同方式的绑定。可以指定固定参数,也可以使用占位符灵活地调整参数顺序,展示了 std::bind 在参数绑定和调整方面的强大功能。
 


在实际问题中的应用——以逆波兰表达式求值为例
 


1. 传统解法


对于逆波兰表达式求值问题(LeetCode相关题目),传统解法通常使用栈来处理。代码如下:
 

class Solution {
public:int evalRPN(std::vector<std::string>& tokens) {std::stack<int> st;for (auto& str : tokens) {if (str == "+" || str == "-" || str == "*" || str == "/") {int right = st.top();st.pop();int left = st.top();st.pop();switch (str[0]) {case '+':st.push(left + right);break;case '-':st.push(left - right);break;case '*':st.push(left * right);break;case '/':st.push(left / right);break;}}else {st.push(std::stoi(str));}}return st.top();}
};


 
这种解法通过遍历逆波兰表达式的字符串数组,利用栈来存储操作数,遇到运算符时进行相应的运算。
 


2. 使用包装器的解法


利用 std::function 和 std::bind 等包装器机制,可以让代码更加简洁和灵活。
 

class Solution {
public:int evalRPN(std::vector<std::string>& tokens) {std::stack<int> st;std::map<std::string, std::function<int(int, int)>> opFuncMap = {{ "+", [](int i, int j){return i + j; } },{ "-", [](int i, int j){return i - j; } },{ "*", [](int i, int j){return i * j; } },{ "/", [](int i, int j){return i / j; } }};for (auto& str : tokens) {if (opFuncMap.find(str) != opFuncMap.end()) {int right = st.top();st.pop();int left = st.top();st.pop();st.push(opFuncMap[str](left, right));}else {st.push(std::stoi(str));}}return st.top();}
};


 
 
在这个解法中,使用 std::map 结合 std::function 将运算符与对应的运算逻辑(以lambda表达式表示)进行映射。在遍历过程中,当遇到运算符时,直接从 map 中获取对应的可调用对象进行运算,使得代码逻辑更加清晰,同时也体现了C++11包装器特性在实际问题中的应用优势。
 
通过对 std::function 和 std::bind 等包装器相关内容的深入探讨以及在实际问题中的应用展示,我们能更全面地掌握C++11在处理可调用对象方面的强大功能,这些特性为我们编写高效、简洁、灵活的代码提供了有力支持。

相关文章:

深入探究C++11的核心特性

目录 引言 C11简介 统一的列表初始化 1. {} 初始化 2. std::initializer_list 变量类型推导 1. auto 2. decltype 3. nullptr 右值引用和移动语义 1. 左值引用和右值引用 2. 左值引用与右值引用比较 3. 右值引用使用场景和意义 移动赋值与右值引用的深入应用 1. 移…...

nltk-英文句子分词+词干化

一、准备工作 ①安装好nltk模块并在&#xff1a; nltk/nltk_data: NLTK Data 链接中手动下载模型并放入到对应文件夹下。 具体放到哪个文件夹&#xff0c;先执行看报错后的提示即可。 ②准备pos_map.json文件&#xff0c;放置到当前文件夹下。该文件用于词性统一 {"…...

系统性能分析基本概念(3) : Tuning Efforts

系统性能调优&#xff08;Tuning Efforts&#xff09;是指通过优化硬件、软件或系统配置来提升性能&#xff0c;减少延迟、提高吞吐量或优化资源利用率。以下是系统性能调优的主要努力方向&#xff0c;涵盖硬件、操作系统、应用程序和网络等多个层面&#xff0c;结合实际应用场…...

部署TOMEXAM

前提&#xff1a;机器上有MySQL&#xff0c;nginx&#xff0c;jdk&#xff0c;tomcat 1.配置MySQL [rootjava-tomcat1 ~]# mysql -u root -pLiuliu!123 mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. C…...

Nginx 1.25.4交叉编译问题:编译器路径与aclocal.m4错误解决方案

Nginx 1.25.4交叉编译问题&#xff1a;编译器路径与aclocal.m4错误解决方案 一、问题描述 在对Nginx 1.25.4进行交叉编译时&#xff0c;遇到以下复合问题&#xff1a; 编译器路径失效&#xff1a;尽管在脚本中配置了交叉编译器&#xff08;如CCaarch64-himix100-linux-gcc&a…...

FPGA通信之VGA

文章目录 基本概念&#xff1a;水平扫描&#xff1a;垂直扫描&#xff1a; 时序如下&#xff1a;端口设计疑问为什么需要输出那么多端口不输出时钟怎么保证电子枪移动速度符合时序VGA转HDMI 仿真电路图代码总结&#xff1a;野火电子yyds 为了做图像处理&#xff0c; 现在我们开…...

[Git] 认识 Git 的三大区域 文件的修改和提交

文章目录 认识 Git 的三大区域&#xff1a;工作区、暂存区、版本库工作区、暂存区、版本库的关系流程图解 (概念) 将文件添加到仓库进行管理&#xff1a;git add 和 git commit场景一&#xff1a;第一次添加文件到仓库查看提交历史&#xff1a;git log&#xff08;进阶理解&…...

交叉编译DirectFB报错解决方法

configure: error: *** DirectFB compilation requires fluxcomp *** Unless you are compiling from a distributed tarball you need fluxcomp available from git://git.directfb.org/git/directfb/core/flux installed in your PATH. 需要先编译安装flux git clone http…...

AllToAll通信为什么用于EP并行?

1 AllToAll通信原理 首先要明白ALLTOALL通信是做了什么事情。 假设我们有3个进程&#xff08;A、B、C&#xff09;&#xff0c;每个进程都有三段数据&#xff0c;分别是a1, a2, a3&#xff1b;b1, b2, b3&#xff1b;c1, c2, c3。 进程A想发送&#xff1a;a1到进程A自己&…...

深入掌握Node.js HTTP模块:从开始到放弃

文章目录 一、HTTP模块入门&#xff1a;从零搭建第一个服务器1.1 基础概念解析1.2 手把手创建服务器 二、核心功能深入解析2.1 处理不同请求类型2.2 实现文件下载功能 三、常见问题解决方案3.1 跨域问题处理3.2 防止服务崩溃3.3 调试技巧 四、安全最佳实践4.1 请求头安全设置4.…...

python安装与使用

Python的安装 1.官网下载python安装包 https://www.python.org/ 2.安装python 勾选add python 3.8 to api将python加入变量 选择Customize installation进行自定义安装 一直选next直到下面界面&#xff0c;根据自己需要将python安装到指定位置&#xff0c;然后install 等贷…...

2025最新版Visual Studio Code for Mac安装使用指南

2025最新版Visual Studio Code for Mac安装使用指南 Installation and Application Guide to The Latest Version of Visual Studio Code in 2025 By JacksonML 1. 什么是Visual Studio Code&#xff1f; Visual Studio Code&#xff0c;通常被称为 VS Code&#xff0c;是由…...

VSCode GitHub Copilot 安装与使用完全指南

文章目录 一、安装准备1.1 系统要求1.2 Copilot订阅选择1.3 获取访问权限 二、安装步骤2.1 安装GitHub Copilot基础扩展2.2 安装GitHub Copilot Chat扩展2.3 登录和授权 三、基本使用&#xff1a;代码自动完成3.1 内联代码建议3.2 自定义Copilot配置3.3 使用注释引导Copilot 四…...

Github超19k+ strar的实时协同编辑的开源框架yjs

Yjs 是一个用于实现实时协同编辑的开源框架&#xff0c;具有以下关键特性和应用价值&#xff1a; 核心特性 基于 CRDT 算法 Yjs 采用无冲突复制数据类型&#xff08;CRDT&#xff09;&#xff0c;确保多用户同时编辑同一文档时无需复杂锁机制或中央协调&#xff0c;最终实现数据…...

【Node.js】工具链与工程化

个人主页&#xff1a;Guiat 归属专栏&#xff1a;node.js 文章目录 1. Node.js 工具链概述1.1 工具链的作用1.2 Node.js 工具链全景 2. 包管理与依赖管理2.1 npm (Node Package Manager)2.2 yarn2.3 pnpm2.4 锁文件与依赖管理2.5 工作空间与 Monorepo 3. 构建工具与打包3.1 Web…...

OceanBase数据库全面指南(函数篇)函数速查表

文章目录 一、数学函数1.1 基本数学函数1.2 三角函数二、字符串函数2.1 基本字符串函数2.2 高级字符串处理函数三、日期时间函数3.1 基本日期时间函数3.2 日期时间计算函数四、聚合函数4.1 常用聚合函数4.2 分组聚合4.3 高级聚合函数五、条件判断函数5.1 基本条件函数5.2 CASE表…...

Chrome 缓存文件路径

Chrome 缓存文件路径查看方法 启动 Chrome 浏览器, 输入 Chrome://Version Google浏览器版本号以及安装路径 Windows 缓存目录 在 “运行” 中输入 %TEMP% 可打开, 一般路径是: C:\Users\Administrator\AppData\Local\Temp, 其中 Administrator 是用户名。 Windows 目录…...

Ubuntu Desktop 24.04 常用软件安装步骤

文章目录 Ubuntu Desktop 24.04 常用软件安装步骤Snipaste F1快捷截图&#xff08;超方便 | 我6台电脑每台都用&#xff09;搜狗输入法快速浏览工具 | 空格键快速预览文件壁纸工具 | varietySSH 工具 | Termius 终端分屏工具 | TmuxCaffeine | 避免息屏小工具 一些设置将启动台…...

Chrome 插件网络请求的全面指南

在 Chrome 插件开发中&#xff0c;网络请求可以在多个上下文中实现&#xff0c;而不仅限于 background.js 和 content.js。以下是完整的网络请求实现方案&#xff1a; 一、主要请求实现位置 1. Background Script (后台脚本) 特点&#xff1a; 生命周期最长适合处理敏感数据…...

SpringBoot Day_03

目录 一、数据校验 二、统一异常处理 1、局部异常处理&#xff08;少&#xff09; 2、全局异常处理&#xff08;多&#xff09; 三、定时器 四、springboot日志 五、swagger 六、springboot自动装配原理 总结 1、如何实现参数校验功能&#xff08;掌握&#xff09; …...

Ubuntu 新建用户

在 Ubuntu 22.04 中创建新用户并赋予 root 权限的步骤如下&#xff0c;综合多篇文档推荐的安全方法&#xff1a; 一、创建新用户 使用 adduser 命令创建用户 sudo adduser your_username系统会提示设置密码及填写用户信息&#xff08;全名、电话等&#xff0c;可直接回车跳过&a…...

从法律视角看湖北理元理律师事务所的债务优化实践

债务问题解决需要专业法律支持。本文将从实务角度&#xff0c;解析湖北理元理律师事务所在债务优化领域的工作方法&#xff0c;为有需要的读者提供参考。 一、法律框架下的债务重组 利率合法性审查 识别超过法定上限的利息部分 收集相关证据材料 启动协商或诉讼程序 还款…...

# JavaSE核心知识点02面向对象编程

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 JavaSE核心知识点02面向对象编程JavaSE核心知…...

从原理到实践:一文详解残差网络

在深度学习的发展历程中&#xff0c;神经网络的深度一直是提升模型性能的关键因素之一。随着网络层数的增加&#xff0c;模型理论上可以学习到更复杂、更抽象的特征表示。然而&#xff0c;在实际训练过程中&#xff0c;研究人员发现&#xff0c;当网络深度达到一定程度后&#…...

把银河装进镜头里!动态星轨素材使用实录

仰望夜空时&#xff0c;神秘的银河与闪烁繁星总令人向往。如今&#xff0c;无需复杂拍摄&#xff0c;借助素材平台就能将绝美星轨融入创作&#xff0c;今天重点安利 制片帮素材&#xff01; 动态星轨&#xff1a;宇宙的浪漫印记 星轨是地球自转时&#xff0c;星星运动留下的轨…...

C++23中std::span和std::basic_string_view可平凡复制提案解析

文章目录 一、引言二、相关概念解释2.1 平凡复制&#xff08;Trivially Copyable&#xff09;2.2 std::span2.3 std::basic_string_view 三、std::span和std::basic_string_view的应用场景3.1 std::span的应用场景3.2 std::basic_string_view的应用场景 四、P2251R1提案对std::…...

【KWDB 2025 创作者计划】_KWDB时序数据库特性及跨模查询

一、概述 数据库的类型多种多样&#xff0c;关系型数据库、时序型数据库、非关系型数据库、内存数据库、分布式数据库、图数据库等等&#xff0c;每种类型都有其特定的使用场景和优势&#xff0c;KaiwuDB 是一款面向 AIoT 场景的分布式、多模融合、支持原生 AI 的数据库…...

树 Part 9

二叉树的建立 了解了二叉树的遍历方法&#xff0c;我们如何在内存中生成一棵二叉链表的二叉树呢&#xff1f;树都没有&#xff0c;哪来遍历。所以我们还得来谈谈关于二叉树建立的问题。 如果要在内存中建立一个如左图这样的树&#xff0c;为了能让每个结点确认是否有左右孩子…...

leetcode每日一题 -- 3362. 零数组变换 III

思路 题意是要找出[最少的区间]使nums数组变为零数组,并且使用的区间可以不连续 我的第一想法是先给区间按照左边界排序(就像区间合并题的准备工作那样)这样的可以使用最大堆,每次将右区间值最大(也就是区间范围最大)的区间应用到差分数组中但是,后续如何处理还是不太会,遂看…...

PARSCALE:大语言模型的第三种扩展范式

----->更多内容&#xff0c;请移步“鲁班秘笈”&#xff01;&#xff01;<----- 随着人工智能技术的飞速发展&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为推动机器智能向通用人工智能&#xff08;AGI&#xff09;迈进的核心驱动力。然而&#xff0c;传统的…...

【 开源:跨平台网络数据传输的万能工具libcurl】

在当今这个互联互通的世界中&#xff0c;数据在各种设备和平台之间自由流动&#xff0c;而 libcurl&#xff0c;就像一把跨平台的万能工具&#xff0c;为开发者提供了处理各种网络数据传输任务所需的强大功能。它不仅是一个库&#xff0c;更是一种通用的解决方案&#xff0c;可…...

2025版 JavaScript性能优化实战指南从入门到精通

JavaScript作为现代Web应用的核心技术&#xff0c;其性能直接影响用户体验。本文将深入探讨JavaScript性能优化的各个方面&#xff0c;提供可落地的实战策略。 一、代码层面的优化 1. 减少DOM操作 DOM操作是JavaScript中最昂贵的操作之一&#xff1a; // 不好的做法&#x…...

RAGFlow知识检索原理解析:混合检索架构与工程实践

一、核心架构设计 RAGFlow构建了四阶段处理流水线,其检索系统采用双路召回+重排序的混合架构: S c o r e f i n a l = α ⋅ B M...

leetcode 148. Sort List

148. Sort List 题目描述 代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNod…...

C#学习11——集合

一、集合 是一组对象的容器&#xff0c;提供了高效的存储、检索和操作数据的方式。 C# 集合分为泛型集合&#xff08;推荐使用&#xff09;和非泛型集合&#xff0c;主要位于System.Collections和System.Collections.Generic命名空间中。 二、集合有哪些&#xff1f; 1&…...

paddlehub搭建ocr服务

搭建环境&#xff1a; Ubuntu20.041080Ti显卡 由于GPU硬件比较老&#xff0c;是Pascal架构&#xff0c;只能支持到paddle2.4.2版本&#xff0c;更高版本无法支持&#xff1b;同时&#xff0c;因为paddle老版本的依赖发生了变化&#xff0c;有些地方存在冲突&#xff0c;花费了…...

CSS3过渡

一、什么是CSS3过渡 CSS3 过渡&#xff08;transitions&#xff09;是一种效果&#xff0c;它允许你平滑地改变CSS属性的值&#xff0c;从一个状态过渡到另一个状态。是一种动画转换的过程&#xff0c;如渐现、渐弱、动画快慢等。过渡效果可以在用户与页面进行交互时触发&#…...

比斯特自动化|移动电源全自动点焊机:高效点焊助力移动电源制造

在移动电源市场蓬勃发展的当下&#xff0c;电池组合的点焊工艺要求愈发严格。移动电源全自动点焊机应运而生&#xff0c;成为提升生产效率与产品质量的关键设备。 工作原理与结构组成 移动电源全自动点焊机通过瞬间放电产生高温&#xff0c;使电池极耳与镍带等材料在极短时间…...

游戏引擎学习第305天:在平台层中使用内存 Arena 的方法与思路

回顾前一天内容&#xff0c;并为今天的开发工作设定方向 我们正在直播制作完整游戏&#xff0c;当前正在实现一个精灵图&#xff08;sprite graph&#xff09;的排序系统。排序的代码已经写完&#xff0c;过程并不复杂&#xff0c;虽然还没做太多优化&#xff0c;但总体思路比…...

[Java][Leetcode middle] 6. Z 字形变换

法一&#xff0c;自己想的 使用一个复合结构的 List<ArrayList<String>> 来存储每一行的字母&#xff0c;最后按序输出。 使用flag来判断到底放到哪一行上去。flag按照&#xff1a;0–1–2–1–0–1–2这样变化&#xff0c;实现躺着的Z字形。 public String conve…...

零基础设计模式——第二部分:创建型模式 - 原型模式

第二部分&#xff1a;创建型模式 - 5. 原型模式 (Prototype Pattern) 我们已经探讨了单例、工厂方法、抽象工厂和生成器模式。现在&#xff0c;我们来看创建型模式的最后一个主要成员——原型模式。这种模式关注的是通过复制现有对象来创建新对象&#xff0c;而不是通过传统的…...

完整改进RIME算法,基于修正多项式微分学习算子Rime-ice增长优化器,完整MATLAB代码获取

1 简介 为了有效地利用雾状冰生长的物理现象&#xff0c;最近开发了一种优化算法——雾状优化算法&#xff08;RIME&#xff09;。它模拟硬雾状和软雾状过程&#xff0c;构建硬雾状穿刺和软雾状搜索机制。在本研究中&#xff0c;引入了一种增强版本&#xff0c;称为修改的RIME…...

【1——Android端添加隐私协议(unity)1/3】

前言&#xff1a;这篇仅对于unity 发布Android端上架国内应用商店添加隐私协议&#xff0c;隐私协议是很重要的东西&#xff0c;没有这个东西&#xff0c;是不上了应用商店的。 对于仅仅添加隐私协议&#xff0c;我知道有三种方式,第一种和第二种基本一样 1.直接在unity里面新…...

笔记本6GB本地可跑的图生视频项目(FramePack)

文章目录 &#xff08;一&#xff09;简介&#xff08;二&#xff09;本地执行&#xff08;2.1&#xff09;下载&#xff08;2.2&#xff09;更新&#xff08;2.3&#xff09;运行&#xff08;2.4&#xff09;生成 &#xff08;三&#xff09;注意&#xff08;3.1&#xff09;效…...

Android View的事件分发机制

ViewGroup的事件分发逻辑 从Activity传递给Window&#xff0c;再传递给ViewGroup&#xff0c;ViewGroup的dispatchTouchEvent()会被调用&#xff0c;如果onInterceptTouchEvent()返回true 转交自身onTouchEvent()处理,如果返回false继续向子View传递,子View的dispatchTouchEve…...

Python字符串格式化(二): f-string的进化

文章目录 一、Python 3.6&#xff1a;重新发明字符串格式化&#xff08;2016&#xff09;1. 语法糖的诞生&#xff1a;表达式直嵌技术2. 性能与可读性的双重提升3. 奠定现代格式化的基础架构 二、Python 3.7&#xff1a;解锁异步编程新场景&#xff08;2018&#xff09;1. 异步…...

力扣HOT100之二叉树:124. 二叉树中的最大路径和

这道题是困难题&#xff0c;靠自己想还是挺难想的&#xff0c;还是去看的灵神的题解&#xff0c;感觉还是要多复习一下这道题。这道题的思路和之前做的543. 二叉树的直径很像&#xff0c;可以参考之前的这篇博客。这里我们还是用递归来做&#xff0c;定义一个lambda函数来实现递…...

【C++】位图+布隆过滤器

1.位图 概念 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常是用来判断某个数据存不存在的或是否被标记。 1.二进制位表示 &#xff1a; 位图中的每一位&#xff08;bit&#xff09;代表一个元素的状态。通常&…...

Google Agent Development Kit与MCP初试

Google Agent Development Kit与MCP初试 一、背景知识二、搭建智能大脑 - Ollama服务器2.1 为什么要先搭建Ollama&#xff1f;2.2 搭建ollama服务器2.2.1 安装2.2.2 试着用curl命令"问"AI一个问题&#xff1a; 三、构建智能体工坊 - ADK环境3.1 创建容器3.2 安装核心…...

云原生+大数据

虚拟化&#xff1a; 虚拟化&#xff0c;是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个逻辑计算机&#xff0c;每个逻辑计算机可运行不同的操作系统&#xff0c;并且应用程序都可以在相互独立的空间内运行而互不影响&#xff0c;从而显著提…...