网站建设 费用预算,高港区住房和城乡建设局网站,公司网站制作企业,直播网站开发核心技术✅1主页#xff1a;#xff1a;我的代码爱吃辣 #x1f4c3;2知识讲解#xff1a;C11——右值引用 ☂️3开发环境#xff1a;Visual Studio 2022 #x1f4ac;4前言#xff1a;右值引用#xff0c;是C11更新的一个非常有价值的语法1主页我的代码爱吃辣 2知识讲解C11——右值引用 ☂️3开发环境Visual Studio 2022 4前言右值引用是C11更新的一个非常有价值的语法可以说是C为了追求极致的性能而出现的以前我们非常惧怕自定义类型的传值返回有了右值引用就还很多了。 目录
一.左值引用和右值引用
1.左值引用和右值引用
2.什么是右值什么是右值引用
二. 左值引用与右值引用比较 三.右值引用使用场景和意义
1.移动构造
2.移动赋值
3.STL中的容器都是增加了移动构造和移动赋值
编辑 四. 右值引用引用左值及其一些更深入的使用场景分析
五. 完美转发
1.模板中的 万能引用
2.std::forward 完美转发在传参的过程中保留对象原生类型属性
3.完美转发实际中的使用场景 一.左值引用和右值引用
传统的C语法中就有引用的语法而C11中新增了的右值引用语法特性所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用都是给对象取别名。
1.左值引用和右值引用 左值是一个表示数据的表达式(如变量名或解引用的指针)我们可以获取它的地址可以对它赋 值左值可以出现赋值符号的左边右值不能出现在赋值符号左边。定义时const修饰符后的左 值不能给他赋值但是可以取它的地址。左值引用就是给左值的引用给左值取别名。
int main()
{// 以下的p、b、c、*p都是左值int* p new int(0);int b 1;const int c 2;// 以下几个是对上面左值的左值引用int* rp p;int rb b;const int rc c;int pvalue *p;return 0;
}
2.什么是右值什么是右值引用 右值也是一个表示数据的表达式如字面常量、表达式返回值函数返回值(这个不能是左值引 用返回)等等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能 取地址。右值引用就是对右值的引用给右值取别名。
int fmin(int a, int b)
{return a;
}
int main()
{double x 1.1, y 2.2;// 以下几个都是常见的右值10;x y;fmin(x, y);// 以下几个都是对右值的右值引用int rr1 10;double rr2 x y;double rr3 fmin(x, y);// 这里编译会报错error C2106: “”: 左操作数必须为左值10 1;x y 1;fmin(x, y) 1;return 0;
} 注意右值是不能取地址的但是给右值取别名后会导致右值被存储到特定位置且可以取到该位置的地址也就是说例如不能取字面量10的地址但是rr1引用后可以对rr1取地 址也可以修改rr1。如果不想rr1被修改可以用const int rr1 去引用是不是感觉很神奇 这个了解一下实际中右值引用的使用场景并不在于此这个特性也不重要。 二. 左值引用与右值引用比较
左值引用总结 左值引用只能引用左值不能引用右值。 但是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;
}
右值引用总结
右值引用只能右值不能引用左值。但是右值引用可以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;
} 三.右值引用使用场景和意义
前面我们可以看到左值引用既可以引用左值和又可以引用右值那为什么C11还要提出右值引用呢是不是化蛇添足呢下面我们来看看左值引用的短板右值引用是如何补齐这个短板的
1.移动构造
#includecassert
namespace ikun
{class string{public: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(){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;}// stringstring operator(char ch){ikun::string str(this-c_str());str.push_back(ch);return str;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}int main()
{ikun::string str(1111);ikun::string str1 str A;return 0;
}
这种情况下避免避免不了的深拷贝。 如果我们有移动构造
首先我们先思考一个下面这个问题 答:次数的str是一个局部变量除了作用域就会销毁如果我们得到他的引用再次访问时就是非法的访问了。所以str只能作为传值返回但是传值返回有一个返回执行之后函数调用结束函数栈帧销毁伴随的局部变量也就销毁了编译器在处理时会使用一个临时变量来存储返回值。如果外面有接收再拷贝给外面这就是我们看到的图上画的两层拷贝构造。 右值也可以分为两种
纯右值字面量等。将亡值比如这里的返回的局部变量 str。
移动构造顾名思义可以将将亡值的资源移动到新的对象里从而节省拷贝得代价。 我们通过查看底层字符串的地址来进一步观察资源移动
int main()
{ikun::string str(123);printf(str:%p\n, str.c_str());//将str强制变成右值ikun::string str1(move(str));printf(str:%p\n, str1.c_str());return 0;
} 注意这里我们把s1 move处理以后, 会被当成右值调用移动构造但是这里要注意一般是不要这样用的因为我们会发现s1的资源被转移给了s3s1被置空了。
// 移动构造
string(string s):_str(nullptr), _size(0), _capacity(0)
{cout string(string s) -- 移动语义—移动构造 endl;swap(s);
} 2.移动赋值
#includecassert
namespace ikun
{class string{public: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(){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;}// stringstring operator(char ch){ikun::string str(this-c_str());str.push_back(ch);return str;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}int main()
{ikun::string str(1111);str ikun::string(123);return 0;
}移动复制和移动构造基本一致
// 移动赋值
string operator(string s)
{cout string operator(string s) -- 移动语义—移动赋值 endl;swap(s);return *this;
} 3.STL中的容器都是增加了移动构造和移动赋值 四. 右值引用引用左值及其一些更深入的使用场景分析
按照语法右值引用只能引用右值但右值引用一定不能引用左值吗因为有些场景下可能 真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时可以通过move 函数将左值转化为右值。C11中std::move()函数位于utility 头文件中该函数名字具有迷惑性它并不搬移任何东西唯一的功能就是将一个左值强制转化为右值引用然后实现移动语义。
STL容器插入接口函数也增加了右值引用版本 #includelist
int main()
{listikun::string lt;ikun::string s1(1111);// 这里调用的是拷贝构造lt.push_back(s1);// 下面调用都是移动构造lt.push_back(2222);lt.push_back(std::move(s1));return 0;
} 五. 完美转发
1.模板中的 万能引用
模板中的不代表右值引用而是万能引用其既能接收左值又能接收右值模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力但是引用类型的唯一作用就是限制了接收的类型后续使用中都退化成了左值我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发。
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; }templatetypename 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;
} 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力但是引用类型的唯一作用就是限制了接收的类型后续使用中都退化成了左值。所以这里都是左值在调用函数Fun 2.std::forward 完美转发在传参的过程中保留对象原生类型属性
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; }templatetypename T
void PerfectForward(T t)
{// std::forwardT(t)在传参的过程中保持了t的原生类型属性。Fun(std::forwardT(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;} 3.完美转发实际中的使用场景
templateclass T
struct ListNode
{ListNode* _next nullptr;ListNode* _prev nullptr;T _data;
};
templateclass T
class List
{typedef ListNodeT Node;
public:List(){_head new Node;_head-_next _head;_head-_prev _head;}void PushBack(T x){//Insert(_head, x);Insert(_head, std::forwardT(x));// 关键位置}void PushFront(T x){//Insert(_head-_next, x);Insert(_head-_next, std::forwardT(x));// 关键位置}void Insert(Node* pos, T x){Node* prev pos-_prev;Node* newnode new Node;newnode-_data std::forwardT(x); // 关键位置// prev newnode posprev-_next newnode;newnode-_prev prev;newnode-_next pos;pos-_prev newnode;}void Insert(Node* pos, const T x){Node* prev pos-_prev;Node* newnode new Node;newnode-_data x; // 关键位置prev-_next newnode;newnode-_prev prev;newnode-_next pos;pos-_prev newnode;}
private:Node* _head;
};