拷贝构造、赋值运算符、运算符重载

news2024/10/5 17:23:58

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

拷贝构造函数

赋值运算符重载

运算符重载

日期类相关的所有操作符的重载

重载赋值运算符

拷贝构造和赋值运算符的显著区别


拷贝构造函数

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

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

特征

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

2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引起无穷递归

3.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

class Date1
{
public:
    Date1(int year=0,int month=0,int day=0)
    {
        _year=year;
        _month=month;
        _day=day;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date1 d1(2023,7,23);
    //这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
    Date1 d2(d1);
    return 0;
}

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

class Stack
{
public:
    Stack(int n)
    {
        _a=(int*)malloc(sizeof(int)*n);
        _top=0;
    }
    void STPush(int x)
    {
        _a[_top]=x;
        _top++;
    }
    void STPop()
    {
        assert(!STEmpty());
        _top--;
    }
    bool STEmpty()
    {
        return _top;
    }
    int STsize()
    {
        return  _top;
    }
    void show()
    {
        int count=_top-1;
        while(count>=0)
        {
            cout<<_a[count]<<endl;
            count--;
        }
    }
    ~Stack()
    {
        free(_a);
    }
private:
    int* _a;
    int _top;
};
int main()
{
    Stack s1(10);
    Stack s2(s1);
    return 0;
}
结果:
7_21(2689,0x1000ebe00) malloc: *** error for object 0x100643370: pointer being freed was not allocated
7_21(2689,0x1000ebe00) malloc: *** set a breakpoint in malloc_error_break to debug

这里就会有问题了,因为我们没有显示定义拷贝构造函数,所以编译器使用的默认浅拷贝,对象中s1中的_a数组是把首地址拷贝给s2,析构的时候,先析构s2,在析构s1,问题出现在这,_a这一个空间,释放了两次,所以编译器就会报错。

我们这里就需要自己先定义一个,拷贝构造函数,实现深拷贝

//拷贝构造函数
//深拷贝
Stack(const Stack& st)
{
     _a=(int*)malloc(sizeof(int)*st._capacity);
     memcpy(_a, st._a, sizeof(int)*st._top);
     _top=st._top;
     _capacity=st._capacity;
}

赋值运算符重载

运算符重载

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

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

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

注意:

1.不能通过连接其他符号来创建新的操作符:比如operator@(必须是已经存在的操作符)

2.重载操作符必须有一个类类型或者枚举类型的操作数

3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义

4.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参

5. .* ,:: ,sizeof ,?: ,. 注意以上5个运算符不能重载

class Date1
{
public:
    Date1(int year=0,int month=0,int day=0)
    {
        _year=year;
        _month=month;
        _day=day;
    }
    //bool operator==(Date1* this,const Date1& di)
    //这里需要注意的是,左操作数是this指向的调用函数的对象
    bool operator==(const Date1& di)
    {
        return _year==di._year&&_month==di._month&&_day==di._day;
    }
private:
    int _year;
    int _month;
    int _day;
};
日期类相关的所有操作符的重载
class Date
{
public:
    Date(int year=0,int month=0,int day=0);
    Date(const Date& d);
    // <<
    friend ostream& operator<<(ostream& out,Date d);
    //赋值运算符的重载
    Date& operator=(const Date& d);
    int GetMonthDay(int year,int month);
    //日期+=天数
    Date& operator+=(int day);
    //日期+天数
    Date operator+(int day);
    // 日期-=天数
    Date& operator-=(int day);
    // 日期-天数
    Date operator-(int day);
    //前置++
    Date& operator++();
    //后置++
    Date operator++(int);
    //前置--
    Date& operator--();
    //后置--
    Date operator--(int);
    //==
    bool operator==(const Date& d);
    //!=
    bool operator!=(const Date& d);
    //>=
    bool operator>=(const Date& d);
    //>
    bool operator>(const Date& d);
    // <=
    bool operator<=(const Date& d);
    // <
    bool operator<(const Date& d);
    int GetYearDay(int year);
    //日期-日期 返回天数
    int operator-(const Date& d);
    void show();
    ~Date();
private:
    int _year;
    int _month;
    int _day;
};

ostream& operator<<(ostream& out,Date d)
{
    out<<d._year<<"/"<<d._month<<"/"<<d._day<<endl;
    return out;
}
Date::Date (int year,int month,int day)
{
    _year=year;
    _month=month;
    _day=day;
}
Date:: Date(const Date& d)
{
    _year=d._year;
    _month=d._month;
    _day=d._day;
}
Date& Date::operator=(const Date& d)
{
    if(this!=&d)
    {
        _day=d._day;
        _month=d._month;
        _year=d._year;
    }
    return *this;
}
int Date::GetMonthDay(int year,int month)
{
    int a[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
    if(month==2&&((year%4==0&&year%100!=0)||year%400==0))
        return a[month]+1;
    return a[month];
}
//日期+=天数
Date& Date::operator+=(int day)
{
    if(day<0)
        return *this-=(-day);
    _day+=day;
    while(_day>GetMonthDay(_year, _month))
    {
        _day-=GetMonthDay(_year, _month);
        _month++;
        if(_month>12)
        {
            _month=1;
            _year++;
        }
    }
    return *this;
}
//日期+天数
Date Date::operator+(int day)
{
    Date temp(*this);
    temp+=day;
    return temp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
    if(day<0)
        return *this+=(-day);
    while((_day-day)<0)
    {
        _month--;
        if(_month<=0)
        {
            _month=12;
            _year--;
        }
        day-=GetMonthDay(_year, _month);
    }
    _day-=day;
    return *this;
}
// 日期-天数
Date Date::operator-(int day)
{
    Date temp(*this);
    temp-=day;
    return temp;
}
//前置++
Date& Date::operator++()
{
    *this+=1;
    return *this;
}
//后置++
Date Date::operator++(int)
{
    Date temp(*this);
    temp+=1;
    return temp;
}
//前置--
Date& Date::operator--()
{
    *this-=1;
    return *this;
}
//后置--
Date Date::operator--(int)
{
    Date temp(*this);
    temp-=1;
    return temp;
}
//==
bool Date::operator==(const Date& d)
{
    if(_year!=d._year)
        return false;
    else
    {
        if(_month!=d._month)
            return false;
        else
        {
            if(_day!=d._day)
                return false;
            return true;
        }
    }
}
//!=
bool Date::operator!=(const Date& d)
{
    return !(*this==d);
}
//>=
bool Date::operator>=(const Date& d)
{
    return (*this>d)||(*this==d);
}
//>
bool Date::operator>(const Date& d)
{
    if(_year>d._year)
        return true;
    else
    {
        if(_year>d._year&&_month>d._month)
            return true;
        else
        {
            if(_year>d._year&&_month>d._month&&_day>d._day)
                return true;
            else
                return false;
        }
    }
}
// <=
bool Date::operator<=(const Date& d)
{
    return (*this<d)||(*this==d);
}
// <
bool Date::operator<(const Date& d)
{
    if(_year<d._year)
        return true;
    else
    {
        if(_year<d._year&&_month<d._month)
            return true;
        else
        {
            if(_year<d._year&&_month<d._month&&_day<d._day)
                return true;
            else
                return false;
        }
    }
}
int Date::GetYearDay(int year)
{
    if((year%4==0&&year%100!=0)||year%400==0)
        return 366;
    return 365;
}
//日期-日期 返回天数
//int Date::operator-(const Date& d)
//{
//    int day=0,month=0,year=0;
//    year=d._year;
//    month=d._month;
//    day=d._day;
//    while(_month>0)
//    {
//        _day+=GetMonthDay(_year, _month);
//        _month--;
//    }
//    while(month>0)
//    {
//        day+=GetMonthDay(year, month);
//        month--;
//    }
//    int CountY=_year-year;
//    if(CountY>0)
//    {
//        while(CountY--)
//        {
//            _day+=GetYearDay(year++);
//        }
//    }
//    return _day-day;
//}
int Date::operator-(const Date& d)
{
    Date min=d;
    Date max=*this;
    int flag=1;
    if(*this<d)
    {
        min=*this;
        max=d;
        flag=-1;
    }
    int n=0;
    while(min!=max)
    {
        ++min;
        n++;
    }
    return n*flag;
}
void Date::show()
{
    cout<<_year<<"/"<<_month<<"/"<<_day<<endl;
}
Date::~Date()
{
    cout<<"日期功能结束"<<endl;
}

重载赋值运算符

在定义的同时进行赋值叫做初始化,定义完成以后再赋值(不管在定义的时候有没有赋值)就叫做赋值。初始化只能有一次,赋值可以有多次。

当以拷贝的方式初始化一个对象时,会调用拷贝构造函数;当给一个对象赋值时,会调用重载过的赋值运算符。即使没有显式的重载赋值运算符,编译器也会以默认地方式重载它。默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,这和默认拷贝构造函数的类似。

编译器默认的赋值运算符

#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year=0,int month=0,int day=0):
    _year(year),
    _month(month),
    _day(day){}
    void show()
    {
        cout<<_year<<"/"<<_month<<"/"<<_day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2023,7,27);
    Date d2;
    d2=d1;//这里我们没有显示对赋值运算符重载,这里使用的编译器中默认的赋值运算符重载函数
    d1.show();
    d2.show();
    return 0;
}
结果:
2023/7/27
2023/7/27

对于简单的类,默认的赋值运算符一般就够用了,我们也没有必要再显式地重载它。但是当类持有其它资源时,例如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认的赋值运算符就不能处理了,我们必须显式地重载它,这样才能将原有对象的所有数据都赋值给新对象。

例如:

#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year=0,int month=0,int day=0):
    _year(year),
    _month(month),
    _day(day)
    {
        _cal =(int*)malloc(sizeof(int)*10);
    }
    void show()
    {
        cout<<_year<<"/"<<_month<<"/"<<_day<<endl;
    }
    ~Date()
    {
        free(_cal);
    }
private:
    int* _cal;
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2023,7,27);
    Date d2;
    d2=d1;
    d1.show();
    d2.show();
    return 0;
}
结果:
2023/7/27
2023/7/27
8_9(2462,0x1000efe00) malloc: *** error for object 0x101b2ad70: pointer being freed was not allocated
8_9(2462,0x1000efe00) malloc: *** set a breakpoint in malloc_error_break to debug

