返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++11语法之右值引用的示例讲解
  • 368
分享到

C++11语法之右值引用的示例讲解

2024-04-02 19:04:59 368人浏览 薄情痞子
摘要

目录一、{}的扩展initializer_list的讲解:二、c++11一些小的更新decltypenullptr范围for新容器三、右值引用右值真正的用法完美转发默认成员函数总结一

一、{}的扩展

在原先c++的基础上,C++11扩展了很多初始化的方法。

#include<iOStream>
using namespace std;
struct A
{
	int _x;
	int _y;
};
int main()
	int a[] = { 1,2,3,4,5 };
	
	int a1[] { 1,2,3,4,5 };
	int* p = new int[5]{ 1,2,3,4,5 };
	A b = { 1,2 };//初始化
	A b2[5]{ {1,1},{2,2},{3,3},{4,4},{5,5} };
	A* pb = new A{ 1,2 };
	A* pb2 = new A[5]{ {1,1},{2,2},{3,3},{4,4},{5,5} };
	return 0;
}

结果:

全部初始化正常,vs下指针后面跟数字可以表示显示多少个。

除了上面的 new[]{}我认为是比较有意义的,很好的解决了new的对象没有构造函数又需要同时创建多个对象的场景。

除了上面的,下面的这种方式底层实现不相同。

initializer_list的讲解:

vector<int> v{1,2,3,4};

跳转initializer_list实现

实际上上面就是通过传参给initializer_list对象,这个对象相当于浅拷贝了外部的{1,2,3,4}的头指针和尾指针,这样vector的构造函数就可以通过迭代器遍历的方式一个个的push_back到自己的容器当中。上述过程initializer_list是很高效的,因为他只涉及浅拷贝指针和一个整形。

#include <iostream>
template <class T>
class initializer_list
{
public:
    typedef T         value_type;
    typedef const T&  reference; //注意说明该对象永远为const,不能被外部修改!
    typedef const T&  const_reference;
    typedef size_t    size_type;
    typedef const T*  iterator;  //永远为const类型
    typedef const T*  const_iterator;
private:
    iterator    _M_array; //用于存放用{}初始化列表中的元素
    size_type   _M_len;   //元素的个数
    
    //编译器可以调用private的构造函数!!!
    //构造函数,在调用之前,编译会先在外部准备好一个array,同时把array的地址传入模板
    //并保存在_M_array中
    constexpr initializer_list(const_iterator __a, size_type __l)
    :_M_array(__a),_M_len(__l){};  //注意构造函数被放到private中!
    constexpr initializer_list() : _M_array(0), _M_len(0){} // empty list,无参构造函数
    //size()函数,用于获取元素的个数
    constexpr size_type size() const noexcept {return _M_len;}
    //获取第一个元素
    constexpr const_iterator begin() const noexcept {return _M_array;}
    //最后一个元素的下一个位置
    constexpr const_iterator end() const noexcept
    {
        return begin() + _M_len;
    }  
};

而{}初始化,和{}调用initializer_list组合起来是可以让初始化变得方便起来的,下面的m0用了initializer_list进行初始化,但还是比较麻烦。但m1用了{}进行单个对象初始化加initializer_list的组合之后变得方便快捷起来。

#include<map>
int main()
{
	map<int, int> m0 = { pair<int,int>(1,1), pair<int,int>(2,2), pair<int,int>(3,3) };
	
	map<int, int> m1= { {1,1},{2,2},{3,3} };
	return 0;
}

小总结:

一个自定义类型调用{}初始化,本质是调用对应的构造函数;自定义类型对象可以使用{}初始化,必须要有对应的参数类型和个数;STL容器支持{}初始化,则容器必须有一个initializer_list作为参数的构造函数。

二、C++11一些小的更新

auto:
定义变量前加auto表示自动存储,表示不用管对象的销毁,但是默认定义的就是自动类型,所以这个关键字后面就不这样用了,C++11中改成了自动推导类型。

#include<cstring>
int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcmp;

	cout << typeid(p).name() << endl;
	cout << typeid(pf).name() << endl;
	return 0;
}

结果:

int *
int (__cdecl*)(char const *,char const *)

decltype

auto只能推导类型,但推导出来的类型不能用来定义对象,decltype解决了这点,推导类型后可以用来定义对象。
decltype(表达式,变量),不能放类型!

#include<cstring>
int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcmp;

	decltype(p) pi;//int*
	pi = &i;
	cout << *pi << endl;//10
	return 0;
}

nullptr

NULL在C中是0,是int类型。C++11添加nullptr表示((void*)0),避免匹配错参数。

范围for

支持迭代器就支持范围for

新容器

