目录
- 一.c++11.简介
- 二.列表初始化和initializer_list
- 1.列表初始化
- 2.initializer_list
- 三.简化声明
- 1.auto
- 2.decltype
- 四.新增容器
- 1.array
- 2.forward_list
- 3.unordered_map/set
- 五.右值引用与移动语义
- 1.左值和右值
- 2.左值引用
- 3.右值引用
- 4.移动构造和移动赋值
- 5.万能引用和引用折叠
- 6.完美转发
- 六.lambda表达式
- 七.模板可变参数
- 八.emplace
- 九.包装器
- 1.function
- 2.bind
一.c++11.简介
C++11是C++编程语言的一个重要版本更新,它在2011年被标准化。C++引入了进140个新特性,并且修正的大量的缺陷,使得C++编程更加高效和便捷。
二.列表初始化和initializer_list
1.列表初始化
c++98中,语言给我们提供了可用花括号{}初始化数组和结构体的方法如下所示:
c++11中则扩大了{}的适用范围,并且在使用时可以不加等号。也可以用在new表达式中。
2.initializer_list
initializer_list是c++11新引入的标准库模板。
由上图可知initializer_list是一个常量区数组,存放在常量区。
各大容器也支持了initializer_list的构造。
三.简化声明
1.auto
auto可以帮助我们自动推导变量的类型,在一些复杂的情况下有很大的作用,如下图所示:
如果这里自己写迭代器类型确实比较麻烦,auto很好的帮助我们解决了这个问题。
2.decltype
decltype 是C++11引入的强大工具,能够查询表达式的类型而不计算表达式的值。在泛型编程、模板编程和类型推断中,decltype
提供了极大的灵活性和类型安全性。通过结合使用 auto 和 decltype,我们可以编写更加简洁、灵活和可维护的代码。
先举个简单的例子窥探他的作用:
int a = 5;
decltype(a) b = 10; // b的类型是int
decltype还经常和auto相结合使用:
int x = 42;
auto y = x; // y的类型是int
decltype(x) z = y; // z的类型也是int
在模板时,我们不能明确形参的类型,也可以使用decltype来推导:
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
decltype 还可以处理更复杂的表达式,推断出嵌套表达式的类型:
struct Example {int value;
};int main() {Example ex;decltype(ex.value) n = 100; // n的类型是intreturn 0;
}
四.新增容器
c++11中又引入了四个新容器:
1.array
C++11标准引入了std::array容器,这是一个新的数组容器类型,位于头文件中。相比于静态数组,其越界检查更加严格。
如上图所示在越界打印 arr2[6] 时编译器并没有报错而在打印 arr[1] 的时候直接就报错了
2.forward_list
C++11标准引入了forward_list,这是一个单向链表容器,位于头文件<forward_list>中。forward_list提供了高效的插入和删除操作,特别适用于对内存和性能有严格要求的应用场景。
#include <forward_list>
#include <iostream>int main() {std::forward_list<int> flist = {1, 2, 3, 4, 5}; // 声明并初始化一个包含5个整数的单向链表// 输出链表元素for (const auto& elem : flist) {std::cout << elem << " ";}std::cout << std::endl;return 0;
}
总体来说 上面两个新容器是比较鸡肋的。
3.unordered_map/set
C++11标准引入了哈希表容器sunordered_map和unordered_set,分别位于头文件<unordered_map>和<unordered_set>中。这些容器提供了高效的元素查找、插入和删除操作,是传统map和set的无序版本,基于哈希表实现。在我们之前在哈希表的学习中就已经了解了。
五.右值引用与移动语义
1.左值和右值
什么是左值和右值?
左值(Lvalue):指向特定内存位置的值,可以出现在赋值运算符的左侧。左值有持久的存储周期,意味着它们在表达式结束后依然存在。
右值(Rvalue):不指向特定内存位置的值,只能出现在赋值运算符的右侧。右值是临时的,在表达式结束后通常会被销毁。
- 简单来说左值可以取地址而右值不可以。
- 左值可以出现在赋值符号的左边而右值不可以出现在左边
- 右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等
int a = 10; // 'a' 是左值
int b = a + 5; // 'a + 5' 是右值
左值举例:
- 变量名(如 a)
- 数组元素(如 arr[0])
- 解引用指针(如 *ptr)
- 字符串字面值(如 “Hello”)
右值举例:
- 字面值(如 10, 3.14)
- 表达式结果(如 a + b)
- 返回右值的函数调用(如 std::move(x))
2.左值引用
左值引用就是我们之前说的引用。
const左值可以引用右值:
int main()
{const int& a = 10;return 0;
}
3.右值引用
右值引用如下所示:
template<class T>
T fmin(T x, T y)
{return x < y ? x : y;
}int main()
{double x = 1.1;double y = 2.1;int&& a = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);return 0;
}
需要注意的是右值时不能取地址的,我们使用右值引用导致右值被存储到特定位置,且可以取到地址,所以我们说右值引用的属性是左值
右值引用可以引用move后的左值:
int main()
{int a = 10;int&& b = move(a);return 0;
}
4.移动构造和移动赋值
C++11引入了移动语义,使得开发者能够高效地转移资源而不是复制它们。移动构造函数和移动赋值运算符是实现这一特性的关键组件,它们通过"移动"资源而不是复制资源,显著提高了程序性能。
- 移动构造函数:当一个对象被初始化为另一个对象的右值时调用,用于“移动”资源。
- 移动赋值运算符:当一个对象被赋值为另一个对象的右值时调用,用于“移动”资源。
string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "移动拷贝" << endl;swap(s);}string& operator=(string&& s){cout << "移动赋值" << endl;swap(s);return *this;}
- 内置类型的右值:纯右值
- 自定义类型的右值:将亡值
c++11这里在对连续的拷贝/赋值时做了特殊处理,合多个为一(提升效率),将函数返回识别成右值,方便进行移动拷贝/构造。
- 浅拷贝的类不需要实现移动构造,因为没有需要转移的资源,传值拷贝的代价也不大。
- 左值引用的价值是减少拷贝,提高效率
- 右值引用的价值弥补左值引用没有解决的场景:传值返回
5.万能引用和引用折叠
引用折叠规则定义了在多层引用的情况下,最终的引用类型。以下是引用折叠规则:
1.所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)
C++11引入了一个强大的特性,称为万能引用(Universal Reference),以及引用折叠(Reference Collapsing)规则。这两个特性极大地增强了模板编程的灵活性和性能,尤其在实现泛型函数和容器时。
什么是万能引用?
万能引用是指能够绑定到左值和右值的引用。在模板参数推导中,如果一个函数参数类型是T&&,并且T是一个模板参数,那么这个引用就是一个万能引用
template <typename T>
void func(T&& param); // param 是万能引用
万能引用的主要应用场景是转发函数,即完美转发(Perfect Forwarding)。完美转发使得我们能够编写接受任意参数并将其完美传递给其他函数的模板。
6.完美转发
在模板编程中,有时需要将参数传递给另一个函数,而不改变其原有的类型属性(即保持左值或右值特性)。完美转发通过万能引用(Universal References)和std::forward实现,使得传递的参数保留其本来的引用类型。
下面的案例可以一窥究竟:
#include <iostream>
#include <utility> // forward// 一个简单的函数,用于展示完美转发
void process(int& x) {cout << "Lvalue reference: " << x << endl;
}void process(int&& x) {cout << "Rvalue reference: " << x << endl;
}// 泛型函数,完美转发参数
template <typename T>
void forwarder(T&& arg) {process(forward<T>(arg)); // forward 进行完美转发
}int main() {int a = 42;forwarder(a); // 传递左值forwarder(42); // 传递右值forwarder(move(a)); // 传递右值return 0;
}
六.lambda表达式
首先是lambda表达式的格式:
[捕捉列表] (参数列表) mutable -> 返回值 { 函数体 }
捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
-
[var]:表示值传递方式捕捉变量var
-
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
-
[&var]:表示引用传递捕捉变量var
-
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
-
[this]:表示值传递方式捕捉当前的this指针
参数列表:与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
性。使用该修饰符时,参数列表不可省略(即使参数为空)。
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导。
可以根据以下演示代码窥探其使用方法:
int main()
{int a = 0, b = 2;double rate = 2.555;auto add1 = [](int x, int y)->int {return x + y; };auto add2 = [](int x, int y) {return x + y; };auto add3 = [rate](int x, int y) {return (x + y)* rate; };cout << add1(a, b) << endl;cout << add2(a, b) << endl;cout << add3(a, b) << endl;auto swap1 = [add1](int& x, int& y) {int tmp = x;x = y;y = tmp;cout << add1(x, y) << endl;};swap1(a, b);cout << a << " " << b << endl;return 0;
}
演示案例2:
int main()
{int x = 0, y = 2;auto swap1 = [x, y]() mutable {// mutable让捕捉的x和y可以改变了,// 但是他们依旧是外面x和y的拷贝int tmp = x;x = y;y = tmp;};swap1();cout << x << ' ' << y << endl;// 引用的方式捕捉auto swap2 = [&x, &y](){int tmp = x;x = y;y = tmp;};swap2();cout << x << ' ' << y << endl;int a = 0;int b = 1;int c = 2;int d = 3;const int e = 1;cout << &e << endl;// 引用的方式捕捉所有对象,除了a// a用传值的方式捕捉auto func = [&, a] {//a++;b++;c++;d++;//e++;cout << &e << endl;};func();cout << a << ' ' << b << ' '<< c << ' ' << d << ' ' << e;return 0;
}
还有些注意事项:
- 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
- 捕捉列表不允许变量重复传递,否则就会导致编译错误。
- lambda表达式之间不能相互赋值
- lambda表达式属于匿名对象
- 可以用typeid看类型(用uuid标识的)
- 底层是仿函数
- lambda是个类每次生成的类会用uuid标识
七.模板可变参数
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
//模板可变参数
template <class T , class ...Args>
void ShowList(T value , Args... args)
{cout << sizeof...(args) << endl;}int main()
{ShowList(1);ShowList(1, 2);ShowList(1, 2, 2.2);ShowList(1, 2, 2.2, string("xxxx"));// ...return 0;
}
运行上述代码我们可以观察参数包中有几个参数:
在底层如何展开参数包的呢,如下图所示:
// 递归终止函数
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;
}
还有另一种方法可以不使用递归展开,逗号表达式展开参数包:
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;
}
八.emplace
C++11 引入的 emplace 系列函数为标准库容器带来了显著的改进。与需要先创建对象然后再添加到容器中的 insert 或 push_back 不同,emplace 允许直接在容器内构造对象。这可以通过消除不必要的拷贝或移动操作来提高性能。
看下面的两个代码来看他的作用:
#include <map>
#include <string>
#include <iostream>using namespace std;int main() {map<int, string> myMap;// 使用 emplace 构造并插入一个键值对myMap.emplace(1, "one");// 遍历并打印 map 中的元素for (const auto& pair : myMap) {cout << pair.first << " : " << pair.second << endl;}return 0;
}
九.包装器
1.function
function 是一个通用、多态的函数包装器,它可以存储、复制和调用任何可调用目标。它的常见用法包括回调函数和事件处理器的定义。
用法案例:
#include <iostream>
#include <functional>using namespace std;// 一个简单的函数
void displayMessage(const string& message) {cout << message << endl;
}int main() {// 定义一个 std::function 包装器,包装一个返回 void,参数为 const string& 的函数function<void(const string&)> func = displayMessage;// 使用 std::function 调用被包装的函数func("Hello, std::function!");return 0;
}
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()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}
2.bind
bind 是一个函数适配器,用于创建绑定了特定参数的新函数对象。它允许我们将函数的一部分参数预先绑定,从而简化后续的调用。
bind可改变参数顺序:
double Plus(int a, int b, double rate)
{return (a + b) * rate;
}double PPlus(int a, double rate, int b)
{return rate * (a + b);
}class Sub
{
public:int sub(int a, int b){return a - b;}
};class SubType
{
public:static int sub(int a, int b){return a - b;}int ssub(int a, int b, int rate){return (a - b) * rate;}
};int Sub(int a, int b)
{return a - b;
}int main()
{function<int(int, int)> rSub = bind(Sub, placeholders::_2, placeholders::_1);cout << rSub(10, 5) << endl;function<double(int, int)> Plus1 = bind(Plus, placeholders::_1, placeholders::_2, 4.0);function<double(int, int)> Plus2 = bind(Plus, placeholders::_1, placeholders::_2, 4.2);function<double(int, int)> Plus3 = bind(Plus, placeholders::_1, placeholders::_2, 4.4);cout << Plus1(5, 3) << endl;cout << Plus2(5, 3) << endl;cout << Plus3(5, 3) << endl;// double PPlus(int a, double rate, int b)function<double(int, int)> PPlus1 = bind(PPlus, placeholders::_1, 4.0, placeholders::_2);function<double(int, int)> PPlus2 = bind(PPlus, placeholders::_1, 4.2, placeholders::_2);cout << PPlus1(5, 3) << endl;cout << PPlus2(5, 3) << endl;function<double(int, int)> Sub1 = bind(&SubType::sub, placeholders::_1, placeholders::_2);SubType st;function<double(int, int)> Sub2 = bind(&SubType::ssub, &st, placeholders::_1, placeholders::_2, 3);cout << Sub1(1, 2) << endl;cout << Sub2(1, 2) << endl;function<double(int, int)> Sub3 = bind(&SubType::ssub, SubType(), placeholders::_1, placeholders::_2, 3);cout << Sub3(1, 2) << endl;cout << typeid(Sub3).name() << endl;return 0;
}
还有几点需要注意的:
调非静态成员函数前面应该加上&,bind的底层和lambda类似都是仿函数。
不想更改顺序的参数不会影响placeholders:1,2.3…的顺序