c++11 新特性
声明:本文主要参考自c++11新特性
1.概述
c++11 是c++标准的第三个版本, 业内通常称为c++2.0(或现代c++), 而在之前的版本(c++98, c++03)则称为c++1.0, 并自c++11 之后开始每三年发布一个标准,其新特性主要分为语言和标准库两种, 本文主要列举一些常用的c++11特性及示例
通过cppreferrence.com 可以查看到各个版本的特性以及支持的编译器,代码中可以通过__cplusplus 来查看目前的编译器支持的版本
1 | //输出为年月,如201402 |
2. 语言新特性
2.1 自动类型推导
C++11引入auto和decltype关键字,使之在编译期推导出变量或者表达式的类型:
auto 用于推导变量类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/*
简便书写复杂类型(复杂的语法细节不深究,只说用到的,开发语言毕竟只是工具, 后文同),注意
1.在不声明为引用或指针时,auto会忽略等号右边的引用类型和cv(const和volatile)限定
2.在声明为引用或者指针时,auto会保留等号右边的引用和cv属性
*/
int i = 0;
auto *a = &i; // a是int*
auto &b = i; // b是int&
auto c = b; // c是int,忽略了引用
const auto d = i; // d是const int
auto e = d; // e是int
const auto& f = e; // f是const int&
auto &g = f; // g是const int&
auto func = [&] {}; // 通常使用auto 因为不关心lamda是什么(实际是匿名仿函数类)deltype 用于推导表达式类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/*
一般都是模板(设计框架)时才需要用到,类似typeof
注意:
decltype不会像auto一样忽略引用和cv属性,decltype会保留表达式的引用和cv属性
若exp是左值,decltype(exp)是exp类型的左值引用
*/
int func() { return 0; }
decltype(func()) i; // i为int类型
int x = 0;
decltype(x) y; // y是int类型
decltype(x + y) z; // z是int类型
const int &i = 1;
decltype(i) b = 2; // b是const int&
int a = 0, b = 0;
decltype(a + b) c = 0; // c是int,因为(a+b)返回一个右值
decltype(a += b) d = c;// d是int&,因为(a+=b)返回一个左值
*/dectype 与auto 混合使用的示例
1
2
3
4
5
6
7
8
9//1.声明返回类型
//直接使用decltype(x+y) add(T1 x, T2 y) 推导返回类型会报错因为x, y 未定义,故而需要后置返回类型
template <tyname T1, typename T2>
auto add(T1 x, T2 y)->decltype(x + y);
//2.传递lamda
auto cmp = [](){};
std::set(Person, decltype(cmp)) coll(cmp);
2.2 右值引用
c++03 开始引入右值引用, c++11 中被广泛应用于性能提升(通过移动右值资源来减少不必要的拷贝操作)
左右值:无法取地址赋值的东西即为右值, 最常见为临时变量, 其他为左值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int i = 2; // 2 是右值
int x = i + 2; // (i + 2) 是右值
int *p = &(i + 2);//错误,不能对右值取地址
i + 2 = 4; // 错误,不能对右值赋值
class A;
A a = A(); // A() 是右值
int sum(int a, int b) {return a + b;}
int i = sum(1, 2) // sum(1, 2) 是右值
//有些定义为只能在=右边即为右值不够严谨如
string a, b;
a+b = "1";左右引用:对应左右值的引用
1
2
3
4
5
6
7
8
9
10
11
12//左值引用
int a = 5;
int &b = a; // b是左值引用
b = 4;
int &c = 10; // error,10无法取地址,无法进行引用
const int &d = 10; // ok,因为是常引用,引用常量数字,这个常量数字会存储在内存中,可以取地址
//右值引用
int a = 4;
int &&b = a; // error, a是左值
// 通过move 可以将左值转换成右值, 这个函数只是将类型转了一下,将其标记为有效但未定义的状态, 至于如何使用则是在设计中自行处理的
int &&c = std::move(a);右值引用的作用
- 过往由于只能操作左值故大量的临时对象只能先被拷贝才能继续使用, 引入右值引用直接偷取原来的资源可以大大较少拷贝的发生
- 通常std::move, 移动拷贝构造, 移动拷贝赋值是结合使用的, 以vector的push_back 为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class A{
A(A&& temp) {
data_ = temp.data_;
size_ = temp.size_;
// 为防止temp析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}
privtae:
int *data_;
int size_;
};
int main(){
A a;
A b(a);
// c++11 STL 中使用这种方式来提升性能如
std::string str1 = "aacasxs";
std::vector<std::string> vec;
vec.push_back(str1); // 传统方法,copy
vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值
} - 由于移动拷贝构造, 移动拷贝赋值的引入,以往我们说的BIG Three 变成了Big Five, 那么默认的移动构造大致如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 每个成员查看都可以移动时会合成默认的移动构造和赋值函数
struct X
{
int i; // 基础类型可移动,将被直接拷贝
std::string s; // 调用string 的move
};
struct hasX{
X men;
};
int main()
{
X x;
x.i = 1;
x.s = "test";
X x2 = std::move(x);
cout << x.i<<" "<<x.s<<endl;
cout << x2.i<<" "<<x2.s;
hasX hx, hx2 = std::move(hx);
} - 完美转发使用std::forward 来避免转发错误,一般日常开发用的比较少
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26void change2(int&& ref_r) {
ref_r = 1;
}
void change3(int& ref_l) {
ref_l = 1;
}
// change的入参是右值引用
// 有名字的右值引用是 左值,因此ref_r是左值
void change(int&& ref_r) {
change2(ref_r); // 错误,change2的入参是右值引用,需要接右值,ref_r是左值,编译失败
change2(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过
change2(std::forward<int &&>(ref_r));//ok,std::forward的T是右值引用类型(int&&),符合条件b,因此u(ref_r)会被转换为右值,编译通过
change3(ref_r); // ok,change3的入参是左值引用,需要接左值,ref_r是左值,编译通过
change3(std::forward<int &>(ref_r)); // ok,std::forward的T是左值引用类型(int&),符合条件a,因此u(ref_r)会被转换为左值,编译通过
// 可见,forward可以把值转换为左值或者右值
}
int main() {
int a = 5;
change(std::move(a));
}
- g++ 默认使用返回值优化(ROV),返回值类型与函数的返回值类型相同或返回局部对象时会默认调用move
2.3 列表初始化
在C++11中可以直接在变量名后面加上初始化列表来进行对象的初始化。一般使用到结构体或简单的类里边
1 | struct A { |
STL 的容器可以初始化任意长度主要是因为
1 | //如此我们也可以初始化任意长度了 |
2.4 Lamda表达式
std::function 是C++11引入的一个可调用对象(即可以直接使用func_name(args…)的调用对象)的封装器
- std::bind 可以将一个可调用对象与一个参数绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using namespace std;
bool isLess(double a, double b){
return a < b;
}
int main()
{
//第二个参数绑定为1
auto func = std::bind(isLess, std::placeholders::_1, 1);
cout<< func(2)<<endl;
return 1;
}
- std::bind 可以将一个可调用对象与一个参数绑定
Lamda 表达式, 可以捕获上下文变量的匿名的函数
1
2
3
4
5
6
7
8
9
10
11
12格式:
auto fun = [ captures ] ( params )(optional) specs { body }
生成类似:
class fun{
private:
captures;
public:
auto operator()(params){
body;
}
};- 以上格式从c++11 至今均可使用, 其他c++标准还加入了其他格式
- 其本质是一个std::function,具体的是一个仿函数对象
- capture: 捕获列表 常用包含格式:
- [] 空捕获
- [a] 值捕获外部变量a, 内部拥有相同(const)副本变量
- [&a] 引用捕获外部变量a, 内部引用副本变量
- [=] 值捕获外部作用域所有变量
- [&] 引用捕获外部作用域所有变量
- [=, &a] 除了a引用捕获外其他使用值捕获, [&, a] 同理
- specs 函数标志, 常用的mutable 表示值引用的类型允许修改(注意仍然是对象内的变量,不修改外部变量如)
1
2
3
4
5
6
7
8int a = 1;
auto func = [a]()mutable{
a++;
};
func();
//输出1
std::cout<<a<<endl;
2.5 模板改进
- 引入using 定义别名
1
2typedef std::vector<std::vector<int>> vvi;
using vvi = std::vector<std::vector<int>>; //c++11 起 - 增加函数模板的默认参数支持(之前支支持类模板默认参数)
1
2
3
4
5
6
7
8// 默认值无顺序要求
template<class T = int, class R>
R func(T arg){
R a;
return a;
}
func<double>(0.0); - 不定参数模板
- 非可变参数:可以使用std::initializer_list
, 参考2.3 - 可变参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21类模板:
template<class... Types>
struct Tuple {};
Tuple<int, float> t;
函数模板:
template<class T>
void f(T& t){} //处理参数通常使用递归的方式
template<class T, class... Types>
void f(T t, Types... args){
std::cout<<t<<std::endl; //处理第一个
f(args...); //递归剩下的
}
f(2, 1.0,"hello"); //生成方法
//f(int, double, string)
//f(double, string)
//f(string)
- 非可变参数:可以使用std::initializer_list
2.6 其它一些简便语法
- range-for 循环
1
2
3
4std::vector<int> vec = {1,2,3};
for(const auto& item: vec){
std::cout<<item<<std::endl;
} - 委托构造
1
2
3
4
5class A{
public:
A(int a){}
A(int a, int b): A(a){} //委托构造
}; - 继承构造
1
2
3
4
5
6
7
8
9
10
11class A{
public:
A(int, int);
A(int);
};
class B:public A{
public:
using A::A;// 继承构造
};
B a(1, 2);//可以直接调用
2.7 其他一些关键字
- nullptr: 平替以往的NULL
- final: 修饰类,表示不可再被继承
- override: 修饰派生函数,表明该方法是被继承的
- defalut: 让编译器生成默认的对应的”Big-Three” 方法
- delete:阻止编译器生成默认的对应的”Big-Three” 方法
1
2
3
4
5class A{
public:
A()=defalut;
A(A&) = delete;
}; - explicit: 阻止隐式转换
1
2
3
4
5
6class A{
public:
explicit A(int);
};
A a = 1; //error - constexpr:表示常量表达式, 即在编译期可以被推导确定的常量 如 constexpr int i = 2;
- enum class: 有作用域的枚举
1
2
3
4
5enum class A{
Red;
};
A::Red; - sizeof: 增加对类成员变量的支持
3. 标准库
3.1 头文件
新式头文件不带副档名.h, 如#include
3.2 智能指针
3.3 并发编程
参考文献:
1.https://zhuanlan.zhihu.com/p/139515439
2.https://zhuanlan.zhihu.com/p/85668787
3.https://zhuanlan.zhihu.com/p/335994370#:~:text=%E5%8F%82%E6%95%B0%E4%B8%BA%E5%B7%A6%E5%80%BC%E5%BC%95%E7%94%A8%E6%84%8F,%E5%BC%95%E7%94%A8%E6%84%8F%E5%91%B3%E7%9D%80%E7%A7%BB%E5%8A%A8%E3%80%82
4.https://www.zhihu.com/tardis/zm/art/194198073?source_id=1005