array,没啥用,静态的数组,不支持push_back,支持方括号,里面有assert断言防止越界,保证了安全

foward_list,没啥用,单链表,只能在节点的后面插入。

unordered_map,很有用,后面讲

unordered_set,很有用,后面讲

三、右值引用

左值
作指示一个数据表达式(变量名或解引用的指针)。
左值可以在赋值符号左右边,右值不能出现在赋值符号的左边。

const修饰符后的左值,不能给他赋值,但是可以取他的地址。左值引用就是给左值的引用,给左值取别名。

左值都是可以获取地址,基本都可以可以赋值
但const修饰的左值,只能获取地址,不能赋值。

右值?
右值也是一个数据的表达式,如字面常量,表达式返回值,传值返回的函数的返回值(不能是左值引用返回)。右值不能取地址,不能出现在赋值符号的左边。

关键看能不能取地址

给右值取别名就用右值引用,右值引用是左值了,放在赋值符号的左边了。

右值不能取地址,但是给右值引用后,引用的变量是可以取地址的,并且可以修改!
右值引用存放的地方在栈的附近。

int main()
{
	int&& rra = 10;
	//不想被修改 const int&& rra
	cout << &rra << endl;
	rra = 100;
	return 0;
}

左值引用总结:

  • 左值引用只能引用左值,不能引用右值。
  • 但是const修饰的左值引用既可以引用左值,又可以引用右值。在没有右值引用的时候就必须采用这种方式了。
  • 左值最重要的特征是都可以取地址,即使自定义类型也有默认的取地址重载。

右值引用总结:
右值引用只能引用右值,不能引用左值。
右值引用可以引用move以后的左值。

左值引用可以接着引用左值引用,右值引用不可以。
原因:右值引用放到左边表示他已经是一个左值了,右值引用不能引用左值!

int main()
{
	int a = 10;
	int& ra = a;
	int& rb = ra;
	int&& rra = 10;
	int&& rrb = rra;//err:无法从“int”转换为“int && "
	return 0;
}

匹配问题:

void func(const int& a)
{
	cout << "void func(const int& a)" << endl;
}
void func(int&& a)
	cout << "void func(int&& a)" << endl;
int main()
	int a = 10;
	func(10);
	func(a);
	return 0;

右值在有右值引用会去匹配右值引用版本!

右值真正的用法

本质上引用都是为了减少拷贝,提高效率。而左值引用解决了大部分的场景,但是左值引用在传值返回的时候比较吃力,由右值引用来间接解决。

左值引用在引用传参可以减少拷贝构造,但是返回值的时候避免不了要调用拷贝构造。

传参用左值拷贝和右值拷贝都一样,但是返回值如果用右值引用效率会高,并且通常左值引用面临着对象出了作用域销毁的问题。所以这就是右值引用的一个比较厉害的用法。

返回对象若出了作用域不存在,则用左值引用返回和右值引用返回都是错误的。

std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝,所以可以提高利用效率,改善性能。所以当作函数返回值的时候如果对象不存在左值引用和右值引用都会报错!

场景:返回的对象在局部域中栈上存在,返回该对象必须用传值返回,并且有返回对象接受,这个时候编译器优化,将两次拷贝构造优化成一次拷贝构造。

测试用的string类

#include<assert.h>
namespace ljh
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动构造
		
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动语义" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			++_size;
			_str[_size] = '\0';
		}
		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

临时变量如果是4/8字节,通常在寄存器当中,但是如果是较大的内存,会在调用方的函数栈帧中开辟一块空间用于接受,这就是临时对象。

临时对象存在的必要性
当我们不需要接受返回值,而是对返回的对象进行直接使用,这个时候被调用的函数中的对象出了函数栈帧就销毁了,所以在栈帧销毁前会将对象拷贝到调用方栈帧的一块空间当中,我们可以用函数名对这个临时对象直接进行操作的(通常不能修改这个内存空间,临时变量具有常性)。

分析下面几组图片代码的效率

不可避免的,下面的这个过程必然要调用两次拷贝构造,编译器对于连续拷贝构造优化成不生成临时对象,由func::ss直接拷贝给main的str,我们如果只有前面所学的左值引用,func中的string ss在出了func后销毁,这个时候引用的空间被销毁会出现问题,这个时候显得特别无力。
在连续的构造+拷贝构造会被编译器进行优化,这个优化要看平台,但大部分平台都会做这个处理。

结果:

即使下面这种情况,在main接受没有引用的情况下,依旧会调用一次拷贝构造,跟上面并没有起到一个优化的作用。

结果:

解决方案:添加移动构造,添加一个右值引用版本的构造函数,构造函数内部讲s对象(将亡值)的内容直接跟要构造的对象交换,效率很高!!

