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
void test(T &&t)
{
    cout << t<

T实际类型

最终类型

T

R&&

T&

R&

T&&

R&&

std::move:移动函数

template 

_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
    return static_cast&&>(_Arg);
}

……

#if _HAS_NODISCARD
#define _NODISCARD [[nodiscard]] //属性,该属性表示返回值不应该被舍弃,当被舍弃时编译器会进行警告。

……

template 
using remove_reference_t = typename remove_reference<_Ty>::type;

……

template 
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
void run_test(T &&t)
{
    test(std::forward(t);
}

int main()
{
    int a = 10;
    run_test(a);
    run_test(10);
    return 1;
}

//针对左值
template 
_NODISCARD constexpr _Ty&& forward(
    remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
    return static_cast<_Ty&&>(_Arg);
}

//针对右值
template 
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
    static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
    return static_cast<_Ty&&>(_Arg);
}

你可能感兴趣的:(C++,c++,C++20)