C/C++ ③ —— C++11新特性

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
}
  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值