C++拷贝构造函数与赋值运算符重载

news2025/1/16 0:16:08

顾得泉:个人主页

个人专栏:《Linux操作系统》 《C++从入门到精通》  《LeedCode刷题》

键盘敲烂,年薪百万!


一、拷贝构造函数

1.概念

       在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

       那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

       拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

2.特性

拷贝构造函数也是特殊的成员函数,其特征如下:

     1.拷贝构造函数是构造函数的一个重载形式。

     2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

class Date
{
public:
     Date(int year = 1900, int month = 1, int day = 1)
     {
         _year = year;
         _month = month;
         _day = day;
     }
     // Date(const Date& d)   // 正确写法
     Date(const Date d)   // 错误写法:编译报错,会引发无穷递归
     {
         _year = d._year;
         _month = d._month;
         _day = d._day;
     }
private:
     int _year;
     int _month;
     int _day;
};

int main()
{
     Date d1;
     Date d2(d1);
     return 0;
}

     3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Time
{
public:
     Time()
     {
         _hour = 1;
         _minute = 1;
         _second = 1;
     }
     Time(const Time& t)
     {
         _hour = t._hour;
         _minute = t._minute;
         _second = t._second;
         cout << "Time::Time(const Time&)" << endl;
     }
private:
     int _hour;
     int _minute;
     int _second;
};
class Date
{
private:
     // 基本类型(内置类型)
     int _year = 1970;
     int _month = 1;
     int _day = 1;
     // 自定义类型
     Time _t;
};
int main()
{
     Date d1;    
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
     Date d2(d1);
     return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

     4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

typedef int DataType;
class Stack
{
public:
     Stack(size_t capacity = 10)
     {
         _array = (DataType*)malloc(capacity * sizeof(DataType));
         if (nullptr == _array)
         {
             perror("malloc申请空间失败");
             return;
         }
         _size = 0;
         _capacity = capacity;
     }
     void Push(const DataType& data)
     {
         // CheckCapacity();
         _array[_size] = data;
         _size++;
     }
     ~Stack()
     {
         if (_array)
         {
             free(_array);
             _array = nullptr;
             _capacity = 0;
             _size = 0;
         }
     }
private:
     DataType *_array;
     size_t _size;
     size_t _capacity;
};
int main()
{
     Stack s1;
     s1.Push(1);
     s1.Push(2);
     s1.Push(3);
     s1.Push(4);
     Stack s2(s1);
     return 0;
}

       运行结果会显示代码崩溃,是因为它拷贝构造的时候,析构了两次,导致第二次析构时,空间没有所产生了崩溃。

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

     5.拷贝构造函数典型调用场景:

         使用已存在对象创建新对象
         函数参数类型为类类型对象
         函数返回值类型为类类型对象

class Date
{
public:
     Date(int year, int minute, int day)
     {
         cout << "Date(int,int,int):" << this << endl;
     }
     Date(const Date& d)
     {
         cout << "Date(const Date& d):" << this << endl;
     }
     ~Date()
     {
         cout << "~Date():" << this << endl;
     }
private:
     int _year;
     int _month;
     int _day;
};
Date Test(Date d)
{
     Date temp(d);
     return temp;
}
int main()
{
     Date d1(2022,1,13);
     Test(d1);
     return 0;
}

注意:为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。


二、赋值运算符重载

1.运算符重载

       C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

       函数名字为:关键字operator后面接需要重载的运算符符号。

       函数原型:返回值类型 operator操作符(参数列表)

    1.不能通过连接其他符号来创建新的操作符:比如operator@ 重载操作符必须有一个类类型参数

    2.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义        

    3.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this.

    4.  .*  :: sizeof    ?:    .   注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

// 全局的operator==
class Date
{ 
public:
   Date(int year = 1900, int month = 1, int day = 1)
   {
        _year = year;
        _month = month;
        _day = day;
   }    
//private:
     int _year;
     int _month;
     int _day;
};

这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。 

bool operator==(const Date& d1, const Date& d2)
{
    return d1._year == d2._year
    && d1._month == d2._month
    && d1._day == d2._day;
}
void Test ()
{
    Date d1(2018, 9, 26);
    Date d2(2018, 9, 27);
    cout<<(d1 == d2)<<endl;
}
class Date
{ 
public:
   Date(int year = 1900, int month = 1, int day = 1)
   {
        _year = year;
        _month = month;
        _day = day;
   }
    
   // bool operator==(Date* this, const Date& d2)
   // 这里需要注意的是,左操作数是this,指向调用函数的对象
   bool operator==(const Date& d2)
   {
        return _year == d2._year;
            && _month == d2._month
            && _day == d2._day;
   }
private:
     int _year;
     int _month;
     int _day;
};

2.赋值运算符重载

1.赋值运算符格式

       参数类型:const T&,传递引用可以提高传参效率

       返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值

       返回*this :要复合连续赋值的含义

class Date
{ 
public :
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
 
    Date (const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
 
    Date& operator=(const Date& d)
   {
        if(this != &d)
        {
             _year = d._year;
             _month = d._month;
             _day = d._day;
        }          
        return *this;
 }
private:
     int _year ;
     int _month ;
     int _day ;
};

2.只能重载成类的成员函数

class Date
{
public:
     Date(int year = 1900, int month = 1, int day = 1)
     {
         _year = year;
         _month = month;
         _day = day;
     }
     int _year;
     int _month;
     int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
     if (&left != &right)
     {
         left._year = right._year;
         left._month = right._month;
         left._day = right._day;
     }
     return left;
}

       编译失败:error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

3.编译器会生成一个默认重载

       当用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:
     Time()
     {
         _hour = 1;
         _minute = 1;
         _second = 1;
     }
     Time& operator=(const Time& t)
     {
         if (this != &t)
         {
             _hour = t._hour;
             _minute = t._minute;
             _second = t._second;
         }
         return *this;
     }
private:
     int _hour;
     int _minute;
     int _second;
};
class Date
{
private:
     // 基本类型(内置类型)
     int _year = 1970;
     int _month = 1;
     int _day = 1;
     // 自定义类型
     Time _t;
};
int main()
{
     Date d1;
     Date d2;
     d1 = d2;
     return 0;
}

       既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
     Stack(size_t capacity = 10)
     {
         _array = (DataType*)malloc(capacity * sizeof(DataType));
         if (nullptr == _array)
         {
             perror("malloc申请空间失败");
             return;
         }
         _size = 0;
         _capacity = capacity;
     }
     void Push(const DataType& data)
     {
         // CheckCapacity();
         _array[_size] = data;
         _size++;
     }
     ~Stack()
     {
         if (_array)
         {
             free(_array);
             _array = nullptr;
             _capacity = 0;
             _size = 0;
         }
     }
private:
     DataType *_array;
     size_t _size;
     size_t _capacity;
};
int main()
{
     Stack s1;
     s1.Push(1);
     s1.Push(2);
 s1.Push(3);
         s1.Push(4);
     Stack s2;
     s2 = s1;
     return 0;
}

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