这里为什么会运行崩溃呢?Date类里面成员变量_cal,cal指向了一块动态开辟的空间,而动态开辟的空间需要我们手动释放,因此我们在析构函数中释放_cal动态开辟的空间。我们把d1赋值给d2时,其中是将d2._cal指向d1._cal指向的空间,所以赋值完成后,d1._cal和d2._cal指向同一块空间,程序结束时会调用析构函数,编译器就会先调用d2的析构函数,对d2._cal指向的空间进行释放,然后编译器再调用d2的析构函数,对d1._cal指向的空间进行释放,因为d1._cal和d2._cal指向同一块空间,这块空间已经被释放,free再次释放就会失败,所以就会运行崩溃。一句话来说就是对一块空间重复释放。这就是默认的赋值运算符实现的浅拷贝带来的问题。

怎么样上面的问题?我们可以显示定义赋值运算符,实现深拷贝的功能

Date& operator=(const Date& d)
{
     _cal=(int*)malloc(sizeof(int)*10);
     memcpy(_cal, d._cal, 40);
     _year=d._year;
     _month=d._month;
     _day=d._day;
     return *this;
}

赋值运算符主要有四点:

1. 参数类型

2. 返回值

3. 检测是否自己给自己赋值

4. 返回*this

5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。

拷贝构造和赋值运算符的显著区别

