c++11 右值引用与Lambda表达式

C++11新特性

  • 初始化列表
  • 默认成员函数的控制
  • 右值引用
    • 左值与右值
    • emplace_back 与 push_back
  • lambda表达式

初始化列表

在C语言中,对于数组的初始化,我们可以这样进行

int arr[] = { 1, 2, 3, 4, 5 };

允许数组使用花括号{}的方式进行初始化,但是在C++98中的vector数组以及一些自定义的类型中,这样初始化还是不被允许的,而C++11对此做出了修正。

他允许verctor使用花括号{}的方式对数组进行初始化.

vector<int> arr2 { 1, 2, 3, 4, 5, 6, 7 };
vector<int> arr3 = { 1, 2, 3, 4, 5, 6, 7 };

再进行初始化的过程中,数组名和花括号中可以不添加=

支持花括号进行初始化的原因:

如果想要使用多个对象对列表进行初始化,需给该类(模板类)添加一个带有initializer_list类型参数的构造函数 和 =运算符的重载。

  • initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin(),end()迭代器,以及获取区间中元素个数的方法size()
  • #include ,在使用之前需要加上该模板的头文件
	template<class T>
	class vector
	{
	public:
		//初始化列表
		vector(initializer_list<T> tmpArr) 
			: _capacity(tmpArr.size())
			, _size(0)
		{
			_arr = new T[_capacity];
			for (auto num : tmpArr)
				_arr[_size++] = num;
		}

		vector<T>& operator=(initializer_list<T> tmpArr) {
			delete[] _arr;
			_capacity = tmpArr.size();
			_arr = new T[_capacity];
			_size = 0;

			for (auto num : tmpArr)
				_arr[_size++] = num;

			return *this;
		}

		size_t size()
		{
			return _size;
		}

		T operator[](size_t idx)
		{
			return _arr[idx];
		}
	private:
		T* _arr;
		size_t _size;
		size_t _capacity;
	};

默认成员函数的控制

在C++中,当我们创建一个类,这个类中会默认有六个成员函数。如果在一些特殊的场合下,我们不想让这些默认的成员函数发生作用,我们在C++98中可以这样来:

	class Date
	{
	private:
		Date();
		~Date();

	private:
		int _year;
		int _month;
		int _day;
	};

我们可以把不想存在的默认构造函数变成私有成员,并只对默认函数进行声明不进行实现,这样在类外就不能进行访问,就可以去掉这个默认的构造函数了。

但是在C++11中,新增了一个delete关键字,他可以取消掉默认构造函数的作用。就是在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

	class Date
	{
	public:
		Date() = delete;
		~Date() = delete;

	private:
		int _year;
		int _month;
		int _day;
	};

对应的还有一个显示缺省函数。在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数

	class Date
	{
	public:
		Date() = default;
		~Date() = default;

	private:
		int _year;
		int _month;
		int _day;
	};

右值引用

C++相比C语言,提出了引用的概念,但是引用只能对一个已经声明的对象进行操作,给他一个别名,引用的变量和引用的实体共用一块内存空间,就相当于指针,只是我们在使用的时候不用像使用指针那样(*p),写法简单。

但是对于那些临时变量,这时使用引用是不可以的,因为他们本身就没有名字,是一个匿名对象。
为了解决这一问题,C++11加入了右值引用的概念,他也是一个别名,只是他只能对右值进行使用。

int&& num = 10;

左值与右值

左值,一般就是可以放在 =左边,并且可以取地址的变量

  1. 普通的变量,他们有名字,那么就可以取地址,就可以作为左值
  2. const 修饰的常量,虽然不可以修改,但是可以取地址,所以说也是左值
  3. 函数的返回值是引用类型,那么也可以认为是左值
  4. 变量的引用类型,也是左值

右值,一般认为只能放在=右边,或者不能取地址的变量

  1. 函数的返回值是一个临时变量,那么就认为这是一个右值
  2. 变量本身就是一个临时变量,或者匿名对象,则为右值
	int a = 10;

	const int& ra1 = 10;
	const int& ar2 = a;

而对于const引用而言,他可以引用右值,也可以引用左值。而普通的引用只可以引用左值。

而右值引用只能引用右值,不能引用左值。

emplace_back 与 push_back

在这里插入图片描述
C++11中,对于拷贝构造的时候,如果所传的参数是一个临时变量,那么就会使用右值引用的方式进行操作,而emplace_back函数就是一个对于临时变量操作的接口,他可以完成对临时对象的尾插操作,避免多余的拷贝。

用基本类型(int,char)来看的话效果不是那么明显,使用string类型来看一下关于右值引用
c++11 右值引用与Lambda表达式_第1张图片
我们可以看到这个过程一共调用了两次拷贝构造,一次是参数的拷贝构造,一次是插入元素时的拷贝构造,这就造成了数据的利用率比较低。

这是因为const 引用既可以引用左值,也可以引用右值,所以对于左值和右值不容易区分。在C++11中使用右值引用,再加一个函数重载就可以了。

那么对于右值引用,只需要将他的参数(一个临时对象),进行swap交换一下就可以了,避免了赋值的时候再调用一次拷贝构造。

	vector(vector<T>&& tmpArr)
	{
		std::swap(_arr, tmpArr._arr);
		std::swap(_capacity, tmpArr._capacity);
		std::swap(_size, tmpArr._size);
	}

	vector<T>& operator=(vector<T>&& tmpArr)
	{
		std::swap(_arr, tmpArr._arr);
		std::swap(_capacity, tmpArr._capacity);
		std::swap(_size, tmpArr._size);

		return *this;
	}

lambda表达式

有时候在做题的时候,经常会看到一些大佬的程序中有这样的代码

	sort(arr.begin(), arr.end(), 
		[](const int& a, const int& b)->bool
		{
			return a > b;
		});

而自己在写的时候是这样的,调用自己在函数外部写的仿函数,然后进行排序

bool (const int& a, const int& b)
{
	return a > b;
}

sort(arr.begin(), arr.end(), cmp);

这就是C++11中的lambda表达式,他可以说是一个匿名函数,在函数的内部声明 + 使用。

他的格式如下:

[capture-list] (parameters) mutable -> return-type { statement }
  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

关于捕捉列表,他其实就是为了让我们在使用lambda表达式的时候,如果想要使用当前作用域内的变量,但是又不想让他变成参数,那么就可以使用捕捉列表对想要使用的变量进行捕捉

内容 意义
[var] 表示值传递方式捕捉变量var
[=] 表示值传递方式捕获所有父作用域中的变量(包括this)
[&var] 表示引用传递捕捉变量var
[&] 表示引用传递捕捉所有父作用域中的变量(包括this)
[this] 表示值传递方式捕捉当前的this指针

c++11 右值引用与Lambda表达式_第2张图片
lambda表达式与普通函数在汇编中的调用方式
c++11 右值引用与Lambda表达式_第3张图片
可以看到两种函数的调用的方式是相同的,都是call指令,只是lambda表达式在调用的时候,会自己给函数起一个lambda的名字,然后使用call进行调用。

你可能感兴趣的:(C++)