网站建设培训学院淘宝seo关键词的获取方法有哪些
一、左值和右值
1.左值
左值是一个表示数据的表达式,比如:变量名、解引用的指针变量。
一般地,我们可以获取它的地址和对它赋值, 定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。
总体而言,可以取地址的对象就是左值。
int main(){// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;}
2.右值
右值也是一个表示数据的表达式,比如:字面常量、表达式返回值,传值返回函数的返回值(是传值返回,而非传引用返回),右值不能出现在赋值符号的左边且不能取地址。
右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。右值引用就是对右值的引用,给右值取别名
总体而言,不可以取地址的对象就是右值。
int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值10;x + y;fmin(x, y);
}
二、左值引用和右值引用
左值引用与右值引用是C++11中出现的新语法 , 无论左值引用还是右值引用,都是给对象取别名。
1.左值引用
左值引用就是对左值的引用,给左值取别名。
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&& rr = x+y;
。
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;
}
三、左值引用与右值引用对比
左值引用:
左值引用只能引用左值,不能引用右值
但const修饰的左值引用可以引用左值, 也可以引用右值.
这个在C++98中解释为, 右值是临时对象, 临时对象具有常性, 因此要引用要加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;
}
move(x):将x从左值强制转化为右值,通过返回值的方式输出
注意:右值引用变量是一个左值, 可以被取地址与修改, 但const修饰的右值引用变量不能被修改
四、使用场景及实际意义
与C++98不同的是C++11出现了右值引用, 要了解右值引用的引用就要先了解左值引用有什么意义.
左值引用的意义:
传值传参和传值返回都会产生拷贝,有的甚至是深拷贝,代价很大。而左值引用的实际意义在于做参数和做返回值都可以减少拷贝,从而提高效率。
int test1()
{int n = 0;n++;return n;
}
int& test2()
{static int n = 0;n++;return n;
}
int main()
{int ret1 = test1();int ret2 = test2();return 0;
}
在上面的代码中,出现了test1和test2两个函数, test是传值返回, 而test2是传引用返回, 在传引用返回中就出现了左值引用返回.
但也出现了许多问题:如
当对象出了函数作用域以后仍然存在时,可以使用左值引用返回,这是没问题的。
但当对象(对象是函数内的局部对象)出了函数作用域以后不存在时,就不可以使用左值引用返回了。此时只能使用传值返回.
>C++11就出现了右值引用
右值引用的意义
于是,为了解决上述传值返回的拷贝问题,C++11标准就增加了右值引用和移动语义。
1.移动语义
(1)移动构造
下面就是一个移动构造的例子:
string(string&& s):_str(nullptr), _size(0), _capacity(0)
{swap(s);
}
但要注意的是:
在进行交换的时候,如果交换的对象是一个move标记的对象,就可以使交换的对象改变.如:
string str;
string str1=str;
string str2=move(str);
对于str1对象调用拷贝构造函数, 对于str2调用移动构造函数, 此时str2将交换str的资源内容.
A
拷贝构造函数和移动构造函数都是构造函数的重载函数,所不同的是:
- 拷贝构造函数的参数是 const左值引用,接收左值或右值;
- 移动构造函数的参数是右值引用,接收右值或被 move 的左值。
在存在移动构造函数的时候, 如果传来的参数是一个右值, 就会自动匹配移动构造函数.
因此:
若是左值做参数,那么就会调用拷贝构造函数,做一次拷贝(如果是像 string 这样的在堆空间上存在资源的类,那么每调用一次拷贝构造就会做一次深拷贝)。
若是右值做参数,那么就会调用移动构造,而调用移动构造就会减少拷贝(如果是像 string 这样的在堆空间上存在资源的类,那么每调用一次移动构造就会少做一次深拷贝)。
当只有拷贝构造没有移动构造:
既有拷贝构造也有移动构造:
因为函数中的 str 是将亡值(右值) ,在构造的时候直接调用移动拷贝
但现在的编译器一般都会进行优化:
因为临时对象有 ret 来接收,先拷贝构造出临时对象再用它移动构造出 ret ,临时对象好像没必要产生一样,不如省略掉。既然 str 是 to_string 函数栈帧的局部对象,最后还是要销毁,不如将 str 视为右值,直接转移 str 的资源用来构造 ret .
只有拷贝构造没有移动构造:
既有拷贝构造也有移动构造:
此外,C++11标准的STL 容器的相关接口函数也增加了右值引用版本
3.完美转发
在此之前我们需要知道什么是万能引用:
确定类型的 && 表示右值引用(比如:int&& ,string&&),
但函数模板中的 && 不表示右值引用,而是万能引用,模板类型必须通过推断才能确定,其接收左值后会被推导为左值引用,接收右值后会被推导为右值引用。
注意区分右值引用和万能引用:下面的函数的 T&& 并不是万能引用,因为类的实例化的时候就确定了 T 的类型.
template<typename T>
class A
{void func(T&& t); // 模板实例化时T的类型已经确定,调用函数时T是一个确定类型,所以这里是右值引用
};
template<typename T>
void f(T&& t) // 万能引用
{//...
}int main()
{int a = 5; // 左值f(a); // 传参后万能引用被推导为左值引用const string s("hello"); // const左值f(s); // 传参后万能引用被推导为const左值引用f(to_string(1234)); // to_string函数会返回一个string临时对象,是右值,传参后万能引用被推导为右值引用const double d = 1.1;f(std::move(d)); // const左值被move后变成const右值,传参后万能引用被推导为const右值引用return 0;
}
在上文中提到, 右值引用的变量是左值,因此右值属性在函数传递中可能被改变
因而出现C++的完美转化
(2)概念
完美转发是指在函数模板中,完全依照模板的参数类型,将参数传递给当前函数模板中的另外一个函数。
因此,为了实现完美转发,除了使用万能引用之外,我们还要用到std::forward
(C++11),它在传参的过程中保留对象的原生类型属性。
void Func(int& x) { cout << "左值引用" << endl; }void Func(const int& x) { cout << "const左值引用" << endl; }void Func(int&& x) { cout << "右值引用" << endl; }void Func(const int&& x) { cout << "const右值引用" << endl; }template<typename T>
void PerfectForward(T&& t) // 万能引用
{Func(std::forward<T>(t)); // 根据参数t的类型去匹配合适的重载函数
}int main()
{int a = 4; // 左值PerfectForward(a);const int b = 8; // const左值PerfectForward(b);PerfectForward(10); // 10是右值const int c = 13;PerfectForward(std::move(c)); // const左值被move后变成const右值return 0;
}
实现完美转发需要用到万能引用和 std::forward 。
例子
#include<iostream>
using namespace std;template<class T>
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};template<class T>
class List
{typedef ListNode<T> Node;
public:List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void PushBack(const T& x) // 左值引用{Insert(_head, x);}void PushFront(const T& x) // 左值引用{Insert(_head->_next, x);}void PushBack(T&& x) // 右值引用{Insert(_head, forward<T>(x)); // 关键位置:保留对象的原生类型属性}void PushFront(T&& x) // 右值引用{Insert(_head->_next, forward<T>(x)); // 关键位置:保留对象的原生类型属性}template<class TPL> // 该函数模板实现了完美转发void Insert(Node* pos, TPL&& x) // 万能引用{Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = forward<TPL>(x); // 关键位置:保留对象的原生类型属性// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}private:Node* _head;
};
--------------------------------------------------------------------------------------------------------------------------------
本文的讲解到此结束,谢谢大家的观看,有问题欢迎给我留评论。