string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动语义" << endl;swap(s);}

有了移动构造,对于上面的案例就变成了一次拷贝构造加一次移动构造。**编译器优化后将ss判断为将亡值,直接移动构造str对象,不产生临时对象了,就只是一次移动构造,效率升高!!**同理移动赋值!

结果:

下面情况是引用+移动构造,但是编译器优化就会判断ss出了作用域还存在,反而会拿ss拷贝构造str,这个时候起不到优化的作用!

结果:

以上采用将对象开辟在堆上或静态区都能够采用引用返回解决问题,但是有一个坏处?
引入多线程的概念,每个线程执行的函数当中若有大量的堆上的数据或者静态区的数据,相当于临界资源变多,要注意访问临界资源要加锁。而每个栈区私有栈,会相对好些

右值:
1、内置类型表达式的右值,纯右值。
2、自定义类型表达式的右值,将亡值。

将亡值:

string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动语义" << endl;
			swap(s);
		}
int main()
{
	ljh::string& str = func2();
	vector<ljh::string> v;
	v.push_back("1234656");//传进去的就是右值,是用"1234656"构造一个string对象传入,就是典型的将亡值
}

移动构造:
将亡值在出了生命周期就要销毁了,构造的时候可以将资源转移过要构造的对象,让将亡的对象指向NULL,相当于替它掌管资源。移动构造不能延续对象的生命周期,而是转移资源。且移动构造编译器不优化本质是一次拷贝构造+一次移动构造(从将亡值(此时返回值还是一个左值)给到临时变量),再有临时变量给到返回值接受对象(移动构造);
编译器优化做第一次优化,会将将亡值当作右值,此时要进行两次移动构造,编译器第二次优化,直接进行一次移动构造,去掉生成临时对象的环节。
只有需要深拷贝的场景,移动构造才有意义,跟拷贝构造一样,浅拷贝意义不大。

move的真正意义:
表示别人可以将这个资源进行转移走。

int main()
{
//为了防止这种情况,也要添加移动赋值。
	ljh::string str1;
	str1 = "123456";
}

c++11的算法swap的效率跟容器提供的swap效率一样了。

vector提供的插入的右值引用版本,就是优化了传右值情况,如果C++98则需要拷贝放入,而有右值就可以直接移动构造。两个接口的效率差不多。
大多数容器的插入接口都做了右值引用版本!!

完美转发

模板函数或者模板类用的&&即万能引用。
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。而forward才能将这种右值特性保持下去。

但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,

此时右值在万能引用成为左值,可能会造成本身右值可以移动构造,却变成左值只能拷贝构造了。
Fun(std::forward<T>(t));才能够保证转发的时候值的特性

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
void Func(int x) {
 // ......
}
template<typename T>
void PerfectForward(T&& t) {
 Fun(t);
}
int main()
{
 PerfectForward(10);           // 右值
 int a;
 PerfectForward(a);            // 左值
 PerfectForward(std::move(a)); // 右值
 const int b = 8;
 PerfectForward(b);      // const 左值
 PerfectForward(std::move(b)); // const 右值
 return 0; }

默认成员函数

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

现在有8个:构造函数,析构函数,拷贝构造,拷贝赋值,取地址,const取地址移动构造,移动赋值。

移动构造函数的默认生成的要求比较严格:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载都没有实现。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型
成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如
实现了移动构造就调用移动构造没有实现就调用拷贝构造
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
同理移动赋值。

即对于深拷贝的类,最好所有的构造,析构,拷贝,赋值重载,移动拷贝,移动赋值都写上。

总结

左值引用通常在传参和传返回值的过程中减少拷贝,这是利用左值引用的语法特性。一般做不到的部分,通常选择传参的时候传引用也可以解决,不通过返回值接受。
右值引用,一般是在深拷贝的类,实现移动构造和移动赋值,能够解决左值引用无法做到的传返回值的效率问题。

到此这篇关于C++11语法之右值引用的文章就介绍到这了,更多相关C++11右值引用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++11语法之右值引用的示例讲解