拷贝构造:一个对象已经创建,一个还没有

例如:Date d1(d2)    Date d1=d2

赋值重载:两个对象都存在

例如:d1=d2

 🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 

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

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

相关文章

二叉树的广度优先遍历 - 华为OD统一考试(D卷)

OD统一考试(D卷) 分值: 200分 题解: Java / Python / C++ 题目描述 有一棵二叉树,每个节点由一个大写字母标识(最多26个节点)。 现有两组字母,分别表示后序遍历(左孩子->右孩子->父节点)和中序遍历(左孩子->父节点->右孩子)的结果,请输出层次遍历的结…

视频号小店应该怎么去做呢?新手必看!一篇详解!

大家好&#xff0c;我是电商小V 视频号小店就是腾讯推出的新项目&#xff0c;目前正处于红利期&#xff0c;现在也是入驻的好时机&#xff0c;背靠腾讯平台&#xff0c;是为商家提供商品信息展示&#xff0c;商品交易&#xff0c;支持商家在视频号场景内开店经营的官方平台&…

六、Redis五种常用数据结构-zset

zset是Redis的有序集合数据类型&#xff0c;但是其和set一样是不能重复的。但是相比于set其又是有序的。set的每个数据都有一个double类型的分数&#xff0c;zset正是根据这个分数来进行数据间的排序从小到大。有序集合中的元素是唯一的&#xff0c;但是分数(score)是可以重复的…

