> 文章列表 > c++值类别(左值与右值)

c++值类别(左值与右值)

c++值类别(左值与右值)

数据在计算机里按址存取,我们只有知道一个数据的准确地址才能访问到实际数据。

左值:根据字面意思,可以出现在赋值运算符=左边的叫做左值,严格来讲左值指的是有固定地址的值,其值可确定某个对象或函数的标识。

右值:和左值相对,右值指的是出现在=右侧的值,而现在右值指的是不能用取址符取址的值。

int a = 10;
int b = a + 10;  

      按照我们上面说的,出现在=左边的值为左值,第一行中a为左值,第二行a出现在=右侧,那么a便是右值吗?

       任意一个值要么是左值要么是右值,左值可以拥有左值属性和右值属性,换言之,但凡能使用右值的地方都能使用左值,反之则不行。

&a;         //正确,a是一个左值,有固定地址。&10;        //错误,10不是左值,所以没法取址。

      

引用:引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。

左值引用:左值引用就是传统意义上的引用,分为普通引用和const引用。

void fun1(string s)
{cout << s << endl;
}void fun2(string const& s)
{cout << s << endl;
}int main()
{string str = "hello world";fun1(str);fun2(str);return 0;
}

fun2相对于fun1少了一步拷贝。

右值引用:右值引用使用&&声明,右值引用也是引用,所以右值虽然绑定在了右值但它仍然是左值。

例1:

int main()
{int&& a = 11;a = 12;cout << a;return 0;
}

可以对右值引用的值进行修改。

思考:为什么能修改呢?

右值引用也是引用!!!

所以右值虽然绑定在了右值但它仍然是左值!!!

例2:

void test(int&& i)
{cout << i << endl;
}int main()
{int a = 10;int&& b = 10;test(a);        //错误,无法将右值引用绑定到左值test(b);        //错误,无法将右值引用绑定到左值???test(10);       //正确return 0;
}

形参永远是左值,虽然b的类型是右值引用,但是b本身是左值,b出现在了=左侧且可以使用取址符&取址。

例3:

struct testMove
{testMove(){m_value = nullptr;size = 0;cout << "testMove" << endl;}void resize(size_t s){m_value = new int[10000];size = 10000;}~testMove(){delete[]m_value;cout << "~testMove" << endl;}testMove(const testMove& other){m_value = new int[other.size];memcpy(m_value, other.m_value, other.size);cout << "copy" << endl;}int *m_value;int size;
};testMove makeTest()
{testMove st;st.resize(10000);return st;
}int main()
{testMove st = makeTest();return 0;
}

有了右值引用之后以后我们对testMove进行扩展。

testMove(testMove&& other)
{m_value = other.m_value;size = other.size;other.m_value = nullptr;other.size = 0;cout << "move" << endl;
}

通过右值引用可以实现移动构造。

万能引用:

template<typename T>
void test(T &&t)
{cout << t<<endl;
}int main()
{int a = 10;int& b = a;test(a);test(b);test(10);return 0;
}

T实际类型

最终类型

T

R&&

T&

R&

T&&

R&&

std::move:移动函数

template <class _Ty>_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movablereturn static_cast<remove_reference_t<_Ty>&&>(_Arg);
}……#if _HAS_NODISCARD
#define _NODISCARD [[nodiscard]] //属性,该属性表示返回值不应该被舍弃,当被舍弃时编译器会进行警告。……template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;……template <class _Ty>
struct remove_reference {using type                 = _Ty;using _Const_thru_ref_type = const _Ty;
};

完美转发std::forward

考虑一个问题:对于同一个功能,有时候我们需要对左值和右值调用不用的方法。

例:

void test(int&& i)
{cout << "&&" << endl;
}void test(int& i)
{cout << "&" << endl;
}template<typename T>
void run_test(T &&t)
{test(std::forward<T>(t);
}int main()
{int a = 10;run_test(a);run_test(10);return 1;
}//针对左值
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvaluereturn static_cast<_Ty&&>(_Arg);
}//针对右值
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvaluestatic_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");return static_cast<_Ty&&>(_Arg);
}