左值和右值,右值引用、重载 std-move,引用折叠 - C++知识总结一

左值和右值

总结

在C++11中:

  • 可以取地址的,有名字的,非临时的就是左值;

  • 不能取地址的,没有名字的,临时的就是右值;

  • 左值一定在内存中,右值有可能在内存或者寄存器中。

  • 为什么++i可以作为左值,而i++不可以?
    因为++i返回的是i,而i++返回的是一个临时变量。
    https://www.cnblogs.com/nanqiang/p/9979059.html

细节

摘抄下面的文章
https://blog.csdn.net/qianyayun19921028/article/details/80875002

引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
左值一定在内存中,右值有可能在内存中也有可能在寄存器中

int a=5;
int b=a;//此时a在内存中

int a=5;
int b=a+1;//此时a+1在寄存器中

int *p=&a;//此时&a在寄存器中

引用:就是取别名 ,引用不可以重定义

void main()
{
    int num1(5);
    int num2(10);
    int *pnum(&num1);//将num1的地址传递给pnum
    int * &rnum = pnum;//rnum是pnum的别名
    rnum = &num2;//rnumhe pnum指向同一片内存 改变了rnum就相当于改变了pnum
    cout << *pnum << endl;
    
    system("pause");
}


void main()
{
    int num1(5);
    int num2(10);
    int * &rnum = &num1;//这是不允许的 无法从“int *”转换为“int *&”
    system("pause");
}

从以上两个例子可以看出int *pnum(&num1); int * &rnum = pnum;通过一个指针在进行取别名是可以的,因为此时指针在内存中,而直接int * &rnum = &num1;取别名是不行的,&num1在寄存器中。在内存中的值是可以直接取别名的也就是引用。但是在寄存器中的值在不可以直接被引用的。其实这就是所谓的左值引用和右值引用。

在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。

在内存中的变量才是可以取地址的,而在寄存器中的变量是不可以取地址的。对于一个不能取地址的表达式或者值是无法直接引用的。所以int * &rnum = &num1;编译不通过。

讲了以上那么多,左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型。右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。

int &a = 2;       # 左值引用绑定到右值,编译失败
 
int b = 2;        # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2;  # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2;  # 常量左值引用绑定到右值,编程通过

右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:

int a;
int &&r1 = c;             # 编译失败
int &&r2 = std::move(a);  # 编译通过

右值引用的方法就是int * &&rnum = &num1; 。

下面来说一下为什么要右值引用,右值引用在你需要使用寄存器中的值的时候可以进行右值引用。寄存器的刷新速度很快,没有右值引用的话就需要将寄存器中的值拷贝到内存中,在进行使用,这是很浪费时间的。

int getdata(int &&num)
{
cout << num;
num += 10;
return num;
}

void main()
{
int a = 5;
cout << getdata(a + 1) << endl;
}

如上int getdata(int &&num)就是对右值进行引用。getdata(a + 1) 中a+1是右值在寄存器中,我们是不可以直接对他进行操作的,如果要操作得将其拷贝到内存中,如果是一个非常大的数据这种拷贝就会很占用内存,如果直接用右值引用就可以直接对其进行操作。从而节约内存。

将右值转化为左值 直接新建变量然后赋值就可以了

int b=a+1 //将a+1这个右值转变为左值了
move(a) //将a这个左值转变为了右值

std::forward和引用折叠

有时候右值会转为左值,左值会转为右值。

  • std::move(),将左值转换为右值
  • std::forward,完美转发,保持原来的左值/右值属性

引用折叠:
https://blog.csdn.net/zhangxiao93/article/details/74974546

  • 1.所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
  • 2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)

std::move 的实现

参考: https://blog.csdn.net/craftsman1970/article/details/82152704

通过static_cast<T&&>实现。源码:

template<typename T>
typename remove_reference<T>::type&& move(T&& t){
    return static_cast<typename remove_reference<T>::type&&>(t);
}

右值引用参数重载

参考https://zhuanlan.zhihu.com/p/97128024

重载右值和非右值,在运行时可以区分开。当拷贝构造函数或者赋值运算符传入的对象为右值时,直接将右值中指针指向的内存区域拿来用,不需要进行内存拷贝和右值内存释放,提高效率。


class Stack {
public:
    Stack(int size=1000):msize(size),mtop(0) {
        std::cout <<this<< ": Stack(int) construct" << std::endl;
        mpstack = new int[size];
    }
    ~Stack() {
        std::cout << this <<": ~Stack()" << std::endl;
        delete[]mpstack;
        mpstack = nullptr;
    }

    // 拷贝构造
    Stack(const Stack &src):msize(src.msize),mtop(src.mtop) {
        cout <<this<< ": Stack(const Stack &src)" << endl;
        mpstack = new int[src.msize];
        for (int i = 0; i < mtop; ++i) {
            mpstack[i] = src.mpstack[i];
        }
    }

    //带右值引用参数的拷贝构造函数
    Stack(Stack &&src):msize(src.msize),mtop(src.mtop) {
        cout << this << ": Stack(Stack&&)" << endl;
        mpstack = src.mpstack;
        src.mpstack = nullptr;
    }

    // 带右值引用参数的赋值运算符重载函数
    Stack& operator=(Stack &&src)
    {
        cout << this << ": operator=(Stack&&)" << endl;

        if (this == &src)
            return *this;

        delete[]mpstack;

        msize = src.msize;
        mtop = src.mtop;

        /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
        mpstack = src.mpstack;
        src.mpstack = nullptr;

        return *this;
    }

    // 赋值重载
    Stack& operator=(const Stack &src) {
        cout << this << " operator=" << endl;
        if (this == &src)
            return *this;
        delete[]mpstack;

        msize = src.msize;
        mtop = src.mtop;
        mpstack = new int[src.msize];
        for (int i = 0; i < mtop; ++i) {
            mpstack[i] = src.mpstack[i];
        }
        return *this;
    }

    int getSize() {
        return msize;
    }
private:
    int *mpstack;
    int mtop;
    int msize;
};

Stack CreateSameSizeStack(Stack &stack)
{
    cout << "create tmp" << endl;
    Stack tmp(stack.getSize());
    cout << "create tmp end" << endl;
    return tmp; //temp对象作为返回值返回时,经历一次copy到main内存中,
}


int main() {
    Stack aa(1000);
    aa = CreateSameSizeStack(aa);//函数返回值作为一个右值临时变量存在,即等号的右边部分。然后等号操作符又执行了一次copy到aa。
    cout << "next bb" << endl;
    Stack bb(aa);
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,963评论 6 542
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,348评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,083评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,706评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,442评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,802评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,795评论 3 446
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,983评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,542评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,287评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,486评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,030评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,710评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,116评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,412评论 1 294
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,224评论 3 398
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,462评论 2 378