【C++】左值、右值、移动拷贝构造函数、移动赋值函数

news/2024/7/5 11:37:54

左值和右值的定义以及区别:

先来看在C语言中的左值和右值 :

        左值:可赋值的值为左值

        右值:不可赋值的值为右值

再来看C++中左值右值的定义如下:

        左值:可以取地址的值(有名字的值、非临时量)
        右值:不可取地址的值(没有名字的值、临时量)
       

左值引用:
int a = 10;   //整型a = 10
int& b = a;   //可以编译通过  左值引用就相当于变量的别名   const int a =  10;  //左值引用字面常量的时候  改为常引用const就可以引用了//左值引用要求右值必须能够取地址,如果不能取地址,则必须为常引用右值引用:
int&& a = 10;   //字面常量10为纯右值,可以编译通过
int&& b = a;    //不可以编译通过  因为a是字面常量10的右值引用   a以及有名字了

说完了上面的定义来看下面这些代码:

//我们下面的String是一个了类
String fun()
{String s2("hello");return s2;
}String & fun()
{String s2("hello");return s2;
}String && fun()
{String s2("hello");return s2;
}
int main()
{    String s1;s1 = fun();
}

上面分别是用String,String &,String &&作为返回,通过下面的主函数去调用fun函数的时候哪些可以通过编译呢?

 移动拷贝构造函数、移动赋值函数

#include<iostream>
#include<cstring>
using namespace std;class String
{char* str;public:String(const char* p = NULL) :str(NULL)  //构造函数{cout << "construct :" << this << endl;if (p != NULL){str = new char[strlen(p) + 1];strcpy(str, p);}else{str = new char[1];*str = '\0';}}~String()   //析构函数{cout << "destroy : " << this << endl;if (str != NULL){delete[] str;}str = NULL;}String(const String& s) :str(NULL)  //拷贝构造函数{cout << "copy construct : " << this << endl;str = new char[strlen(s.str) + 1];strcpy(str, s.str);}char* Relese(char* p){char* old = str;str = p;return old;}String(String&& s){cout << " move copy construcgt :" << this << endl;str = s.str;s.str = NULL;}String& operator=(String&& s){if (this != &s){s.str = Relese(s.str);}cout << this << " move operator=: " << &s << endl;return *this;}
};String fun()
{String s2("hello");return s2;
}int main()
{String s1;s1 = fun();return 0;
}

先来看上述代码,创建s1对象,s1对象堆区申请空间,进入fun函数中,创建s2对象,s2堆区申请空间,return时将s2通过拷贝构造函数创建临时对象,临时对象申请空间,s2释放堆区空间,临时空间释放堆区空间,到main程序结束时,释放s1对象空间,下面来看上述代码执行结果:

 上述代码表明了刚刚所推断的都是正确的,但是简单的几行代码对堆区进行多次操作,而我们想要达到的目的只是将对象s2中的字符串转移到对象s1中,于是乎移动拷贝构造函数、移动赋值函数就有了,顾名思义,它的作用是移动对象中的资源,并不是移动对象

结果如下:

 

上述调用移动拷贝构造和移动赋值具体如下:

1.s1对象创建,堆区申请空间给个'\0' 

2.调用fun函数

3.调用拷贝构造函数创建s2对象,堆区申请空间

4.return时调用移动拷贝构造(这里为什么会调用移动拷贝构造呢,本来临时对象通过拷贝构造函数传参s2来构建对象,但是现在有移动拷贝构造函数(参数为右值引用),因为我们前面说过右值引用可以是临时量,所以这块就是临时对象调用移动拷贝构造将临时对象指针指向hello堆区,s2对象指针置为空,临时对象调用完移动拷贝构造之后,s2对象指针已经为NULL,s2对象被析构不会释放掉堆区内存)。----》对了,return s2底层调用了rerun std::move(s2),将s2强转为右值对象!

5.这一步相当于右值引用传参来重载等号运算符,也是一样的,因为临时对象给s1赋值,所以临时对象右值引用来进行传参,进入到移动赋值函数时,将s1对象指针指向hello堆区,并将临时对象指针置为NULL。