3.前置++和后置++重载

class Date
{
public:
     Date(int year = 1900, int month = 1, int day = 1)
     {
         _year = year;
         _month = month;
         _day = day;
     }
     // 前置++:返回+1之后的结果
     // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
     Date& operator++()
     {
         _day += 1;
         return *this;
     }
     // 后置++:
     // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
     // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
     // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
     //       而temp是临时对象,因此只能以值的方式返回,不能返回引用
     Date operator++(int)
     {
         Date temp(*this);
         _day += 1;
         return temp;
     }
private:
     int _year;
     int _month;
     int _day;
};
int main()
{
     Date d;
     Date d1(2022, 1, 13);
     d = d1++;    // d: 2022,1,13   d1:2022,1,14
     d = ++d1;    // d: 2022,1,15   d1:2022,1,15
     return 0;
}

结语:关于C++类和对象之拷贝构造函数和赋值运算符重载分享到这里就结束了,希望本篇文章的分享会对大家的学习带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言,最后祝大家新的一年里学业有成,天天开心~~~ 

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

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

相关文章

day1:组件的代码实现

思维导图 设计一个登录界面 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {this->setWindowFlag(Qt::FramelessWindowHint);this->resize(700,550);this->setWindowIcon(QIcon("D:/学校工作/截图/b1.jpg"));/***…

经验分享——Jmeter压力测试工具安装,使用

之前测试都是简单的写个线程模拟请求测试&#xff0c;后面经同事推荐这款压力测试工具。真的很好用&#xff0c;使用过几次了&#xff0c;于是把自己使用过程中遇到的问题&#xff0c;以及相关配置分享出来。希望给需要用到的人一些帮助。 一 下载 首选下载这款工具&#xff…

【蜂窝物联】公寓WiFi全覆盖解决方案

项目背景 随着移动设备越来越普及&#xff0c;人们对于网络的需求愈发强烈&#xff0c;WIFI覆盖也逐渐进入网民的视野中。部署无线网络不仅能提高出租屋服务水平及竞争力&#xff0c;同时也可以为出租屋提高收入&#xff0c;蜂窝为出租屋房东提供一套完整的解决方案。 01 需求…

SICTF Round#3 wp web

web hacker sql无列名注入&#xff1b; 提示查询username参数&#xff0c;flag在flag表中&#xff1b; 传参测试发现&#xff0c;union select 可用&#xff0c;空格被过滤可以使用/**/代替 &#xff0c;or也被过滤了且无法大小写、双写等绕过&#xff0c;导致无法查询flag表…

【开源】SpringBoot框架开发高校宿舍调配管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统展示四、核心代码4.1 查询单条个人习惯4.2 查询我的室友4.3 查询宿舍4.4 查询指定性别全部宿舍4.5 初次分配宿舍 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的…

java8的 lambda表达式到stream API总结备忘

文章目录 1. Lambda 表达式为什么使用 Lambda 表达式从匿名类到 Lambda 的转换Lambda 表达式语法语法格式一&#xff1a;无参&#xff0c;无返回值&#xff0c;Lambda 体只需一条语句语法格式二&#xff1a;Lambda 需要一个参数语法格式三&#xff1a;Lambda 只需要一个参数时&…

如何在iStoreOS软路由系统中安装cpolar实现公网远程本地电脑桌面

文章目录 简介一、配置远程桌面公网地址二、家中使用永久固定地址 访问公司电脑**具体操作方法是&#xff1a;** 简介 软路由是PC的硬件加上路由系统来实现路由器的功能&#xff0c;也可以说是使用软件达成路由功能的路由器。 使用软路由控制局域网内计算机的好处&#xff1a…

微信商户平台:如何查看超级管理员

如果有普通员工身份&#xff0c;可以登录 https://pay.weixin.qq.com/index.php/core/account/info 扫码进入后&#xff0c;进入菜单&#xff1a;账户中心》商户信息&#xff0c; 即可看到超级管理员的信息。

感觉我国的程序员前景一片灰暗,是这样吗?

程序员也分为好几等&#xff0c;在现在看来大部分的Android、Java、前端等等开发。已经看不到希望了&#xff0c;很多人都在边缘挣扎&#xff1b;刚看到一位Android开发者&#xff0c;过完年回公司就通知被裁&#xff1b;可见每年都会有很多互联网公司倒闭&#xff0c;或者裁员…

海鹰数据:Shopee卖家的利器,助力选品决策

在如今激烈竞争的电商市场中&#xff0c;Shopee作为一家备受欢迎的在线购物平台&#xff0c;吸引了大量卖家加入平台进行销售。然而&#xff0c;要在这个竞争激烈的市场中脱颖而出&#xff0c;并非易事。因此&#xff0c;对于Shopee卖家来说&#xff0c;如何科学合理地进行选品…

Milvus数据库介绍

参考&#xff1a;https://www.xjx100.cn/news/1726910.html?actiononClick Milvus 基于FAISS、Annoy、HNSW 等向量搜索库构建&#xff0c;核心是解决稠密向量相似度检索的问题。在向量检索库的基础上&#xff0c;Milvus 支持数据分区分片、数据持久化、增量数据摄取、标量向量…

C++ 区间合并 算法(详解) + 例题

1、定义 把所有&#xff0c;有交集的区间合并 图解&#xff1a; 2、实现 步骤如下&#xff1a; 1、首先按照每个区间左端点排序 2、扫描 所有区间&#xff0c;进行区间合并 上述第二条&#xff0c;可以理解为&#xff1a;拿出一个区间去跟它后面的所有的区间去进行合并&…

面试经典150题——矩阵置零

​"Dream it. Wish it. Do it." - Unknown 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力求解 思路一很简单&#xff0c;就是尝试遍历矩阵的所有元素&#xff0c;如果发现值等于0&#xff0c;就把当前行与当前列的值分别置为0。同时我们需要注意&#xff0c;…

如何使用安卓平板远程Ubuntu服务器通过VS Code远程开发

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;…

IDEA连接database数据库

文章目录 一、连接数据库1、连接mysql2、连接参数配置3、配置驱动从maven仓库下载&#xff1a;要求联网将提前下载好的jar放到本地目录 4、完成 二、执行sql1、选择要操作的数据库2、执行sql 三、问题1、可能因为时区问题连接不上 一、连接数据库 1、连接mysql 2、连接参数配置…

初始回溯算法

回溯算法一般用于对数据枚举后选取符合条件的结果并最终返回结果集的问题&#xff0c;之所以叫回溯法&#xff0c;是因为它可进可退 要想理解回溯的本质&#xff0c;还是要通过具体的题目去学习。 路径问题 https://www.nowcoder.com/practice/b736e784e3e34731af99065031301b…

【RL】Value Function Approximation(值函数逼近)

Lecture 8: Value Function Approximation Algorithm for state value estimation Objective function 令 v π ( s ) v_{\pi}(s) vπ​(s)和 v ^ ( s , w ) \hat{v}(s, w) v^(s,w)是真实state value和近似函数。 算法的目标是找到一个最优的 w w w&#xff0c;使得 v ^ …

基于微信小程序的比赛赛程管理系统设计与实现

在全面健身的倡导下通过各级赛事的举办完成体育人才的选拔&#xff0c;当由于缺乏信息化的管理手段而只能通过人工完成比赛报名、赛程制定及成绩记录等流程的管理&#xff0c;因此常常因意外而导致比赛赛程管理不善、成绩不理想等问题出现。为了帮助比赛组织者优化赛程管理流程…

0220作业

C语言实现LED1闪烁 led.h #ifndef __LED_H__ #define __LED_H__//RCC寄存器封装 #define RCC_MP_AHB4_ENSETR (*(volatile unsigned int*)0x50000A28) //寄存器封装//GPIO寄存器封装 typedef struct{volatile unsigned int MODER; //00volatile unsigned int OTYPER; //04vol…

环信IM Android端实现华为推送详细步骤

首先我们要参照华为的官网去完成 &#xff0c;以下两个配置都是华为文档为我们提供的 1.https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/android-config-agc-0000001050170137#section19884105518498 2.https://developer.huawei.com/consumer/cn/doc/HMSCore…