文章目录
1.右值引用和移动语义
1.1左值引用和右值引用
之前我们对左右值比较浅显的概念是:左值可以修改 右值不可以修改
接下来我们会对左右值有进一步的理解:左值可以取地址
- 无论左值引用还是右值引用,都是给对象取别名。
- 左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
int main()
{
//变量名
int b = 0;
int* pb = &b; //左值可以取地址
int& rb = b; //左值引用
//指针
int* p = new int(1); //*p也是一个左值
int*& rp = p;
int& pvalue = *p; //左值引用
//const后的左值
const int c = 2;
const int* pc = &c;
//c = 20; //不是所有的左值都可被修改 但是可以进行&操作
const int& rc = c; //左值引用
//左值在=左右都可以出现
int d = c;
return 0;
}
- 右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。
double getmin(double a, double b)
{
return a < b ? a : b;
}
double& Getmin(double a, double b)
{
return a < b ? a : b;
}
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10; //字面常量
x + y; //表达式返回值[此返回值有空间]
getmin(x, y); //函数返回值[非左值引用返回]
//右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = getmin(x, y);
//编译报错 -- 右值不可修改 右值不可放在=左边 -- 表达式必须是可修改的左值
10 = 1;
x + y = 1;
getmin(x, y) = 1;
Getmin(x, y) = 1; //左值引用返回不报错
//表达式必须为左值或函数指示符 -- 右值不能取地址
&10;
&(x + y);
&getmin(x, y);
return 0;
}
- 右值引用特殊场景[不重要 了解即可]
int main()
{
//不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1
double x = 1.1, y = 2.2;
int&& rr1 = 10;
const double&& rr2 = x + y;
rr1 = 20;
rr2 = 5.5; // 报错
return 0;
}
- 左值引用和右值引用
左值引用只能引用左值,不能引用右值 非得引用右值 可以使用const左值引用
右值引用只能引用右值,不能引用左值 非得引用左值 可以引用move后的左值
int main()
{
int a = 0;
int b = 1;
// 左值引用给左值取别名
int& ref1 = a;
// 左值引用给右值取别名需要加const 保持权限一致
const int& ref2 = (a + b);
// 右值引用给右值取别名
int&& ref3 = (a + b);
// 右值引用给move后左值取别名
int&& ref4 = move(a);
return 0;
}
1.2右值引用使用场景
区分传过来的参数是左值还是右值
1.string为例介绍
//void func(const int& a)
//{
// cout << "void func(int& a) or void func(int&& a)" << endl;
//}
void func(int& a)
{
cout << "void func(int& a)" << endl;
}
void func(int&& a)
{
cout << "void func(int&& a)" << endl;
}
int main()
{
int a = 0;
int b = 1;
func(a); //传左值
func(a + b); //传右值
return 0;
}
在string类里面的应用
int main()
{
string s1("hello");
string s2("world");
//左右值拷贝
string s3 = s1; //左值拷贝:不能修改s1的内容 s3和s1相互独立 这也是之前我们模拟实现为什么要搞深拷贝
string s4 = s1 + s2; //右值拷贝:一个临时对象 具有常性 s1 + s2这个表达式的返回值所形成的临时对象 和s1\s2没有关系
//我们可以进行资源转移 而不再进行深拷贝
return 0;
}
在模拟实现string类里进一步探索右值引用
普通拷贝参数const string& 既可引用左值又可引用右值
移动拷贝构造只可引用右值
右值传参调用移动拷贝构造而不调用普通拷贝构造:编译器调用最匹配的那个
代码示例
//模拟实现string类
namespace ape
{
class string
{
public:
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//构造函数
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//交换内容
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);
}
// 移动构造: 传过来的s[即 (s1 + '!')]是右值
//ape::string ret2 = (s1 + '!'); 右值拷贝 ret2.string( (s1 + '!') );
string(string&& s)
:_str(nullptr)
{
cout << " string(string&& s) -- 右值移动拷贝" << endl;
swap(s); //直接换过来
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
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)
{
push_back(ch);
return *this;
}
//+号运算符重载 不改变this 返回新string
string operator+(char ch)
{
cout << "operator+:";
string tmp(*this);
tmp += ch;
return tmp;
}
//c_str函数
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
//整形变字符串
ape::string int_to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
ape::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
//数字变字符
str += (x + '0' );
}
if (flag == false)
str += '-';
reverse(str.begin(), str.end());
return move(str);
}
库里的移动构造
s1是个左值 move函数的返回值被识别为右值 – 调用移动构造 s3的内容[啥也没有]给了s1 s1的内容给了s3
2.其他容器在C++11后的构造函数
2.容器的插入函数
2.1list
2.2map
3.左值引用/右值引用的总结
左值引用通过引用直接操作 省去中间额外的拷贝
右值引用通过识别是左值还是右值 如果是右值 直接移动拷贝
4.完美转发
4.1首先看右值引用的特例
首先我们需要思考 C++11为什么要大费周折搞出一个右值引用? 右值引用的意义何在? 右值引用的初衷是啥?
显而易见,通过上述的讨论 我们知道 数据分为左值和右值 当涉及到容器的深层拷贝时 左值需要做深层拷贝[不动原数据 将原数据拷贝给新对象] 但是右值是一个临时变量 具有常性 当作用域完结 他的值也就消失 即所谓的"将亡值" 为了最大化提升C++语法的优势 大佬搞出了一个叫右值引用的东西 其目的就在于当数据为右值时 直接把该数据的资源转移到新对象上 而不管你这个临时对象被转移后会发生什么 因为你是个临时变量 程序结束自动销毁 但是上述代码又显示出了一个新的问题 当右值引用 引用了所传的参数 该引用的属性变为了左值
为什么要改成左值呢?
因为右值引用的初衷就是当参数时右值 可以直接进行资源转移 要想转移 此引用的属性就需要时左值
由此引发了新的问题 如下代码的输出违背了代码的本意
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;
}
//万能引用/引用折叠:既可以引用左值 又可以引用右值
template<typename T>
//这里是一个右值引用 当这个函数被调用 t是参数的右值引用 但是此时 t的属性变为了左值
void PerfectForward(T&& t)
{
//Fun(forward<T>(t));
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(move(b)); // const 右值
return 0;
}
由于t的属性成为左值 所以不管你原来是左值还是右值 都会调用左值的代码
怎么办?怎么解决?好不容易搞出来的右值引用难不成要抛弃?解决办法见下:
此时 代码的本意达到 提示:这里的forward<T>(t)
作用在于将t的属性置为原有属性 当原有属性为左值 你还是左值 是右值 你还是右值 当需要把原有属性是右值但是在该函数需要当成左值来用时 不加forward<T>(t)
即可
4.2接着看完美转发的应用场景
1.详细图解
2.完整代码
List.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <functional>
#include <assert.h>
using namespace std;
namespace ape
{
//结点类---右值引用结点构造函数
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prv;
T _data;
list_node(const T& x = T())
:_next(nullptr)
, _prv(nullptr)
, _data(x)
{
}
list_node(T&& x = T())
:_next(nullptr)
, _prv(nullptr)
, _data(forward<T>(x))
{
}
};
//迭代器类
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> node;
typedef __list_iterator<T, Ref, Ptr> self;
node* _node;
__list_iterator(node* n)
:_node(n)
{
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prv;
return *this;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prv;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
//链表类
template<class T>
class list
{
typedef list_node<T> node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
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);
}
//无参构造
list()
{
_head = new node(T());
_head->_next = _head;
_head->_prv = _head;
}
//迭代器区间构造
template <class Iterator>
list(Iterator first, Iterator last)
{
_head = new node(T());
_head->_next = _head;
_head->_prv = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
//交换内容
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);
}
//拷贝构造
list(const list<T>& lt)
{
_head = new node(T());
_head->_next = _head;
_head->_prv = _head;
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
//赋值运算符重载
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
//析构函数
~list()
{
clear();
delete _head;
_head = nullptr;
}
//清空函数
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
//尾插 --- 右值引用尾插
void push_back(const T& x)
{
insert(end(), x);
}
void push_back(T&& x)
{
insert(end(), forward<T>(x));
}
//头插 -- 右值引用头插
void push_front(const T& x)
{
insert(begin(), x);
}
void push_front(T&& x)
{
insert(begin(), forward<T>(x));
}
//尾删
void pop_back()
{
erase(--end());
}
//头删
void pop_front()
{
erase(begin());
}
//插入函数 --- 右值引用插入
void insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prv;
node* new_node = new node(x);
prev->_next = new_node;
new_node->_prv = prev;
new_node->_next = cur;
cur->_prv = new_node;
}
void insert(iterator pos, T&& x)
{
node* cur = pos._node;
node* prev = cur->_prv;
node* new_node = new node(forward<T>(x));
prev->_next = new_node;
new_node->_prv = prev;
new_node->_next = cur;
cur->_prv = new_node;
}
//删除函数
iterator erase(iterator pos)
{
assert(pos != end());
node* prev = pos._node->_prv;
node* next = pos._node->_next;
prev->_next = next;
next->_prv = prev;
delete pos._node;
return iterator(next);
}
private:
node* _head;
};
}
String.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <functional>
#include <assert.h>
using namespace std;
namespace ape
{
class string
{
public:
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//构造函数
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//交换内容
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);
}
// 移动构造: 传过来的s[即 (s1 + '!')]是右值
//ape::string ret2 = (s1 + '!'); 右值拷贝 ret2.string( (s1 + '!') );
string(string&& s)
:_str(nullptr)
{
cout << " string(string&& s) -- 右值移动拷贝" << endl;
swap(s); //直接换过来
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
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)
{
push_back(ch);
return *this;
}
//+号运算符重载 不改变this 返回新string
string operator+(char ch)
{
cout << "operator+:";
string tmp(*this);
tmp += ch;
return tmp;
}
//c_str函数
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
#include <stack>
#include <string>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <functional>
#include <assert.h>
using namespace std;
#include "List.h"
#include "String.h"
int main()
{
ape::list<ape::string> lt;
ape::string s1("hello world");
lt.push_back(s1);
lt.push_back(ape::string("hello world"));
lt.push_back("hello world");
return 0;
}