C++11(右值引用、移动构造、移动赋值、lambda表达式)

1、列表初始化(了解其用法)
2、范围for循环
3、final 与 override
  • final:定义最终类或修饰虚函数,是该类和虚函数不能被继承
  • override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
  • 注意区分:如果基类定义纯虚函数,则子类必须重写纯虚函数。
4 、新增加容器—静态数组array、forward_list(单链表)以及unordered_xx系列

在这里插入图片描述

5、变量类型推导
  • auto
    可以使用auto来推导变量的实际类型,auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型

  • typeid
    只能查看类型不能用其结果类定义类型。

    int a = 10;
    cout<<typeid(a).name()<<endl; 
    
  • decltype
    decltype是根据表达式的实际类型推演出定义变量时所用的类型。比如:

    1、推演表达式类型作为变量的定义类型
    int a = 10;  int b = 20; 
    decltype(a+b) c;// 用decltype推演a+b的实际类型,作为定义c的类型
    2、推演函数返回值的类型
     int add(int x1, int x2)
    {
    	return x1 + x2;
    } 
     int main()
    {
        //如果没有带参数,推导函数的类型 
    	cout << typeid(decltype(add)).name() << endl;
    	//如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数 
    	cout << typeid(decltype(add(10, 20))).name() << endl;
    	system("pause");
    	return 0;
    }
    
6、默认成员函数控制
  • default:可以让编译器显示生成某个默认成员函数。

  • delete:可以让编译器禁止生成某个默认成员函数。

    A() = default;//让编译器显示生成默认构造函数
    A(const A&) = delete;// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载 
    
7、右值引用
  • 左值:可以被修改的对象,或可以取地址的对象

    如:const int a = 10; a 不能被修改但是能取地址,所以 a 是一个左值

  • 右值:

    ①纯右值:常量,基本类型表达式的返回值(如:100,a+b)
    ②将亡值:函数按照值的方式进行返回的临时自定义对象、表达式返回的临时自定义对象。(如:string s1,s2; s3 = s1+s2;当中的s1+s2就会返回一个临时自定义对象拷贝构造s3)

  • 左值引用:左值引用就是给左值取别名。左值引用只可以引用左值,但是 const 左值引用既可以引用左值也可以引用右值。

    int a = 10;
    const int& a = 10;//const 引用右值
    
    int b = 20;
    const int& c = b;//const 引用左值
    
  • 右值引用:右值引用就是给右值取别名。右值引用只可以引用右值,但是可以使用move 将左值的属性改为右值进行右值引用。

    int b = 100;
    int&& a = 10;//右值引用引用右值
    int&& c = move(b);//右值引用通过move()引用左值
    
7.1 移动语义(移动构造,移动赋值)

C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式。移动语义主要是右值引用带来的移动构造移动赋值。移动构造与移动赋值也属于默认成员函数,在C++11中,编译器会为类默认生成一个移动构造、移动赋值,该移动构造、移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造、移动赋值。对于不涉及资源管理的类来说,移动构造、移动赋值并起不了什么作用。

在之前写的 MyString 类的时候,我们重载了operator+()这个函数:

class String//其他函数已舍去
{
public:
	.......
	String(const String& s) 
		: _str(new char[strlen(s._str) + 1]) 
	{
		strcpy(_str, s._str);
	}
	
	String operator+(const String& s)  
	{ 
		char* pTemp = new char[strlen(_str) + strlen(s._str) + 1]; 
		strcpy(pTemp, _str);  
		strcpy(pTemp + strlen(_str), s._str);   
		String strRet(pTemp);      
		return strRet; 
	}  
	........     
private:   
	char* _str;
};
int main()
{
	String s1("hello");
	String s2("world");
	String s3 = s1 + s2;
	return 0;
}

