活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 一键部署Llama3大模型学习机 0代码一键部署,预装最新主流大模型Llama3与StableDiffusion
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 产品能力
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      C++:类与对象(2)

      首页 知识中心 软件开发 文章详情页

      C++:类与对象(2)

      2025-05-09 08:20:32 阅读次数:1

      函数,拷贝,构造函数,类型,编译器,运算符,重载

       一、六大默认成员函数

      C++为了弥补C语言的不足,设置了6个默认成员函数

      C++:类与对象(2)

      二、构造函数

      2.1 概念

             在我们学习数据结构的时候,我们总是要在使用一个对象前进行初始化,这似乎已经成为了一件无法改变的事情,如以下的Data类

      C++:类与对象(2)

             对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,我们的祖师爷就在想,像初始化这种傻瓜式的行为,能不能交给编译器去完成呢?能否在对象创建时,就将信息设置进去呢?于是就有了构造函数!!

            构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。


      2.2 特性

           构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
      其特征如下:
      特性1. 函数名与类名相同。
      特性2. 无返回值。
      特性3. 对象实例化时编译器自动调用对应的构造函数。(由编译器完成)
      特性4. 构造函数可以重载。(即一个类可以有多种构造函数,也就是多种初始化方式)

      C++:类与对象(2)

      思考: 

      1、为什么调用无参构造不加个括号呢??总感觉很奇怪??

      答:其实按道理来说加个括号比较合理,但是如果我们加上了一个括号,如上图的Date d3(),就会发现这个和函数的声明会难以区分,从函数的声明来看,会被翻译成声明了一个d3函数,该函数无参,返回一个Date对象,所以为了区分这两种情况,要求无参构造不能加括号。那你可能会问,为什么传参构造就不会当成有参函数的声明了呢??因为有参函数声明的写法应该是Date d2(int x,int y,int z)这个样子的写法,那么你会发现有参构造和他是有区别的。所以这里不需要区分。

      2、每次都写一个有参构造和无参构造是不是有点麻烦??有没有改进方法?

      答:这个时候我们之前学的缺省参数就派上用场了!!我们可以将有参和无参构造合并成一个全缺省构造函数。一样可以完成这个初始化过程,如下图:

      C++:类与对象(2)

      3、既然构造函数支持重载,那么全缺省的构造函数和无参构造函数可以同时存在吗??

      答:不行!这两个只能存在一个,虽然你在定义的时候好像没有报错,但是你在调用的时候就存在歧义,因为编译器区分不出来应该去调用哪个!

      C++:类与对象(2)

      特性5:如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

             如下图,当我们注释掉我们之前写的构造函数,编译器调用了他自动生成的默认构造函数,将实例化对象的成员初始化成了随机值。

      C++:类与对象(2)

            如下图,如果我们自己写了一个构造函数,无论有参还是无参,编译器的默认构造函数都不会生成了!!

      C++:类与对象(2)

      思考:

      1、不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

      答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
      型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看
      下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
      函数。

      C++:类与对象(2)

      所以我们可以得到两个结论: 

      默认生成的构造函数

      (1)对内置类型不做处理

      (2)自定义类型的成员,会去调用他们的默认构造(无参构造、自动生成的构造、全缺省的构造)

      2、 既然都可以默认处理自定义类型了,那为什么不顺便把内置类型也处理一下呢?

      这其实是设计过程中遗留下来的一个问题,后来在C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

      C++:类与对象(2)

       特性6:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(特性4的思考3中已经分析过了)

      注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
      是默认构造函数。

      思考:

      1、我们怎么去把握什么时候用编译器的构造函数,什么时候用自己写的构造函数呢?

      答:无论是自己写的还是编译器提供的,一般的建议是,确保每个类都提供一个默认构造函数,因为有时候如果该类中有自定义类型的成员,我们就可以利用特性(自定义类型的成员,会去调用他们的默认构造),让编译器来帮助我们完成对自定义类型成员的初始化。

      2、内置类型的初始化一般怎么处理?

      答:由于编译器不会处理,所以有两种思路:1、自己写构造函数,根据该类的特定去初始化其内置类型成员。尽量用缺省,这样可以同时应对无参和有参的情况 2、利用c++11新增加的特性,让内置类型在声明的时候给一个默认值。一般来说这两个可以综合起来运用。

       三、析构函数

      3.1 概念

      通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
      析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
      编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

      3.2 特性

      析构函数是特殊的成员函数

      其特征如下:
      特性1. 析构函数名是在类名前加上字符 ~。
      特性2. 无参数无返回值类型。
      特性3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
      函数不能重载

      特性4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

      typedef int DataType;
      class Stack
      {
      public:
      	Stack(size_t capacity = 3)
      	{
      		_array = (DataType*)malloc(sizeof(DataType) * capacity);
      		if (NULL == _array)
      		{
      			perror("malloc申请空间失败!!!");
      			return;
      		}
      		_capacity = capacity;
      		_size = 0;
      	}
      	void Push(DataType data)
      	{
      		// CheckCapacity();
      		_array[_size] = data;
      		_size++;
      	}
      	// 其他方法...
      	~Stack()
      	{
      		if (_array)
      		{
      			free(_array);
      			_array = NULL;
      			_capacity = 0;
      			_size = 0;
      		}
      	}
      private:
      	DataType* _array;
      	int _capacity;
      	int _size;
      };
      int main()
      {
      	Stack s;
      	s.Push(1);
      	s.Push(2);
      }


      C++:类与对象(2)

      特性5: 如果类中没有显式定义析构函数,则C++编译器会自动生成一个析构函数,一旦用户显式定义编译器将不再生成。

      C++:类与对象(2)

      通过上图我们可以得到结论: 

      默认构造函数对

      (1)内置类型成员不处理

      (2)自定义类型成员,调用他的析构函数

      思考:

      1、对于构造函数,我们会想办法对他的内置类型初始化,那析构函数需要对内置类型需要处理吗??

      答:并不需要!对于内置类型来说,销毁时是不需要进行资源清理的,最后系统的内存会将其回收的(因为我们把内置类型成员恢复成0是没有意义的,因为他不管是多少,当内存被系统回收后,最后都是会被覆盖的,不用多此一举)

      2、我们之前学过,局部对象在函数调用完成后会随着函数栈帧的销毁而销毁,该过程是由编译器完成的,那究竟什么时候我们需要用到析构函数??

       答:析构函数并不是对对象本身进行销毁,因为对象本身作为一个局部变量,在函数结束后会自动被回收的,所以析构函数本质上是对对象的资源进行清理,什么叫做资源呢?可以理解成我们实例化某些对象时需要向内存申请在堆区开辟的空间,所以需要在对象销毁前将该空间还给操作系统,否则就容易造成内存泄露!!

      结论:如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
      Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类(Stack类的实例化需要在堆区申请空间)

      3、了解了构造函数和析构函数,我们来对比一下和C语言使用起来的区别(如下图)

      C++:类与对象(2)

            使用起来不仅简洁,而且不需要担心自己忘记初始化栈或者销毁栈。只要一开始我们把每个类的定义的构造函数和析构函数都考虑清楚,那么其他的就放手交给编译器去做!!

      四、拷贝构造函数 

      在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
      然后我们的祖师爷思考:那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?所以有了 拷贝构造函数

      4.1 概念

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

      4.2 特性

      拷贝构造函数也是特殊的成员函数

      其特征如下:
      特性1:拷贝构造函数是构造函数的一个重载形式。(可以理解成比较特殊的构造函数)
      特性2:拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

      C++:类与对象(2)

      思考:

      1、拷贝明明是一个对象拷贝给另一个对象,为什么这边只有一个参数呢?

      答:因为成员函数会隐藏一个this指针,在运行的时候编译器会自动帮我们处理,所以我们只需要传那个我们需要拷贝的类类型对象就行

      2、为什么传值方式编译器会无限递归? 

      C++:类与对象(2)

      我们观察上图,通过函数栈帧的了解我们可以知道,每次传值调用的时候本质上形参和实参并不是同一块空间,而是我们在调用的时候开辟了一块形参的空间,然后将实参的数据拷贝过来,再到函数中进行使用。而每次拷贝本质上都是创建一个同类型的对象。然后他为了和实参同步数据也会调用自己的拷贝构造, 因此就跟套娃一样引发无线递归。但如果是传引用,就不存在这个问题了,因为存引用本身就是给实参起一个别名,函数调用的时候操作的是同一块空间,不需要创建新的对象也不需要拷贝。所以不会引发无穷递归!

      3、为什么概念里提到,对本类类型对象的引用一般用const修饰?有什么好处吗?

      好处1:确保被拷贝的对象不会被修改,比如我们一不小心写反了,这个时候const可以及时帮助我们报错来提示我们

      C++:类与对象(2)

      好处2:如果拷贝构造传的是const修饰的变量,如果你的拷贝构造函数没用const修饰,就会造成权限放大

      C++:类与对象(2)

       把拷贝构造函数的参数用const修饰就可以避免这种问题

      C++:类与对象(2)

      特性3:若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
       C++:类与对象(2)

      通过上图我们可以得到结论:

      1、内置类型,编译器可以直接拷贝

      2、自定义类型的拷贝,需要调用其拷贝构造函数

      思考

      1: 我们发现,我们将我们原来写的拷贝构造删除掉,编译器生成的拷贝构造也可以完成字节序的值拷贝工作啊,那我们还有必要自己写拷贝构造函数吗?

       答:要分具体情况而定,如果是之前的Date日期类,就不需要,但是有些情况下浅拷贝就会造成很严重的后果

      // 这里会发现下面的程序会崩溃掉?这里需要深拷贝去解决。
      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;
      }

      C++:类与对象(2)

      报错原因如下: 

       C++:类与对象(2)

      2、根据思考1,总结浅拷贝可能造成的问题,然后思考怎么用深拷贝去解决问题。 

      答:问题1:两个对象操控同一块空间,严重情况下会造成数据丢失!!如上图,s1 push了4个元素后,他的size变成了4,但是s2并不知道size变成了4,他的size还是0,如果s2继续push 5 6 7 8,那么因为操控的是同一块空间,那么就会造成s2  push的数据将s1 push的数据给覆盖了,造成了数据丢失,这个在实际工作中是很严重的问题(假如你在银行先存了100万,我后存了200元,如果由于这个原因导致我的200把你的100万覆盖了。你在系统上看到的就是200元而不是你原来的100万!)问题2:造成空间的多次释放,这个在上图已经解释过了,由于共用一块空间,s2先调用析构函数把空间释放了,但是s1并不知道,他再调用自己的析构函数释放的时候就会造成程序的崩溃!!!

      3、怎么用深拷贝解决上述问题??

      上述问题的根源是指向了同一块空间,所以我们解决思路就是给拷贝出来的对象也开辟一块相应的空间,让他们能够各自操作各自独立的空间。

      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;
      	}
      	Stack(const Stack& st)
      	{
      		_array = (DataType*)malloc(st._capacity * sizeof(DataType));
      		if (nullptr == _array)
      		{
      			perror("malloc申请空间失败");
      			return;
      		}
      		memcpy(_array, st._array, st._size * sizeof(DataType));//要记得把原来的数据拷贝过去
      		_size = st._size;
      		_capacity = st._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);
      //看看操作s2会不会影响s1
      	s2.Push(5);
      	s2.Push(6);
      	s2.Push(7);
      	s2.Push(8);
      	return 0;
      }

      如上图,这个深拷贝的关键就是要开辟一个新的空间,然后将原空间的数组拷贝过来! 

      C++:类与对象(2)

      我们发现s1和s2的array已经不是指向一块空间了

      C++:类与对象(2)

       而且对s2操作不会影响到s1,他们去调用自己的析构函数都会去释放自己独立的空间,这就是深拷贝!!

       4.3 使用场景

      1、使用已存在对象创建新对象
      2、函数参数类型为类类型对象
      3、函数返回值类型为类类型对象
      C++:类与对象(2)

       C++:类与对象(2)     为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
      尽量使用引用(看看栈帧销毁后他的空间是否还存在,如果存在就引用,不存在就不要引用)

      C++:类与对象(2)

      五、运算符重载 

              以前我们学习操作符的时候都知道操作符只能对内置类型其效果,对有多个成员的结构体成员是起不到效果的,因为编译器无法判断对哪一个类型成员进行操作或者哪一个类型成员进行比较,但是有些时候我们还是避免不了要对结构体进行比较和操作,如果总是通过调用函数来操作自定义类型的话,可读性太差!所以我们的祖师爷发明了运算符重载。

      5.1 运算符重载

      C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
      返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
      函数名字为:关键字operator后面接需要重载的运算符符号。
      函数原型:返回值类型 operator操作符(参数列表)
       

      注意事项:

      1、不能通过连接其他符号来创建新的操作符:比如operator@


      2、重载操作符必须有一个类类型参数

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

      4、作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
      藏的this(所以至少有一个类型参数就够了)

      5、.*(少用,注意和*区分)     ::(访问限定符)     sizeof(计算类型大小)      ?:(三目运算符)      .(类成员访问操作符)     注意以上5个运算符不能重载。
        C++:类与对象(2)

       如上图,我们如果用全局的operator,那么我们就不能给他的成员变量用private保护起来,否则会访问不到对应的操作数

      C++:类与对象(2)

      因此我们的运算符重载一般在类的里面去定义,这样有两个好处:

      1、在类内部定义就可以用private去保护成员变量了,保证了封装性 

      2、在类里面定义,这样operator就有一个隐藏的this指针,所以只要传一个参数就可以了。

      5.2 赋值运算符重载

      特性1:参数类型:const T&,传递引用可以提高传参效率
      特性2:返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
      特性3:检测是否自己给自己赋值(避免额外的开销)
      特性4:返回*this :要复合连续赋值的含义

      Date& operator=(const Date& d)
      {
      if(this != &d)
      {
      _year = d._year;
      _month = d._month;
      _day = d._day;
      }
      return *this;
      }

      思考:

      1、为什么要避免自赋值的情况 ?不避免可以吗?

      如果是自赋值,如果类里面含有指针指向动态开辟的内存的话,那么自身赋值就可能出错,因为在赋值前需要把原来的空间给释放掉。就不能赋值了。

      2、为什么要用引用返回?

      为了支持连续赋值!!

      特性5:赋值运算符只能重载成类的成员函数不能重载成全局函数

      C++:类与对象(2)

      思考:

      1、之前我们实现其他运算符,也是可以定义全局函数啊,大不了传两个参数不就行了。为什么这里赋值运算符重载必须是成员函数? 

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

      上图的情况就是编译器自动生成的默认赋值重载函数

      为了不和该情况冲突,C++强制让=重载必须是成员函数。

      C++:类与对象(2)

      特性6:用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

      C++:类与对象(2)

      思考:

      1、既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?

      答:当然像日期类这样的类是没必要,但是一旦涉及到资源管理就必须要自己去实现赋值运算符的重载。

      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;
      }

      C++:类与对象(2)

      比如上述代码会崩掉 

      C++:类与对象(2)

      5.3 前置++和后置++重载

              前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载。C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
      int只是用来占位,没有实际意义

       前置++:

      Date& operator++()
      {
      _day += 1;
      return *this;
      }

      ++d1,相当于调用d1.operator( ) 

      后置++:

      Date operator++(int)
      {
      Date temp(*this);
      _day += 1;
      return temp;
      }

      d1++,相当于调用d1.operator(0)  

       从这里可以看出,后置++还需要再实例化一个对象用来拷贝原先的数据,并且由于temp是局部变量,出作用域销毁,所以这里不能用传引用返回,效率相比前置++有一定拷贝的损失,所以我们平时要尽量用前置++。

      关于==    !=   <  <=   >   >=   += -= = - ……我们后面通过一个日期类来实现

      六、const成员函数(修饰*this)

      有些时候我们可能会遇到以下情况

      C++:类与对象(2)

      由于我们定义的d1是const类型,当取d1的地址传给隐藏的*this时,出现了权限放大!!那我们要样才能让const类型对象调用自己的成员函数???就必须给*this指针也加上const,这样传参就不会出现权限放大的问题。但是C++中的*this指针是隐含的参数,我们没办法直接加,C++为了解决此类问题,规定当我们将const修饰放在成员函数后面的时候,默认就是将该成员函数隐藏的*this进行const修饰

      将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
      隐含的this指针
      ,表明在该成员函数中不能对类的任何成员进行修改。
      C++:类与对象(2)

      注意:

      1、非const的对象可以调用const或者是非const的成员函数,而const的对象只能调用const的成员函数,其实总的来说就是权限可以变小、可以平移,就是不能放大

      2、使用建议:内部不改变成员变量的成员函数最好加上const,这样const对象和普通对象都可以调用

      七、取地址及const取地址操作符重载

      这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
      C++:类与对象(2)

           这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

      C++:类与对象(2)

       八、用类实现一个数组

      我们可以设置一个类,在里面定义一个静态数组,然后重载一个[ ]来让这个类模拟数组

      C++:类与对象(2)

       你可能会觉得,这样子是不是多此一举,其实不是的。

      1、一般来说,编译器对数组的越界检查并不是非常的明显,如果我们用这个类去模拟数组,功能就可以很丰富,比如说使用assert去检查越界

      C++:类与对象(2)

      2、 我可以重载两个[ ],一个用const修饰*this确保类不被改变,另一个不用const修饰,确保类可以改变,这样可以根据不同的场景去调用

      C++:类与对象(2)

      九、日期类的实现(时间计算器)

      先展示全部代码,然后再细扣

      9.1 Date.h

      #pragma once
      #include<iostream>
      #include<assert.h>
      using namespace std;
      class Date
      {
      	//友元,告诉该类这两个全局函数是我们的朋友,允许使用私有成员
      	friend ostream& operator<<(ostream& out, const Date& d);
      	friend istream& operator>>(istream& in, Date& d);
      public:
      	//全缺省的构造函数
      	Date(int year = 1900, int month = 1, int day = 1);
      	//用来打印
      	void Print() const;
      	//比较
      	bool operator==(const Date& d) const;
      	bool operator!=(const Date& d) const;
      	bool operator<(const Date& d) const;
      	bool operator<=(const Date& d) const;
      	bool operator>(const Date& d) const;
      	bool operator>=(const Date& d) const;
      	//日期加天数
      	Date& operator+=(int day);
      	Date operator+(int day) const;
      	//日期减天数
      	Date& operator-=(int day);
      	Date operator-(int day) const;
      	// ++d1
      	Date& operator++();
      	// int参数 仅仅是为了占位,跟前置重载区分
      	Date operator++(int);
      	// --d1 -> d1.operator--()
      	Date& operator--();
      	// d1-- -> d1.operator--(1)
      	Date operator--(int);
      	//返回两个日期的相差天数
      	int operator-(const Date& d) const;
      	//void operator<<(ostream& out);做成员函数的话,限制了左操作数必须是d。变成  d<<cout;
      private:
      	int _year;
      	int _month;
      	int _day;
      	//获取每月的天数
      	int GetMonthDay(int year, int month) const;
      	//判断是不是闰年
      	bool is_leapyear(int year) const;
      };

      9.2 Date.c

      #include"Date.h"
      
      Date::Date(int year, int month, int day)
      {
      	//确保日期合法
      	if ((month > 0 && month < 13) && (day > 0 && day <= GetMonthDay(year, month)))
      	{
      		_year = year;
      		_month = month;
      		_day = day;
      	}
      	else
      		cout << "日期非法" << endl;
      }
      
      void Date::Print() const
      {
      	cout << _year << "/" << _month << "/" << _day << endl;
      }
      
      int Date::GetMonthDay(int year, int month) const
      {
      	assert(month>0 && month < 13);//确保传进来的month是合法的
      	int montharr[13] = { 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 29;
      	else
      		return montharr[month];
      }
      
      bool Date::operator==(const Date& d) const
      {
      	return _year == d._year && _month == d._month && _day == d._day;
      }
      
      bool Date::operator!=(const Date& d) const
      {
      	return !(*this == d);//复用==
      }
      
      bool Date::operator<(const Date& d) const
      {
      	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) const
      {
      	return (*this == d) || (*this < d);
      }
      
      bool Date::operator>(const Date& d) const
      {
      	return !(*this <= d);
      }
      
      bool Date::operator>=(const Date& d) const
      {
      	return!(*this < d);
      }
      
      Date& Date::operator+=(int day)//为了满足连续+=
      {
      	//如果传的是负数  +负的相当于-正的
      	if (day < 0)
      	{
      		*this -= -day;
      		return *this;
      	}
      	_day += day;
      	while (_day > GetMonthDay(_year, _month))
      	{
      		_day -= GetMonthDay(_year, _month);
      		_month++;
      		if (_month == 13)
      		{
      			++_year;
      			_month = 1;
      		}
      	}
      	return *this;
      }
      
      Date Date::operator+(int day) const
      {
      	Date temp(*this);
      	temp += day;//复用+=
      	return temp;
      }
      
      
      Date& Date::operator-=(int day)
      {
      	//如果传的是负数  -负的相当于+正的
      	if (day < 0)
      	{
      		*this += -day;
      		return *this;
      	}
      	_day -= day;
      	while (_day <= 0)
      	{
      		--_month;//借位
      		if (_month == 0)
      		{
      			--_year;
      			_month = 12;
      		}
      		_day += GetMonthDay(_year, _month);
      	}
      	return *this;
      }
      
      Date Date::operator-(int day) const
      {
      	Date temp(*this);
      	temp -= day;//复用-=
      	return day;
      }
      
      Date& Date::operator++()
      {
      	*this += 1;
      	return *this;
      }
      
      Date Date::operator++(int)
      {
      	Date temp(*this);
      	*this += 1;
      	return temp;
      }
      
      Date& Date::operator--()
      {
      	*this -= 1;
      	return *this;
      }
      
      Date Date::operator--(int)
      {
      	Date temp(*this);
      	*this -= 1;
      	return temp; 
      }
      
      
      //方法1,不断++直到等于大的那个年份(好写,但是效率低点)
      //int Date::operator-(const Date& d) const
      //{
      //	Date max = *this;
      //	Date min = d;
      //	int flag = 1;
      //	if (*this < d)//假设错了就认错
      //	{
      //		Date max = d;
      //		Date min = *this;
      //		int flag = -1;//用来标记
      //	}
      //	int count = 0;
      //	while (min != max)
      //	{
      //		++min;
      //		++count;
      //	}
      //	//循环结束得到的count就是目标天数
      //	return count * flag;
      //}
      
      //方法2,先把两个年份修饰到1月1日,然后算两个年之间有多少年,如果是平年+365,闰年+366 (难写,但是效率高点)
      bool Date::is_leapyear(int year) const
      {
      	if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
      		return true;
      	else
      		return false;
      }
      
      int Date::operator-(const Date& d) const
      {
      	//不知道哪个操作数大,先假设
      	Date max = *this;
      	Date min = d;
      	int flag = 1;
      	if (*this < d)//假设错了就认错
      	{
      		Date max = d;
      		Date min = *this;
      		int flag = -1;//用来标记
      	}
      	int count =0;
      	//大的减到1月1日  count++
      	while (!(max._day == 1 && max._month == 1))
      	{
      		--max;
      		++count;
      	}
      	//小的减到1月1日  count--
      	while (!(min._day == 1 && min._month == 1))
      	{
      		--min;
      		--count;
      	}
      	//都减到1月1日了  算差多少年
      	while (min._year != max._year)
      	{
      		if (is_leapyear(min._year))
      			count += 366;
      		else
      			count += 365;
      		++min._year;
      	}
      	return flag * count;
      }
      
      
      
      ostream& operator<<(ostream& out, const Date& d)
      {
      	out << d._year << "" << d._month << "" << d._day << "" << endl;
      	return out;
      }
      
      istream& operator>>(istream& in, Date& d)
      {
      	in >> d._year >> d._month >> d._day;
      	return in;
      }

      9.3 流提取和插入

      通过运算符重载的学习,我们知道了cout和cin的本质也是也是一个输入和输出流对象,而<<和>>是他们重载出来的运算符,cout属于ostream类,cin属于istream类

      C++:类与对象(2)

      我们还可以发现为什么cout和cin可以自动识别类型呢??因为他的类里面有针对不同类型的运算符重载 

       那我们是否也可以通过重载<<和>>去打印日期和提取日期呢?

      我们先尝试重载<<打印日期

      C++:类与对象(2)

      如果我们在Date内部去对<<重载,要使用的话是这样的

      C++:类与对象(2)

      为什么是这样呢?因为在Date类里面定义的话默认Data类是第一个操作数,d1<<cout显然不符合我们的习惯。 

      所以方法就是在类外去重载<<,这样我们可以去改变操作数,使其变成cout<<d1,这样可以符合我们的使用习惯,但是这样会面临一个问题:类外没办法访问Data类的私有成员。。如果我们把权限放开了又不合适,这个时候就要用到我们的友元,即告诉Date类,这个全局函数是我们的朋友,可以访问私有成员。

       C++:类与对象(2)

       注意事项:

      1、用他们的类引用作返回值,是为了应对连续流提取和连续流插入的情况

      2、要注意流提取不能对Date进行const修饰,因要通过键盘读取数据存进date对象的成员变量里面

      3、对于这样的短小的函数,可以用内联(在头文件定义)

      9.4 比较运算符的重载

      基本上只要实现了==和<(>),其他的就可以直接复用函数。

      9.5 不希望别人使用的成员函数

      C++:类与对象(2)

      这两个函数并不希望别人使用,因为这两个函数都是在成员函数内部去调用的,所以不需要公有。

      9.6 +=和+  -=和- 

      要注意的是,+=是直接改变对象,而+并不会改变原对象。所以+=可以直接就操作,+却要重新创建一个类对象然后进行拷贝,再拷贝的对象上进行操作

      注意事项:

      1、+=和-=都是引用返回、不能用const修饰(改变原对象),返回值是类(支持连续操作)

      2、+和-都是传值返回(因为拷贝的是一个局部变量出作用域会销毁)、可以用const修饰(再拷贝对象上操作),返回值也是类支持(连续)

      3、按道理来说,我们实现+=和+只需要实现一个即可。另外一个进行复用就行,从效率的角度考虑,先实现+=,再用+复用+=会更好,因为+=不会进行拷贝,而且是传引用返回,可以避免不必要的损失,如果是+=复用+,那么无论是+还是+=都会调用一个+,也就是无论如何都要拷贝。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.csdn.net/weixin_51142926/article/details/136276368,作者:✿༺小陈在拼命༻✿,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:DS初阶:八大排序之直接插入排序、希尔排序和选择排序

      相关文章

      2025-05-09 08:20:32

      STL:Vector的模拟实现

      STL:Vector的模拟实现

      2025-05-09 08:20:32
      memcpy , pos , vector , 失效 , 扩容 , 拷贝 , 迭代
      2025-05-09 08:20:32

      STL:模版初阶 | STL简介

      STL:模版初阶 | STL简介

      2025-05-09 08:20:32
      STL , 函数 , 实例 , 模板 , 模版 , 类型 , 编译器
      2025-05-09 08:20:32

      STL:模版进阶 | Priority_queue的模拟实现

      模板参数分类为类型形参与非类型形参。

      2025-05-09 08:20:32
      函数 , 参数 , 容器 , 模板 , 模版 , 类型
      2025-05-08 09:03:47

      剑指 Offer 30. 包含min函数的栈

      定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。 

      2025-05-08 09:03:47
      min , 函数 , 复杂度 , 示例 , 调用
      2025-05-08 09:03:29

      Java序列化有什么作用

      对java对象进行序列化之后,会变成字节码,这样就会比较方便在网络上进行传输,也可以在磁盘上进行存储。

      2025-05-08 09:03:29
      对象 , 序列化 , 接口 , 构造函数
      2025-05-08 09:03:07

      【Java 】包装类详解:从基本概念到实战技巧

      在 JAVA 中,八大基础数据类型(int,float,double...)不是继承自 Object,并且不具备对象的特征的。比如:基本数据类型就不能调用方法。

      2025-05-08 09:03:07
      Integer , 包装 , 基本 , 数据类型 , 类型
      2025-05-07 09:12:52

      C语言:内存函数

      C语言:内存函数

      2025-05-07 09:12:52
      memcpy , memmove , 内存 , 函数 , 字节 , 拷贝 , 重叠
      2025-05-07 09:12:52

      C语言:预处理详解

      C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。

      2025-05-07 09:12:52
      define , 函数 , 参数 , 头文件 , 定义 , 替换 , 编译
      2025-05-07 09:12:52

      C语言:函数递归

      递归式一种解决问题的方法,在C语言中,递归就是自己调用自己。

      2025-05-07 09:12:52
      个数 , 函数 , 迭代 , 递归
      2025-05-07 09:10:01

      MySQL—函数—日期函数(基础)

      MySQL—函数—日期函数(基础)

      2025-05-07 09:10:01
      date , type , 函数 , 天数 , 当前 , 日期 , 时间
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33096

      阅读量

      4934838

      查看更多

      最新文章

      STL:模版初阶 | STL简介

      2025-05-09 08:20:32

      Java序列化有什么作用

      2025-05-08 09:03:29

      【Java 】包装类详解:从基本概念到实战技巧

      2025-05-08 09:03:07

      C语言:预处理详解

      2025-05-07 09:12:52

      C语言:内存函数

      2025-05-07 09:12:52

      C语言:函数递归

      2025-05-07 09:12:52

      查看更多

      热门文章

      Python 函数调用父类详解

      2023-04-23 09:44:31

      C++拷贝构造函数(深拷贝,浅拷贝)详解

      2023-03-30 09:59:46

      游戏编程之六 游戏编程的特点

      2024-09-25 10:13:46

      C#8.0新语法

      2023-02-07 10:34:04

      实现远程线程DLL注入

      2023-05-04 08:57:15

      (10)Qt对象模型

      2023-02-13 07:55:59

      查看更多

      热门标签

      java Java python 编程开发 开发语言 代码 算法 线程 html Python 数组 C++ javascript 元素 c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      【多线程】c++11多线程编程(六)——条件变量(Condition Variable)

      Flink类型与Java基本类型关系映射表

      初始Java篇(JavaSE基础语法:方法的使用)(3)

      Java中的泛型编程:高级用法与技巧

      vue3-组合式API

      函数与二元关系在编程中的应用

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 权益商城
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 权益商城
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号