1. 类型推导
1.1 auto
- auto可以让编译器在编译期就推导出变量的类型
- auto的使⽤必须⻢上初始化,否则⽆法推导出类型
- auto在⼀⾏定义多个变量时,各个变量的推导不能产⽣⼆义性,否则编译失败
- auto不能⽤作函数参数
- 在类中auto不能⽤作⾮静态成员变量
- auto不能定义数组,可以定义指针
- auto⽆法推导出模板参数
- 在不声明为引⽤或指针时,auto会忽略等号右边的引⽤类型和const(常量)、volatile(易变性)限定
- 在声明为引⽤或者指针时,auto会保留等号右边的引⽤和cv属性
1.2 decltype
- ⽤于推导表达式类型,这⾥只⽤于编译器分析表达式的类型,表达式实际不会进⾏运算
- decltype不会像auto⼀样忽略引⽤和cv属性,decltype会保留表达式的引⽤和cv属性
- 对于decltype(exp),若exp是表达式,decltype(exp)和exp类型相同;若exp是函数调⽤,decltype(exp)和函数返回值类型相同;若exp是左值,decltype(exp)是exp类型的左值引⽤。
- auto和decltype配合使用:
template<typename T, typename U>
auto add(T t, U u)->decltype(t + u){
return t + u;
}
2. 范围for循环
- 基于范围的迭代写法,for(变量:对象)表达式:
string str ("some thing");
for(char c : str) cout << c << endl;
- 对vector中的元素进⾏遍历:
vector<int> arr(5, 100);
for(vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) cout << *i << endl;
for(auto &i : arr) cout << i << endl;
3. 右值引用
3.1 左值和右值
- 左值:可以放在等号左边,可以取地址并有名字
- 右值:不可以放在等号左边,不能取地址,没有名字
- 其定义形式为:Type &&var;其中 Type 表示变量的类型,var 表示变量名。
- ++i、–i是左值,i++、i–是右值
3.2 左值引用和右值引用
- 左值引用:
- 左值引⽤就是对左值进⾏引⽤的类型,是对象的⼀个别名
- 并不拥有所绑定对象的堆存,所以必须⽴即初始化。 对于左值引⽤,等号右边的值必须可以取地址,如果不能取地址,则会编译失败,或者可以使⽤const引⽤形式
- 右值引用:
- 右值引用所引用的对象是一个右值,右值对象是指其生命周期即将结束的对象,例如一次函数调用的返回值、临时变量等。
- 表达式等号右边的值需要是右值,可以使⽤std::move函数强制把左值转换为右值
3.3 右值引用的使用场景
- 以字符串类为例,假设有一个字符串对象 A,我们要把它赋值给另外一个字符串对象 B
string A = "HELLO";
string B = A;
- 这样做的结果是,我们创建了两个相同内容但是不同地址的字符串对象,其中一个占用了额外的内存,存在性能问题。
- 为了解决这个问题,C++11 移动语义提供了将对象 A 移动到 B 中的操作:
- 当我们需要把一个对象赋值给另一个对象时,编译器会调用其复制构造函数或者赋值构造函数来创建一个新对象。但是,在某些情况下,复制操作会非常耗时;如果复制操作是不必要的,此时,使用移动构造函数,它不需要复制整个对象,而只是需要将原对象中的指针等资源转移到目标对象中即可,这样可以提高复制性能。
string A = "HELLO";
string B = std::move(A);
- 上述代码就可以把对象 A 移动到 B 中,并不需要创建新的对象和分配内存。
- 需要注意的是,只能对一个右值引用或者一个将要销毁的对象调用 std::move() 函数,否则会导致潜在的内存问题和错误。此外,在使用右值引用时,需要注意数据的生命周期问题,不要在使用后再次使用已经被移动的对象。
3.4 forward 完美转发
- forward 完美转发实现了参数在传递过程中保持其值属性的功能,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。
- &&既可以对左值引用,亦可以对右值引用,但它后面的val值本身是个左值;&只能左值引用
template <class T>
void Print(T &t){ cout << "L" << t << endl; }
template <class T>
void Print(T &&t){ cout << "R" << t << endl; }
template <class T>
void func(T &&t){
Print(t);
Print(move(t));
Print(forward<T>(t));
}
int main(){
cout << "-- func(1) --" << endl;
func(1);
cout << "-- func(x) --" << endl;
int x = 10;
int y = 20;
func(x); // x本身是左值
cout << "-- func(forward<int>(y)) --" << endl;
func(forward<int>(y)); //T为int,以右值方式转发y
cout << "-- func(forward<int&>(y)) --" << endl;
func(forward<int&>(y));
cout << "-- func(forward<int&&>(y)) --" << endl;
func(forward<int&&>(y));
return 0;
}
运行结果:
3.5 emplace_back 减少内存拷贝和移动
- emplace_back能就地通过参数构造对象,不需要拷贝或者移动内存,相比 push_back能更好地避免内存的拷贝与移动,使容器插入元素的性能得到进一步提升。在大多数情况下应该优先使用emplace_back来代替push_back。
4. lambda 匿名函数
- 表示⼀个可调⽤的代码单元,没有命名的内联函数,不需要函数名因为我们直接(⼀次性的)⽤它,不需要其他地⽅调⽤它
- 一般情况下,编译器可以自动推断出lambda表达式的返回类型,所以我们可以不指定返回类型。但是如果函数体内有多个return语句时,编译器无法自动推断出返回类型,此时必须指定返回类型。
- 语法:[ 捕获列表 ] (参数列表) -> 返回类型 { 函数体 }
int main(){
auto Add = [](int a, int b)->{ return a+b; };
cout << Add(1, 2) << endl;
return 0;
}
值捕获
void func() { // c=10 d=20 add(1,2)=23
int c = 10;
int d = 20;
auto add = [c, d](int a, int b) {
cout << "c=" << c << endl;
cout << "d=" << d << endl;
return d+a+b;
};
d = 50;
cout << "add(1, 2)=" << add(1, 2) << endl;
}
引用捕获
void func() { // c=1 d=50 add(1,2)=53
int c = 10;
int d = 20;
auto add = [&c, &d](int a, int b) {
c = a;
cout << "c=" << c << endl;
cout << "d=" << d << endl;
return d+a+b;
};
d = 50;
cout << "add(1, 2)=" << add(1, 2) << endl;
}
隐式捕获、空捕获
void func() {
int c = 10;
int d = 20;
// 如果[&]代表引用捕获,[=]代表值捕获,如果[]为空捕获表示Lambda不能使用所在函数中的变量
auto add = [&](int a, int b) {
c = a;
cout << "c=" << c << endl;
cout << "d=" << d << endl;
return d+a+b;
};
d = 50;
cout << "add(1, 2)=" << add(1, 2) << endl;
}
表达式捕获
// c++14之后支持捕获右值,允许捕获的成员用任意的表达式进行初始化,被声明的捕获变量类型会根据 表达式进行判断,判断方式与使用 auto 本质上是相同的
void func4() {
auto p = make_unqiue<int>(1);
auto add = [v1=1, v2=move(p)](int x, int y)->int{ return x+y+v1+(*v2); };
cout << "add(1, 2)=" << add(1, 2) << endl;
}
泛型捕获
void func() {
auto add = [](auto x, auto y){ return x+y; };
cout << "add(1, 2)=" << add(1, 2) << endl;
cout << "add(1.1, 2.2)=" << add(1.1, 2.2) << endl;
}
可变
// 采用值捕获的方式,lambda不能修改其值,如果想要修改,使用mutable修饰;或者采用引用捕获的方式
void func() {
int v = 5;
auto ff = [v]() mutable { return ++v; };
v = 0;
auto j = ff(); // ff捕获的是值,即为5,因此j为6
int m = 5;
auto gg = [&m] {return ++m; };
m = 0;
auto n = gg(); // gg捕获的是引用,即为0,因此n为1
}