本文链接: https://lsjlt.com/news/145020.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++11语法之右值引用的示例讲解
    目录一、{}的扩展initializer_list的讲解:二、C++11一些小的更新decltypenullptr范围for新容器三、右值引用右值真正的用法完美转发默认成员函数总结一...
    99+
    2024-04-02
  • C++11语法之右值引用的方法
    这篇文章主要讲解了“C++11语法之右值引用的方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++11语法之右值引用的方法”吧!一、{}的扩展在原先c++的基础上,C++11扩展了很多初...
    99+
    2023-06-29
  • C++11右值引用和移动语义的实例解析
    目录基本概念左值 vs 右值左值引用 vs 右值引用右值引用使用场景和意义左值引用的使用场景左值引用的短板右值引用和移动语义右值引用引用左值右值引用的其他使用场景完美转发万能引用完美...
    99+
    2024-04-02
  • C++11学习之右值引用和移动语义详解
    目录左值引用与右值引用1、左值与右值2、纯右值、将亡值3、左值引用与右值引用4、右值引用和 std::move 使用场景引用限定符const 和引用限定符移动语义—std...
    99+
    2023-02-23
    C++11右值引用 移动语义 C++11右值引用 C++11 移动语义
  • C++11万能引用和右值引用的方法
    这篇文章主要介绍了C++11万能引用和右值引用的方法的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++11万能引用和右值引用的方法文章都会有所收获,下面我们一起来看看吧。正文实际上,type&&...
    99+
    2023-06-29
  • C++11右值引用方法是什么
    本篇内容介绍了“C++11右值引用方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!左值和右值在C++表达式的特性中有一个左值和右值的...
    99+
    2023-06-19
  • C++右值引用的示例分析
    这篇文章主要介绍了C++右值引用的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。概述在C++中,常量、变量或表达式一定是左值(lvalue)或右值(rvalue)。左...
    99+
    2023-06-15
  • 如何进行C++ 11右值引用的理解
    本篇文章给大家分享的是有关如何进行C++ 11右值引用的理解,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。C++ 11中引入的一个非常重要的概念就是右值引用。理解右值引用是学习...
    99+
    2023-06-17
  • C++11右值引用和移动语义的方法是什么
    本文小编为大家详细介绍“C++11右值引用和移动语义的方法是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“C++11右值引用和移动语义的方法是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。左值引用与右值...
    99+
    2023-07-05
  • C++11新特性之右值引用与完美转发详解
    目录一、左值与右值二、左值引用与右值引用三、右值引用应用1.移动构造与移动赋值1.模拟实现的string2.移动构造3.移动赋值四、默认移动构造和移动赋值重载函数五、完美转发1.万能...
    99+
    2024-04-02
  • C语言示例讲解ifelse语句的用法
    目录1、前言2、if语句的语法结构3、关于if else语句的示例4、if else 书写形式的对比5、例子1、前言 (1)C语言是结构化的程序设计语言。C语言的三种基本程序结构分别...
    99+
    2024-04-02
  • C++ STL 中的数值算法示例讲解
    目录1.iota2.accumulate3.partial_sum4.adjacent_difference5.inner_product以下算法均包含在头文件 numeric 中 ...
    99+
    2024-04-02
  • C语言示例讲解for循环的用法
    目录1、循环语句for的语法2、for循环中的break以及continue3、for语句的循环变量控制的一些建议4、for循环的变种5、题目1、循环语句for的语法 for (表达...
    99+
    2024-04-02
  • C++左值与右值,右值引用,移动语义与完美转发详解
    目录C++——左值与右值、右值引用、移动语义与完美转发一、左值和右值的定义二、如何判断一个表达式是左值还是右值(大多数场景)三、C++右值引用四、std::m...
    99+
    2024-04-02
  • C语言示例讲解while循环语句的用法
    目录1、while语句结构2、代码示例在学习和回顾该知识前,已经掌握了if语句的结构和用法。 if (条件)    语句; 当条件满足的情况下,if结构...
    99+
    2024-04-02
  • C语言示例讲解switch分支语句的用法
    目录1、了解switch分支语句2、示例3、default子句4、练习1、了解switch分支语句 switch语句也是一种分支语句,常常用于多分支的情况。 比如: 输入1,就会输出...
    99+
    2024-04-02
  • C语言示例讲解do while循环语句的用法
    目录1、do while()循环-先执行后判断2、do while中的break以及continue3、练习4、猜数字游戏1、do while()循环-先执行后判断 do语句的语法 ...
    99+
    2024-04-02
  • C++中右值引用与移动语义的方法是什么
    今天小编给大家分享一下C++中右值引用与移动语义的方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。意义充分利用临时对...
    99+
    2023-07-05
  • C++实例讲解引用的使用
    目录1.什么是引用2.引用的用法2.1 普通引用2.2 const 引用2.3 作用在函数参数2.4 作用在函数返回值3.引用的本质1.什么是引用 引用可以看作是一个已经定义的变量的...
    99+
    2024-04-02
  • C++11中的引用限定符示例代码
    目录1. C++11:引用限定符2. const和引用限定符C++中有左值和右值的概念。其实,左值和右值的区分也同样适用于类对象,本文中将左值的类对象称为左值对象,将右值的类对象称为...
    99+
    2023-01-03
    C++11 引用限定符 C++ 引用限定符 C++11 限定符
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作