目录
6。左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
我们先看看左值和右值的定义和如何使用吧!!!
//左值和右值
//都是给对象起别名
//右值也是有地址的,只是在语法层取不了
下面演示左值和左值引用:
int main()
{
// 以下的p、b、c、*p都是左值
// 左值:一般情况下可以取地址+可以对其赋值+左值可以出现在赋值符号的左边
//const修饰的左值不可以修改所以不能对其赋值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s1 = { "ddddd" };
//s1[0];
// 左值引用给左值取别名
int& h = b;
string& s2 = s1;
int*& p1 = p;
return 0;
}
下面演示右值和右值引用:
int main()
{
double x = 1.1;
double y = 2.2;
//常见的右值可以是字面常量(比如10这种),函数的返回值(不能是左值引用返回),表达式返回值(临时变量,匿名对象)
//x + y
//10
//fmin(x,y)
//string("11111")---表达式的返回值
//右值只能出现在表达式的右边,且不可以取地址的,也不可以修改的
//右值引用
//double& p = x + y;//不能直接左值引用的
//string& s1 = string("wefewfwfe");//同上
//右值引用给右值取别名, 下面演示正确的右值引用方法
int&& p = 10;
double&& h = x + y;
string&& s1 = string("hdehveb");
string&& s1 = { "cdfw" };//隐式类型转换会形成临时对象,得用右值
//鉴于右值这种特性又可以分为,纯右值---内置类型右值和将亡值---类类型右值
return 0;
}
int main()
{
int x = 10;
int& ret1 = x;
int&& ret2 = x + 10;
//进入汇编层了之和我们会发现无论是左值还是右值底层都差不多
//这边底层和语法层是分裂的,在底层中右值也是有地址的,只是语法层取不了而已
//左值和右值完全由编译器决定的,没有哪个值是天生的左值或者右值
//左值引用引用给右值取别名:不能直接引用,但是const 左值引用可以
//原本不能的原因是权限被放大了,加上const加以缩小
const int& ret3 = x + 10;
//右值引用引用给左值取别名:不能直接引用,但是move(左值)以后右值引用可以引用
//所以move的作用就是将原本是左值的值变成右值
int&& p = move(x);
string s1 = { "cdcc" };
string&& s2 = move(s1);
vector<string> s4;
s4.push_back(s1);
s4.push_back(string());
s4.push_back(string("dcdfwefe"));
//以上都能成功插入s4,说明C++11中的容器的push_back能同时插入左值和右值了
return 0;
}
// 底层汇编等实现和上层语法表达的意义,有时是背离的,所以不要结合到一起去理解,互相佐证
7。移动构造和移动赋值
前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引 用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!
#include<iostream>
#include<assert.h>//要加.h
using namespace std;
// 引用的意义:减少拷贝
// 左值引用解决的场景:引用传参/引用传返回值
// 左值引用没有彻底解决的场景:传返回值
namespace bit
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
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);
}
// 拷贝构造
// s2(s1)
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 移动构造
// 临时创建的对象,不能取地址,用完就要消亡
// 深拷贝的类,移动构造才有意义
string(string&& s)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);//反正右值也就是将亡值了,出了作用域也就消亡了
//直接取代它的空间就行了,不需要额外创造空间再拷贝过去
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
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];
if (_str)
{
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 = nullptr;
size_t _size = 0;
size_t _capacity = 0; // 不包含最后做标识的\0
};
bit::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
bit::string str;
cout << &str << endl;//打印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;
}
}
#include"List.h"
int main()
{
//C++11之后对于容器新加入了移动构造和移动赋值
bit::string s1 = bit::to_string(1234);//连拷贝都没有了,s1相当于str的引用,原理上相当于只构造了s1
cout << &s1 << endl;//分别打印s1和str的地址会发现两个地址都是一样的
return 0;
}
以上代码为一个自定义的string的自我实现,代码很容易理解,加入了移动拷贝和移动赋值,其实C++11之后很多容器的很多函数都加入了移动拷贝和移动赋值。
不对呀,为什么没有拷贝呢, 这个赋值bit::string s1 = bit::to_string(1234);按照C++原本的语法来说应该是先在函数体里面构造str,然后传值返回会生成临时对象会生成一层构造,然后这个临时对象再赋值给s1理论上又会生成一次拷贝,所以至少也有两次构造,为什么这里只有一次构造和没有拷贝呢?对,我这边推理的完全没有问题,非常的C++,但是你没有发现这样很麻烦吗。所以编译器一般这样优化:
不生成str在返回时不临时对象了,直接调用移动拷贝,拷贝赋值给s1,这样就只有一下构造+一次拷贝了。但是也不对呀,这里是完全没有拷贝的呀。
这里就不得不提一下VS2022的神级优化!!!
VS2022的神级优化
VS2022的优化是这么说的:如果函数内部构造一个对象,这个对象作为返回值,并且上一个函数栈中直接进行接收了,就直接构造上一个函数栈中的对象。也就是说s1作为最初的栈帧,接受到了来自上一层栈帧的str的返回值,就直接那str构造s1,就相当于str是s1的别名,两个地址一样也说明了这一点。
简单来说就是这个神级优化让str直接构造s1,相当于连原本的移动拷贝都没有了。但是有的同学通过调试发现不对呀,当跳到bit::string str这一行之后就跳到构造函数了,所以我感觉是直接构造str,s1类似像str的左值引用一样,这里是你理解错了,要完全理解为什么是构造s1需要进一步查看底层(编码),这个是编译器把实际的代码做了修改,虽然看着是构造str,实际上构造的是s1。
但是这个优化对比正常的编译器至少将移动拷贝优化掉了,但是我们知道移动构造是针对右值的,也就是将亡值的,本身消耗就很小了,所以这个神级优化感觉没有什么实际意义。
上面可以看到很多容器都加入了移动构造和移动拷贝和移动赋值,像日期类这种类是没有移动拷贝和移动构造的必要的,因为里面的数据走深拷贝的代价不大,所以这更体现了这个神级优化的作用其实不大。
其实这个神级优化不是每种情景都起作用的:比如下面这种:
int main()
{
//C++11之后对于容器新加入了移动构造和移动赋值
bit::string s1;
s1 = bit::to_string(1234);//只能先构造s1,然后,构造str,然后
//由于str是右值是将亡值所以直接调用了移动构造,构造了s1
return 0;
}
List下的移动构造拷贝和深拷贝(实战)
#include"List.h"
int main()
{
bit::list<bit::string> lt;
bit::string s1("111111111111111111111");
lt.push_back(s1);//左值
lt.push_back(bit::string("22222222222222222222222222222"));//右值
lt.push_back("3333333333333333333333333333");//右值
lt.push_back(move(s1));//右值
bit::string&& r1 = bit::string("22222222222222222222222222222");
//r1(右值引用本身)的属性是左值,引用了右值的值是左值
return 0;
}
List.h部分在下面会提供出来,我们先看一下由于左值调用的肯定是深拷贝,后面push_back的三个肯定是右值调用的是移动构造,没有问题,最上面两行由于要先构造哨兵位结点再拷贝,所以前面两行可以不用看,最后这个是神级优化。
然后如果要调用移动拷贝就需要在原本的基础上重载一个能传入右值的push_back
由于push_back调用的是insert,所以:
接着由于insert里面创造了一个新的结点会调用构造函数,所以构造函数也需要一个右值版本:
最后由于在List的构造函数里面_data会调用对应类型的构造函数,string会调用string的,为了最后变成移动构造,所以也需要将传进去的值变成右值。
传入上一个函数,完成浅拷贝,我们会发现这一路上只有所有函数都接收的是右值才会最后变成移动拷贝呀,又由于引用右值的值如果不做特殊处理是会被编译器默认为左值的(这个很容易证明,其实是由于引用右值的那个值是可以修改的,但是右值是不可以修改的,所以是左值),所以移动拷贝的那条路径上的所有函数的接收的左值都要强制性转换成右值,我们先都使用move一一进行修改,后面为了方便可以使用完美转发——万能引用,否则只有一个地方没有修改(传的是左值),就会走到深拷贝那里!!!
证明:左右值是由编译器决定的
我们可以使用以下程序证明:
void func(const bit::string& s)
{
cout << "void func(bit::string& s)" << endl;
}
void func(bit::string&& s)
{
cout << "void func(bit::string&& s)" << endl;
}
int main()
{
bit::string s1("1111111");
func(s1);//左值
func((bit::string&&)s1);//强制性转换成右值
func(bit::string("1111111"));//右值
func((bit::string&)bit::string("1111111"));//强制性转换成左值
return 0;
}
我们可以看到左右值是可以通过编译器互相强制性类型转换的,所有可以推得他们就是同一种类型的东西,没有哪个值天生就是左值或者右值。
附加:自己实现的List.h
//#include<iostream>
#include<assert.h>
//using namespace std;
namespace bit
{
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
, _prev(nullptr)
, _data(data)
{}
ListNode(T&& data)
:_next(nullptr)
, _prev(nullptr)
, _data(move(data))
{}
};
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
// ++it;
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
//template<class T>
//class ListConstIterator
//{
// typedef ListNode<T> Node;
// typedef ListConstIterator<T> Self;
// Node* _node;
//public:
// ListConstIterator(Node* node)
// :_node(node)
// {}
// // ++it;
// Self& operator++()
// {
// _node = _node->_next;
// return *this;
// }
// Self& operator--()
// {
// _node = _node->_prev;
// return *this;
// }
// Self operator++(int)
// {
// Self tmp(*this);
// _node = _node->_next;
// return tmp;
// }
// Self& operator--(int)
// {
// Self tmp(*this);
// _node = _node->_prev;
// return tmp;
// }
// //*it
// const T& operator*()
// {
// return _node->_data;
// }
// const T* operator->()
// {
// return &_node->_data;
// }
// bool operator!=(const Self& it)
// {
// return _node != it._node;
// }
// bool operator==(const Self& it)
// {
// return _node == it._node;
// }
//};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
// 不符合迭代器的行为,无法遍历
//typedef Node* iterator;
//typedef ListIterator<T> iterator;
//typedef ListConstIterator<T> const_iterator;
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
//iterator it(_head->_next);
//return it;
return iterator(_head->_next);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
list(initializer_list<T> il)
{
empty_init();
for (const auto& e : il)
{
push_back(e);
}
}
// lt2(lt1)
list(const list<T>& lt)
{
empty_init();
for (const auto& e : lt)
{
push_back(e);
}
}
// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(_head, lt._head);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
void push_back(const T& x)
{
insert(end(), x);
}
void push_back(T&& x)
{
insert(end(), move(x));
}
void pop_back()
{
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
// 没有iterator失效
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(move(x));
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
// erase 后 pos失效了,pos指向节点被释放了
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
private:
Node* _head;
};
void Func(const list<int>& lt)
{
// const iterator const 迭代器不能普通迭代器前面加const修饰
// const 迭代器目标本身可以修改,指向的内容不能修改 类似const T* p
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
// 指向的内容不能修改
//*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list1()
{
list<int> lt1;
// 按需实例化(不调用就不实例化这个成员函数)
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
Func(lt1);
//ListIterator<int> it = lt1.begin();
list<int>::iterator it = lt1.begin();
while (it != lt1.end())
{
*it += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
}
struct Pos
{
int _row;
int _col;
Pos(int row = 0, int col = 0)
:_row(row)
, _col(col)
{}
};
void test_list2()
{
list<Pos> lt1;
lt1.push_back(Pos(100, 100));
lt1.push_back(Pos(200, 200));
lt1.push_back(Pos(300, 300));
list<Pos>::iterator it = lt1.begin();
while (it != lt1.end())
{
//cout << (*it)._row << ":" << (*it)._col << endl;
// 为了可读性,省略了一个->
cout << it->_row << ":" << it->_col << endl;
//cout << it->->_row << ":" << it->->_col << endl;
cout << it.operator->()->_row << ":" << it.operator->()->_col << endl;
++it;
}
cout << endl;
}
void test_list4()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
Func(lt1);
lt1.push_front(10);
lt1.push_front(20);
lt1.push_front(30);
Func(lt1);
lt1.pop_front();
lt1.pop_front();
Func(lt1);
lt1.pop_back();
lt1.pop_back();
Func(lt1);
lt1.pop_back();
lt1.pop_back();
lt1.pop_back();
lt1.pop_back();
//lt1.pop_back();
Func(lt1);
}
void test_list5()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
Func(lt1);
list<int> lt2(lt1);
lt1.push_back(6);
Func(lt1);
Func(lt2);
list<int> lt3;
lt3.push_back(10);
lt3.push_back(20);
lt3.push_back(30);
lt1 = lt3;
Func(lt1);
Func(lt3);
}
void test_list6()
{
list<int> lt1 = { 1,2,3,4,5,6 };
Func(lt1);
}
}