51单片机软件环境安装

keli5的安装 把CID放到破解程序中 破解程序会给一串数字然后填到那个框中 驱动程序的安装 安装完了以后 设备管理器会出现这个 同时c盘会出现这个文件夹

好题总结汇总

好题总结汇总 总结一些做完很有收获的题。 一、经典问题 DP的结合 1、题意&#xff1a; 给定 n n n 种颜色的球的数量 a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1​,a2​,...,an​&#xff0c;选出一些不同种类的球(也就是在n种球中选球的任意情况)&#xff0c;将球…

把项目打包成Maven Archetype(多模块项目脚手架)

1、示例项目 2、在pom.xml中添加archetype插件 <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-archetype-plugin</artifactId><version>3.2.0</version> </plugin>3、打包排除某些目录 当我们使用…

上海AI Lab开源首个可替代GPT-4V的多模态大模型

与开源和闭源模型相比&#xff0c;InternVL 1.5 在 OCR、多模态、数学和多轮对话等 18 个基准测试中的 8 个中取得了最先进的结果。 上海AI Lab 推出的 InternVL 1.5 是一款开源的多模态大语言模型 (MLLM)&#xff0c;旨在弥合开源模型和专有商业模型在多模态理解方面的能力差距…

智慧公厕打造智慧城市新标杆

公共厕所作为城市基础设施的重要组成部分&#xff0c;直接关系到市民的生活品质和城市形象。传统的公厕管理方式存在着许多问题&#xff0c;如环境脏乱、清洁不及时等&#xff0c;给市民带来了诸多不便和不满。而智慧公厕作为一种全新的管理模式&#xff0c;通过物联网、大数据…

CSS-浮动

float (浮动) 作用&#xff1a;盒子的顶点是一样的&#xff0c;具备行内块的特征&#xff0c;能设置宽高 属性&#xff1a;float 属性值&#xff1a;left 浮动在网页左边 right 浮动在网页右边 .a{width: 100px;height: 100px;float:left;background-color: red;}.b…

AI算法-高数2-导数定义和公式

P14 2.1 导数的定义(一):2.1 导数的定义_哔哩哔哩_bilibili 导数定义&#xff1a; 导数公式&#xff1a; P15 2.1 导数的定义(二)&#xff1a;2.1 导数的定义&#xff08;二&#xff09;_哔哩哔哩_bilibili [a,b]可导&#xff0c;a的端点&#xff1a;右可导&#xff0c;b端点&…

