跳到主要内容 C++11 新特性深度解析:可变参数模板与 Lambda 表达式 | 极客日志
C++ 算法
C++11 新特性深度解析:可变参数模板与 Lambda 表达式 C++11 引入多项重要特性,包括可变参数模板、移动语义、右值引用及 Lambda 表达式。文章详细解析了 emplace 系列接口在容器中的高效用法,对比了 push_back 与 insert 的性能差异。阐述了默认成员函数、move 构造、default/delete 关键字的使用场景。介绍了 final 和 override 在继承控制中的作用。涵盖 STL 新增容器如 unordered_map/set,以及范围 for 循环。重点讲解 Lambda 表达式的语法、捕捉列表机制(传值/传引用)、底层仿函数原理及应用场景,如排序比较器替代。提供完整链表实现与测试代码示例。
这个文档在 C++98、C++11 时候还行,之后就完全没法用了……
这个行,包括 C++26 都同步了,以后主要会看这个。
4 ~> 可变参数模版
4.5 emplace 系列接口
4.5.1 不同容器 emplace 系列接口展示 C++11 以后 STL 容器新增了 emplace 系列的接口,emplace 系列的接口均为模板可变参数,功能上兼容 push 和 insert 系列,但是 emplace 还支持新玩法。假设容器为 container,emplace 还支持直接插入构造 T 对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造 T 对象。
emplace_back 总体而言是更高效,推荐以后使用 emplace 系列替代 insert 和 push 系列。push_back 效率其实也不差,传参数包那种 emplace_back 效率才有优势,传右值传左值两者效率其实是差不多的,传 string 参数包有区别——push_back 要先移动构造再构造,emplace_back 直接构造——一步到位。
如下图,我们模拟实现了 list 的 emplace 和 emplace_back 接口,这里把参数包不断往下传递,最终在结点的构造中直接去匹配容器存储的数据类型 T 的构造,所以达到了前面说的 emplace 支持直接插入构造 T 对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造 T 对象。
传递参数包过程中,如果是 Args&&... args 的参数包,要用完美转发参数包,方式如下 std::forward<Args>(args)...,否则编译时包扩展后右值引用变量表达式就变成了左值。
4.5.3 emplace 系列接口在 list.h 文件中的使用
4.5.4 emplace 系列接口在 Test.cpp 文件中的使用
4.5.5 万能引用
5 ~> 新的类功能
5.1 默认成员函数:默认移动构造和移动赋值 原来 C++ 类中,有 6 个默认成员函数:构造函数 / 析构函数 / 拷贝构造函数 / 拷贝赋值重载 / 取地址重载 / const 取地址重载 ,最后重要的是前 4 个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的 。C++11 新增了两个默认成员函数:移动构造函数和移动赋值运算符重载 。
条件苛刻: 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节赋值,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
5.2 成员函数声明时要给缺省值 成员变量声明时给的缺省值(类内成员初始化)会在构造函数的初始化阶段使用。具体来说:如果某个成员变量没有在初始化列表中显式初始化,编译器会自动在初始化列表中使用这个缺省值来初始化它;如果该成员在初始化列表中被显式初始化了,那么显式初始化的值会覆盖声明时的缺省值。
5.3 default 和 delete
5.3.1 概念 C++11 可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。
如果能想要限制某些默认函数的生成,在 C++98 中,是该函数设置成 private(私有),并且 只声明不实现 ,这样只要其他人想要调用就会报错。在 C++11 中更简单,只需在该函数声明加上【 = delete】即可,该语法指示编译器不生成对应函数的默认版本,称【= delete】修饰的函数为 删除函数 。
5.3.2 最佳实践
5.4 目标构造函数和委托构造函数(了解)
5.4.1 目标构造函数
5.4.2 委托构造函数
5.4.3 最佳实践
5.5 final 和 override final 和 override 我们在继承和多态那个章节已经进行了详细讲过了,如果忘了可以查阅相关章节。
这里重新展示一下关于 final 和 override 的思维导图。
6 ~> C++11:STL 的变化
6.1 新的容器 下面这张图中圈起来的就是 C++11 的 STL 中的新增容器 ,但是实际中最有用的是 unordered_map 和 unordered_set 。
C++11 新增容器:array、forward_list(单链表)、unordered_map 和 unordered_set(真正有用的就这俩)。
这两个我们前面已经进行了非常详细的介绍,其他的大家了解一下即可。
6.2 新的接口 STL 中容器的新接口也不少,最重要的就是右值引用和移动语义相关的 push / insert / emplace 系列接口 (插入数据系列的接口 );移动构造和移动赋值 (雪中送炭),还有 initializer_list 版本的构造 (锦上添花的作用)等,这些前面都讲过了,还有一些无关痛痒的 cbegin / cend 等需要时查查文档即可。
6.3 宝藏:范围 for 容器的范围 for 遍历,这个在容器部分也讲过了。
7 ~> lambda
7.1 lambda 表达式的语法
7.1.1 概念 lambda 表达式本质是一个匿名函数对象 ,跟普通函数不同的是:lambda 表达式可以定义在函数内部 。
lambda 表达式语法使用层而言没有类型,所以我们 一般是用 auto 或者模板参数定义的对象去接收 lambda 对象。
[capture-list] (parameters) -> return type { function body }
[ capture-list ] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节在下面介绍捕捉列表的部分再细说。捕捉列表为空也不能省略。
( parameters ) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同 () 一起省略。
->returntype :返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{functionbody} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
7.1.2 最佳实践
7.2 lambda 的应用场景
7.2.1 说明 在介绍 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的,以后我们会不断接触到,主要这个是一个我们之前没有接触过的新知识点,读者要留意一下哦!
7.2.2 最佳实践 struct Goods {
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& gl, const Goods& gr) {
return gl._price < gr._price;
}
};
struct ComparePriceGreater {
bool operator () (const Goods& gl, const Goods& gr) {
return gl._price > gr._price;
}
};
struct CompareEvaluateGreater {
bool operator () (const Goods& gl, const Goods& gr) {
return gl._evaluate < gr._evaluate;
}
};
struct CompareEvaluateLess {
bool operator () (const Goods& gl, const Goods& gr) {
return gl._evaluate < gr._evaluate;
}
};
int main () {
vector<Goods> v = {{"苹果" , 2.1 , 5 }, {"香蕉" , 3 , 4 }, {"橙子" , 2.2 , 3 }, {"菠萝" , 1.5 , 4 }};
sort (v.begin (), v.end (), [](const Goods& gl, const Goods& gr) { return gl._price < gr._price; });
sort (v.begin (), v.end (), [](const Goods& gl, const Goods& gr) { return gl._price > gr._price; });
sort (v.begin (), v.end (), [](const Goods& gl, const Goods& gr) { return gl._evaluate < gr._evaluate; });
sort (v.begin (), v.end (), [](const Goods& gl, const Goods& gr) { return gl._evaluate > gr._evaluate; });
return 0 ;
}
大家应该注意到了被注释掉的代码段其实就是用到了我们的 lambda 表达式,这四个比较的仿函数,用 lambda 表达式只要一段代码就能完成,这就是 lambda 表达式,非常的方便。原理部分详细介绍一下——其实 lambda 原理和同样是 C++11 更新的内容——范围 for——的原理很类似,这里的'很像'不是指 lambda 的原理也是底层被替换成迭代器(lambda 的底层是一个 operator(),`编译器会帮你生成一个仿函数,)这里我们说的'很像',指的是 lambda 和范围 for 都是编译器帮你生成!
7.3 捕捉列表(*)
7.3.1 概念 lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。
第一种捕捉方式 是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x,y,&z] 表示 x 和 y 是值捕捉,z 是引用捕捉。
第二种捕捉方式 是在捕捉列表中隐式捕捉,我们在捕捉列表写一个 = 表示隐式值捕捉,在捕捉列表写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些变量。
第三种捕捉方式 是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=,&X] 表示其他变量隐式值捕捉,x 引用捕捉;[&,X,y] 表示其他变量引用捕捉,x 和 y 值捕捉。当使用混合捕捉时,第一个元素必须是 & 或 =,并且 & 混合捕捉时,后面的捕捉变量必须是值捕捉,同理 = 混合捕捉时,后面的捕捉变量必须是引用捕捉。
lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下, lambda 捕捉列表是被 const 修饰的,也就是说传值捕捉的过来的对象不能修改,mutable 加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
7.3.2 最佳实践
7.4 lambda 的原理
7.4.1 原理 lambda 的原理和范围 for 很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围 for 这样的东西。范围 for 底层是迭代器,而 lambda 底层是仿函数对象,也就说我们写了一个 lambda 以后,编译器会生成一个对应的仿函数的类。
仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,**lambda 参数 / 返回类型 / 函数体就是仿函数 operator() 的参数/返回类型/函数体**,lambda 的捕捉列表本质是生成的仿函数类的成员变量——也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉不同,编译器也不是傻瓜,实际上,编译器看使用哪些就传哪些对象。
7.4.2 最佳实践 语法层拿不到 lambda 的类型,不是说没有类型,而是我们拿不到,但是编译器能够拿到。
7.4.3 捕捉列表就是仿函数的成员函数
7.4.4 补充:成员函数中写了 lambda 也可以修改成员变量,这里 this 捕捉的本质是 lambda 可以访问成员变量。
注意:局部的静态变量和全局的全局变量,不用也不能捕捉(两者的生命周期在全局)!
C++11 完整代码示例与实践演示
list.h: #pragma once
namespace jqj {
template <class T >
struct list_node {
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node (const T& x = T ()) : _next(nullptr ), _prev(nullptr ), _data(x)
{}
};
template <class T , class Ref , class Ptr >
struct list_iterator {
using Self = list_iterator<T, Ref, Ptr>;
using Node = list_node<T>;
Node* _node;
list_iterator (Node* node) : _node(node) {}
Ref operator *()
{
return _node->_data;
}
Ptr operator ->()
{
return &_node->_data;
}
Self& operator ++()
{
_node = _node->_next;
return *this ;
}
Self operator ++(int )
{
Self tmp (*this ) ;
_node = _node->_next;
return tmp;
}
Self& operator --()
{
_node = _node->_prev;
return *this ;
}
Self operator --(int )
{
Self tmp (*this ) ;
_node = _node->_prev;
return tmp;
}
bool operator !=(const Self& s) const
{
return _node != s._node;
}
bool operator ==(const Self& s) const
{
return _node == s._node;
}
};
template <class T >
class list {
using Node = list_node<T>;
public :
using iterator = list_iterator<T, T&, T*>;
using const_iterator = list_iterator<T, const T&, const T*>;
iterator begin () {
return iterator (_head->_next);
}
iterator end () {
return iterator (_head);
}
const_iterator begin () const {
return const_iterator (_head->_next);
}
const_iterator end () const {
return const_iterator (_head);
}
void empty_init ()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
list () {
empty_init ();
}
list (initializer_list<T> il) {
empty_init ();
for (auto & e : il) {
push_back (e);
}
}
template <class InputIterator>
list (InputIterator first, InputIterator last)
{
empty_init ();
while (first != last) {
push_back (*first);
++first;
}
}
list (size_t n, T val = T ())
{
empty_init ();
for (size_t i = 0 ; i < n; ++i) {
push_back (val);
}
}
list (int n, T val = T ())
{
empty_init ();
for (size_t i = 0 ; i < n; ++i) {
push_back (val);
}
}
~list () {
clear ();
delete _head;
_head = nullptr ;
_size = 0 ;
}
list (const list<T>& lt) {
empty_init ();
for (auto & e : lt) {
push_back (e);
}
}
list<T>& operator =(const list<T>& lt) {
if (this != <) {
clear ();
for (auto & e : lt) {
push_back (e);
}
}
return *this ;
}
void swap (list<T>& lt)
{
std::swap (_head, lt._head);
std::swap (_size, lt._size);
}
void clear () {
iterator it = begin ();
while (it != end ()) {
it = erase (it);
}
}
template <class ... Args>
void emplace_back (Args&&... args) {
emplace (end (), args...);
emplace (end (), forward<Args>(args)...);
}
template <class ... Args>
void emplace (iterator pos, Args&&... args) {
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node (forward<Args>(args)...);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
void push_back (T&& x) {
insert (end (), forward<T>(x));
}
void push_front (const T& x) {
insert (begin (), x);
}
void pop_back () {
erase (--end ());
}
void pop_front () {
erase (begin ());
}
void insert (iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node (x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
void insert (iterator pos, T&& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node (move (x));
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
}
iterator erase (iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
--_size;
return next;
}
size_t size () const {
return _size;
}
private :
Node* _head;
size_t _size = 0 ;
};
}
Test.cpp: #define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <map>
#include <list>
#include <string>
using namespace std;
#include <assert.h>
#include <algorithm>
namespace Alice {
class string {
public :
typedef char * iterator;
typedef const char * const_iterator;
iterator begin () { return _str; }
iterator end () { return _str + _size; }
const_iterator begin () const { return _str; }
const_iterator end () const { return _str + _size; }
string (const char * str = "" ) : _size(strlen (str)), _capacity(_size) {
cout << "string(char* str)-构造" << endl;
_str = new char [_capacity + 1 ];
strcpy (_str, str);
}
void swap (string& s) {
std::swap (_str, s._str);
std::swap (_size, s._size);
std::swap (_capacity, s._capacity);
}
string (const string& s) {
cout << "string(char* str)-拷贝构造" << endl;
reserve (s._capacity);
for (auto ch : s) {
push_back (ch);
}
}
string (string&& s) {
cout << "string(char* str)-移动构造" << endl;
swap (s);
}
string& operator =(const string& s) {
cout << "string(char* str)-拷贝赋值" << endl;
if (this != &s) {
_str[0 ] = '\0' ;
_size = 0 ;
reserve (s._capacity);
for (auto ch : s) {
push_back (ch);
}
}
return *this ;
}
string& operator =(string&& s) {
cout << "string(char* str)-移动赋值" << endl;
swap (s);
return *this ;
}
~string () {
delete [] _str;
_str = nullptr ;
}
char & operator [](size_t pos) {
assert (pos < _size);
return _str[pos];
}
void reserve (size_t new_capacity) {
if (new_capacity > _capacity) {
char * tmp = new char [new_capacity + 1 ];
if (_str) {
strcpy (tmp, _str);
delete [] _str;
}
_str = tmp;
_capacity = new_capacity;
}
}
void push_back (char ch) {
if (_size >= _capacity) {
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2 ;
reserve (newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0' ;
}
string& operator +=(char ch) {
push_back (ch);
return *this ;
}
const char * c_str () const { return _str; }
size_t size () const { return _size; }
private :
char * _str = nullptr ;
size_t _size = 0 ;
size_t _capacity = 0 ;
};
string addStrings (string num1, string num2) {
string str;
int end1 = num1. size () - 1 , end2 = num2. size () - 1 ;
int next = 0 ;
while (end1 >= 0 || end2 >= 0 ) {
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0 ;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0 ;
int ret = val1 + val2 + next;
next = ret / 10 ;
ret = ret % 10 ;
str += ('0' + ret);
}
if (next == 1 ) str += '1' ;
reverse (str.begin (), str.end ());
cout << &str << endl;
return str;
}
}
struct Date {
int _y;
int _m;
int _d;
Date (int year, int month, int day) : _y(year), _m(month), _d(day) {}
};
int x = 0 ;
auto func1 = []() { x++; };
class A {
public :
void Func () {
int x = 0 , y = 1 ;
auto f1 = [=] { _a1++; return x + y + _a1 + _a2; };
cout << f1 () << endl;
auto f2 = [&] { x++; _a1++; return x + y + _a1 + _a2; };
cout << f2 () << endl;
auto f3 = [x, this ] { _a1++; return x + _a1 + _a2; };
cout << f3 () << endl;
}
private :
int _a1 = 0 ;
int _a2 = 1 ;
};
int main () {
int a = 0 , b = 1 , c = 2 , d = 3 ;
auto func1 = [a, &b] {
int ret = a + b;
x++;
return ret;
};
cout << func1 () << endl;
auto func2 = [=] {
int ret = a + b + c;
return ret;
};
cout << func2 () << endl;
auto func3 = [&] { a++; c++; d++; };
func3 ();
cout << a << " " << b << " " << c << " " << d << endl;
auto func4 = [& , a, b] {
c++; d++;
return a + b + c + d;
};
func4 ();
cout << a << " " << b << " " << c << " " << d << endl;
auto func5 = [a, &b](int x) { ++b; return a + b + x; };
return 0 ;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online