tips:这块为什么临时对象就会调用移动拷贝构造函数、移动赋值函数?因为临时对象符合右值引用的条件,如果你没有写出移动拷贝构造函数、移动赋值函数的话,就会调用普通的哪些函数!

还有一点上述那个代码中的s1对象指向堆区的'\0'会造成内存泄漏,所以得写一个Relese函数释放s1堆区空间!

移动拷贝构造函数、移动赋值函数就是为了减少对堆区的操作次数,仅仅移动对象的资源,并不是移动对象!

如果本篇文章对你有帮助的话,可以点个赞哦,如有错误,敬请指出,共同学习,共同进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pgtn.cn/news/15593.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

【C++】typedfe和宏定义的区别

先来说结论&#xff1a; 1.宏定义作用于编译之前(也就是预编译阶段&#xff0c;而typedef作用于编译阶段 2.typedef仅限于类型的重定义&#xff0c;而宏定义不仅可以为类型起别名还可以为数值定义起别名(简单的替换) 下面来看一下区别 下面这是typedef的用法 typedef int* PI…

GPS部标平台的架构设计(十)-基于Asp.NET MVC构建GPS部标平台

在当前很多的GPS平台当中&#xff0c;有很多是基于asp.NETsiverlight开发的遗留项目&#xff0c;代码混乱而又难以维护&#xff0c;各种耦合和关联&#xff0c;要命的是界面也没见到比Javascript做的控件有多好看&#xff0c;随着需求的增多&#xff0c;平台已经臃肿不堪。 设计…

【C++】虚函数

虚函数是构成C多态的重要一步&#xff0c;今天来说一下虚函数&#xff01; 虚函数&#xff1a; 在基类(或父类)中&#xff0c;使用virtual关键字对函数进行声明为并在一个或多个派生类(子类)中被重新定义的成员函数&#xff0c;通过指向派生类的基类指针或引用&#xff0c;访…

看了极光推送技术原理的几点思考

看了极光推送技术原理的几点思考 分类&#xff1a; android2012-11-26 20:50 16586人阅读 评论(18) 收藏 举报目录(?)[] 移动互联网应用现状 因为手机平台本身、电量、网络流量的限制&#xff0c;移动互联网应用在设计上跟传统 PC 上的应用很大不一样&#xff0c;需要根据手机…

【C++】多态(早期绑定、后期绑定)、抽象类(纯虚函数)、虚析构函数

我们都知道面向对象编程的三大特征是封装、继承、多态&#xff0c;今天我们就来说一下其中之一的多态。 概念&#xff1a; 多态&#xff1a; 多态字面意思就是多种形态&#xff0c;C 多态意味着调用成员函数时&#xff0c;会根据调用函数的对象的类型来执行不同的函数。(取自…

【C++】多线程thread

进程和线程这部分呢我之前在我Linux中写过这些东西&#xff0c;和C中线程的概念差不多&#xff0c;大家可以去看一下&#xff1a; Linux多线程_神厨小福贵&#xff01;的博客-CSDN博客进程和线程的区别有哪些呢&#xff1f;进程是资源分配的最小单位&#xff0c;线程是CPU调度…

【C++】多线程互斥锁、条件变量

我们了解互斥量和条件变量之前&#xff0c;我们先来看一下为什么要有互斥量和条件变量这两个东西&#xff0c;了解为什么有这两东西之后&#xff0c;理解起来后面的东西就简单很多了&#xff01;&#xff01;&#xff01; 先来看下面这段简单的代码&#xff1a; int g_num 0;…

【C++】二叉树的先序、中序、后序遍历序列

二叉树常用到的遍历有这三种 先序遍历&#xff1a;先遍历根节点&#xff0c;然后再分别遍历左节点和右节点。(根左右) 中序遍历&#xff1a;先遍历左节点&#xff0c;然后再遍历根节点&#xff0c;最后遍历右节点。(左根右) 后序遍历&#xff1a;先遍历左节点&#xff0c;然…