[ 视频号]代替用户发布视频api

使用接口&#xff0c;替代用户使用设备直接发布视频api 接口地址&#xff1a; http://接口地址/api/v2 先调用登录接口&#xff0c;进行账号登录 登录二维码接口入参&#xff1a; {"appId": "","proxyIp": "","regionId"…

企业网站管理系统(多语言)源码搭建/部署/上线/运营/售后/更新

一款基于FastAdminThinkPHPUniapp开发的企业网站管理系统插件。支持自定义多语言、自定义模型与字段、自定义表单等功能。 功能特性 多语言功能&#xff08;默认中英文&#xff0c;后台可新增语言&#xff0c;并支持管理前台语言包&#xff09;有道api翻译功能响应式布局&…

安全加固

目录 1.文件锁定管理 2.设置用户账户有效期 3.查看并清除命令历史记录 4.设置用户超时登出时间 5.用户切换 6.用户提权 7.禁用重启热键CtrlAltDel 8.设置单用户模式密码 9.调整BIOS引导设置 10.禁止root用户从本地登录&#xff1a; 11.禁止root用户通过ss…

Linux系统编程--初识Linux

目录 一、相关概念 1、Unix系统 2、操作系统 操作系统的分类&#xff1a; 流行的操作系统&#xff1a; 3、Ubuntu系统及特点 二、Ubuntu安装 三、Linux目录 /根目录 路径分类&#xff1a; 四、shell指令 1、命令行提示符&#xff1a; 2、指令 2.1命令基本的操作&…

Meta FAIR: 深层网络不合理的低效性

这篇文章的标题"The Unreasonable Ineffectiveness of the Deeper Layers"巧妙地呼应了著名物理学家尤金维格纳在1960年发表的一篇论文"数学在自然科学中不合理的有效性"(The Unreasonable Effectiveness of Mathematics in the Natural Sciences)。维格纳…

ubuntu22.04服务器docker-compose方式部署ldap服务

一&#xff1a;系统版本 二&#xff1a;部署环境 节点名称 IP 部署组件及版本 配置文件路径 机器CPU 机器内存 机器存储 Ldap 10.10.10.111 self-service-password:latest phpldapadmin:latest openldap:latest openldap:/data/openldap/config phpldapadmin&#x…

梅兰日兰NSJ400N断路器NSJ400N可议价

梅兰日兰 NSJ400N 3 极 400 安培 600 伏交流电 紧凑型断路器 制造商的原始标签 脱扣单元&#xff1a;LS 功能 –&#xff08;长时间和短时间&#xff09; 负载侧凸耳 中断额定值&#xff1a;65kA 240 Vac 35kA 480 伏交流电压 18kA 600 伏交流电压 &#xff08;外观可能与照…

01、vue+openlayers6实现自定义测量功能(提供源码)

首先先封装一些openlayers的工具函数&#xff0c;如下所示&#xff1a; import VectorSource from ol/source/Vector; import VectorLayer from ol/layer/Vector; import Style from ol/style/Style; import Fill from ol/style/Fill; import Stroke from ol/style/Stroke; im…

【SpringBoot整合系列】SpringBoot整合RabbitMQ-消息过期(死信队列和延迟队列)

目录 业务场景传统轮询消息队列完整版 默认情况TTL&#xff08;消息的有效期&#xff09;TTL 的设置有两种不同的方式单条消息过期队列消息过期特殊情况 死信队列概述应用场景产生原因原理图死信交换机死信队列实现一下 延迟队列背景定时任务&#xff1f;延迟队列实现思路代码 …

Spring @Repository 注解

Spring 的项目严重依赖注解。 Repository 注解 在Spring2.0之前的版本中&#xff0c;Repository注解可以标记在任何的类上&#xff0c;用来表明该类是用来执行与数据库相关的操作(即dao对象)&#xff0c;并支持自动处理数据库操作产生的异常 在Spring2.5版本中&#xff0c;引…