【C++进阶学习】第十一弹——C++11(上)——右值引用和移动语义

CSDN 2024-08-21 14:35:08 阅读 61

前言:

前面我们已经将C++的重点语法讲的大差不差了,但是在C++11版本之后,又出来了很多新的语法,其中有一些作用还是非常大的,今天我们就先来学习其中一个很重要的点——右值引用以及它所扩展的移动定义

目录

一、左值引用和右值引用

左值引用 

右值引用

二、左值引用与右值引用的比较

三、右值引用的使用

移动构造

移动赋值

四、总结


一、左值引用和右值引用

左值引用 

左值引用是最常见的引用类型,通常用于绑定到一个左值。左值是一个具有名称的对象,可以取地址,通常出现在赋值操作符的左边。(简单的说,能取地址的就是左值)

语法:

<code>类型 &引用名 = 左值;

示例:

int a = 10;

int &refA = a; // refA是一个左值引用,绑定到左值a

特点:

左值引用必须初始化,并且只能绑定到左值。左值引用可以修改绑定的对象。

右值引用

右值引用是C++11引入的新特性,用于绑定到一个右值。右值是一个临时对象,通常没有名称,不能取地址,通常出现在赋值操作符的右边。(右值不能取地址,比如常量)

语法:

类型 &&引用名 = 右值;

示例:

int &&refB = 20; // refB是一个右值引用,绑定到右值20

特点:

右值引用必须初始化,并且只能绑定到右值。右值引用主要用于实现移动语义和完美转发。

有一个需要强调的是,常变量虽然也属于常量,但是它可以取地址,所以它属于左值

二、左值引用与右值引用的比较

左值引用:

1. 左值引用只能引用左值,不能引用右值。

2. 但是const左值引用既可引用左值,也可引用右值

int main()

{

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

   int a = 10;

   int& ra1 = a;   // ra为a的别名

   //int& ra2 = 10;   // 编译失败,因为10是右值

   // const左值引用既可引用左值,也可引用右值。

   const int& ra3 = 10;

   const int& ra4 = a;

   return 0;

}

右值引用:

1. 右值引用只能右值,不能引用左值。

2. 但是右值引用可以move以后的左值。

int main()

{

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

int&& r1 = 10;

// error C2440: “初始化”: 无法从“int”转换为“int &&”

// message : 无法将左值绑定到右值引用

int a = 10;

int&& r2 = a;

// 右值引用可以引用move以后的左值

int&& r3 = std::move(a);

return 0;

}

三、右值引用的使用

在上面我们也已经讲到了,左值引用及可以引用左值,又可以引用右值,那么C++11为什么还要设计右值引用呢?下面我们来看一下原因。

我们借助string类来讲解

先来看一下下面所出现的所有代码,可以先思考看看思考思考

namespace zda

{

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(string&& s) //右值引用

:_str(nullptr)

, _size(0)

, _capacity(0)

{

cout << "string(string&& s) -- 移动语义" << endl;

swap(s);

}

// 移动赋值

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);

}

_str[_size] = ch;

++_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

};

string to_string(int value)

{

bool flag = true;

if (value < 0)

{

flag = false;

value = 0 - value;

}

string str;

while (value > 0)

{

int x = value % 10;

value /= 10;

str += ('0' + x);

}

if (flag == false)

{

str += '-';

}

std::reverse(str.begin(), str.end());

return str;

}

}

左值引用使用场景:

void func1(bit::string s)

{}

void func2(const bit::string& s)

{}

int main()

{

bit::string s1("hello world");

// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值

func1(s1);

func2(s1);

// string operator+=(char ch) 传值返回存在深拷贝

// string& operator+=(char ch) 传左值引用没有拷贝提高了效率

s1 += '!';

return 0;

}

左值引用短板:

当函数返回对象为临时变量的时候,左值引用就派不上用场了,就只能传值返回,就需要拷贝至少一次(老一点的编译器为两次)

右值引用和移动语义:

对于上面这种问题,我们就可以通过右值引用和移动语义来实现

移动构造

移动构造的本质就是将参数的右值窃取过来,占为己有,这样它就不用再深度拷贝了,所以叫做移动构造

<code>// 移动构造

string(string&& s)

:_str(nullptr)

,_size(0)

,_capacity(0)

{

cout << "string(string&& s) -- 移动语义" << endl;

swap(s);

}

int main()

{

zda::string ret2 = bit::to_string(-1234);

return 0;

}

当返回值是右值时,因为移动构造并没有开辟空间进行深拷贝,所以效率就会更高

需要注意的是,当拷贝构造和移动构造同时存在时,编译器默认的也会调用移动构造,因为编译器会默认调用效率更高的函数

移动赋值

// 移动赋值

string& operator=(string&& s)

{

cout << "string& operator=(string&& s) -- 移动语义" << endl;

swap(s);

return *this;

}

int main()

{

zda::string ret1;

ret1 = zda::to_string(1234);

return 0;

}

// 运行结果:

// string(string&& s) -- 移动语义

// string& operator=(string&& s) -- 移动语义

这里运行后发现,调用了一次移动构造和一次移动赋值,因为这里的ret1是一个已经存在的对象,用它来接受函数返回值的时候编译器就无法再优化了,所以会在移动构造后创建一个临时变量,且这个临时变量会被编译器识别为右值,从而调用移动赋值

四、总结

上面我们就简单的先提了一下右值引用的应用:移动语义,下一篇我们再重点讲解一下右值引用的另一个重点语法:完美挥发

感谢各位大佬观看,创作不易,还请各位大佬点赞支持一下!!!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。