在我们执行 s3 = s1 + s2 这句代码时候,首先会调用 operator+()这个函数,由于strRet是个局部变量,出了作用域就销毁了,所以返回值是 string 而不是 string& 。而此时真正返回的值是一个strRet 通过拷贝构造出的一个临时对象,临时对象构造完以后 strRet 就被销毁 了。这个临时对象是一个右值(右值中的将亡值)。然后通过 s3 = 临时对象 调用拷贝构造函数,这个临时对象可以传参给拷贝构造函数(因为const 左值引用可以引用右值)。完成这些后进行拷贝构造,拷贝构造完成后这个临时对象就会被析构。仔细观察会发现:strRet、临时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,这样做还增加了拷贝的次数。如何解决这个问题呢?–》使临时对象被充分的利用起来?****移动构造和移动赋值可以解决这个问题:

	String(String&& s)
		: _str(nullptr)
	{
		swap(_str, s._str);
	}
	int main()
	{
		String s1("hello");
		String s2("world");
		String s3 = s1 + s2;//这里是移动构造
		return 0;
	}

因为strRet对象的生命周期在创建好临时对象后就结束了,即将亡值,C++11认为其为右值,在用strRet构造 临时对象时,就会采用移动构造,即将strRet中资源转移到临时对象中。而临时对象也是右值,因此在用临 时对象构造s3时,也采用移动构造,将临时对象中资源转移到s3中,整个过程,只需要创建一块堆内存即 可,既省了空间,又大大提高程序运行的效率。当然还有移动赋值,原理同上面相同。

	String& operator=(String&& s)
	{
		_str = nullptr;
		swap(_str, s._str);
		return *this;
	}
	int main()
	{
		String s1("hello");
		String s2("world");
		String s3;
		s3 = s1 + s2;//这里用的是移动赋值
		return 0;
	}

右值引用的应用主要在函数参数(移动构造、移动赋值)与函数的返回值(临时对象),都在上面那个例子体现。应用在函数参数还有一些其他场景,容器的插入:
在这里插入图片描述
在这里插入图片描述
移动构造带来的副作用:move将s1转化为右值后,在实现s2的拷贝时就会使用移动 构造,此时s1的资源就被转移到s2中,s1就成为了无效的字符串(s1会被置空),要避免这种情况的产生:

int main()
{
	string s1("hello");
	string s2(move(s1));
	return 0;
}
7.2完美转发

函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相 应实参是右值,它就应该被转发为右值。但是,模板传参过程中,实参的右值属性就会被丢失,都会被处理成左值。如何解决这个问题?

完美转发可以解决,C++11通过forward函数来实现完美转发:
在这里插入图片描述

7.3 总结右值引用的作用:
  • ①:实现移动语义(移动构造与移动赋值)

  • ②:给中间临时变量取别名。

    string s1("hello"); 
    string s2(" world");  
    stirng&& s4 = s1 + s2//s4就是s1和s2拼接完成之后结果的别名 
    
  • ③:实现完美转发 (通过 forward 实现)

8、 lambda表达式

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法,如果待排序元素为自定义类型,需要用户定义排序时的比较规则。随着C++语法的发展,开始觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去 写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了 极大的不便。因此,在C11语法中出现了lambda表达式。

struct Goods 
{ 
	string _name;  
	double _price;
};
int main()
{
	Goods gds[] = { { "苹果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, { "菠萝", 1.5 } };

	//第一种用法
	auto ret = [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; };
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), ret);

	//第二种写法,不用获取返回值直接用
	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; });

	for (int i = 0; i < 4; i++)
	{
		cout << gds[i]._name << ":" << gds[i]._price << endl;
	}
	
	system("pause");
	return 0;
}

lambda 表达式语法:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
lambda 表达式实现原理

  • 实际在底层编译器对于lambda表达式的处理方式,完全和仿函数一样。即:如果定义了一 个 lambda 表达式,编译器会自动生成一个名字叫做lambda_uuid ( uuid是一个字符串生成技术,它可以使每个 lambda 表达式都生成一个名字唯一的类),在该类中重载了operator(),lambda 表达式自身或用 auto 接受的返回值其实是一个对象,可以像仿函数那样使用。

    struct add
    {
    	int operator()(const int& a, const int& b)
    	{
    		return a + b;
    	}
    };
    int main()
    {
        //仿函数用法
    	add tmp1;
    	tmp1(10, 20);
    	
    	//lambda表达式用法
    	auto tmp2 = [](const int& a, const int& b)->int{return a + b; };
    	tmp2(10, 20);
    	return 0;
    }
    

    可以看出 lambda 表达式用法与仿函数的用法并无差别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值