「C++」类和对象2

🎇个人主页:Ice_Sugar_7
🎇所属专栏:C++启航
🎇欢迎点赞收藏加关注哦!

文章目录

  • 🍉前言
  • 🍉构造函数
    • 🍌参数
    • 🍌默认构造函数
      • 🥝两种类型
      • 🥝编译器生成的默认构造函数(附调试小技巧)
  • 🍉析构函数
    • 🍌默认析构函数
      • 🥝调用
  • 🍉拷贝构造函数
    • 🍌无穷递归
    • 🍌浅拷贝
    • 🍌构造与拷贝构造
  • 🍉运算符重载
    • 🍌一元运算符重载
    • 🍌二元运算符重载
    • 🍌运算符重载和函数重载的关系
    • 🍌全局运算符重载
  • 🍉赋值重载函数
    • 🍌默认赋值重载函数
  • 🍉两个取地址重载函数
    • 🍌const与非const
  • 🍉写在最后

🍉前言

C++中,为了方便操作对象,引入了六个默认成员函数,它们默认就有的,即使类为空,它们也是实际存在的。这六大函数各有特色,规则也与与常规函数有所不同,建议不要以理解常规函数的方式来理解这六大函数,不然你学起来会很难受的

🍉构造函数

你写好了一个栈,在定义一个变量之后就直接去进行入栈操作,由于你没初始化,top和capacity都为空,很可能导致程序崩溃。
没有初始化的后果挺严重的,但是老是会被忘记,于是C++中引进构造函数,可以帮你初始化,来看看它是怎么初始化的。

class Date {
public:Date(int year = 2023, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};

可以看到:构造函数的函数名与类名相同,没有返回值
注意:没有返回值指的是函数返回值的位置什么都不用写,不要写成void

构造函数在创建类类型对象时由编译器自动调用,用来保证每个数据成员都有一个合适的初始值,而且在对象整个生命周期内只会调用一次
(虽然叫作构造,但是它并不创造什么东西,只是对对象成员进行初始化)

构造函数也支持重载,不过默认构造函数只有一个

上面说过,构造函数会自动调用,进行初始化

🍌参数

如果构造函数没有参数的话,那你在调用的时候,后面不能加括号(),因为这样会与函数声明混淆,所以规定不加了。带参的话就和正常函数一样写就ok了

🍌默认构造函数

如果一个构造函数不带参数或者全缺省,也就是说不传参就能调用的话,它就成了默认构造函数

🥝两种类型

编译器会将变量识别为两种类型:内置类型(如char、int…)和自定义类型
而对于自定义类型的指针,它也是内置类型,因为它只是指向自定义类型,但本质还是一个指针变量

🥝编译器生成的默认构造函数(附调试小技巧)

如果你没写默认构造函数,那么编译器会自动生成一个默认构造函数(有写的话就不会生成了),它会初始化自定义类型的变量(调用这个成员的默认构造函数,如果没有,那编译器就会生成该成员的默认构造函数),而对于内置类型变量,有的编译器会进行初始化(vs2019、vs2022会),有的不会,也就是说不确定是否会初始化,建议一般当成没有初始化

总结一下,一般情况下都要自己写构造函数,除非所有成员都是自定义类型,或者给了缺省值。你不写的话就自动用系统的默认构造函数,不过系统自带的有点不靠谱

不过像这样,有的变量会初始化,而有的不进行初始化确实很不好,所以C++11支持声明时给缺省值,用缺省值对内置类型进行初始化,完善编译器自带的默认构造函数的功能(注意:此时仍然是声明而非定义

class Date {
public://...
private:int _year = 2023;int _month = 1;int _day = 1;
};

学到这里,可以知道默认构造函数有三种:

①没有显式定义默认构造函数时,编译器默认生成的构造函数;
②无参构造函数也可以叫做默认构造
③全缺省也可以叫默认构造

不传参数就调用构造,都可以叫默认构造

注意:这三个不能同时存在,因为它们都可以无参调用,同时存在的话调用会产生歧义

另外补充一个调试的小技巧:在调试时想查看对象里面的成员,在监视窗口输入this就可以全部看到了,不用去一个一个敲了。


🍉析构函数

如果你信誓旦旦地说“我肯定不会忘记初始化的”,那么我猜你肯定还是会忘记销毁的。(doge)
与构造函数相对,析构函数,类似我们自己写的destroy函数,它的特征其实和构造函数差不多:

①函数名就是类名前面加上~
②没有参数(说是说没有参数,但实际上参数部分自带一个this),也没有返回值类型。因此,它不支持重载(也就是说你自己写的析构函数只有一个)
③一个类只能有一个析构函数。若未显式定义,那么系统会自动生成默认的析构函数

析构函数清理的是对象当中动态开辟的空间(也就是用free把它们给释放掉,不然会导致内存泄漏),而非对象本身。对象及其他非静态局部变量的空间都是编译器建立栈帧时开的。这些空间在作用域结束后也跟着销毁了。
下面是一个栈的析构函数:

class Stack {
public:~Stack() {free(array);array = nullptr;}private:int* array;int top;int capacity;
};

🍌默认析构函数

刚才上面说过,你自己不能写多个析构函数,只能写一个,那其实这个也就是默认析构函数了
如果你不写,那编译器也会自动给你生成一个。
默认生成的析构函数,它的行为和构造函数相似,也是不处理内置类型,而对于自定义类型,则会去调用该类型的析构函数
如果对象的成员都是内置类型,且不包括指针,那你靠编译器生成的这个就ok了,因为像是int a、char b这些其实没有清理的必要。真正需要清理的是动态内存开辟的空间

🥝调用

默认析构函数会在对象销毁时自动调用
如果在一个函数中,那么函数栈帧销毁时就会调用。
(如果是在主函数中,也是等到主函数结束时才调用,但注意这里的“结束”并不是真正的结束,此时会调用所有全局变量和静态变量的析构函数,直到所有全局变量和静态变量的析构函数完成后,程序才算真正结束)


🍉拷贝构造函数

拷贝构造函数长得和构造函数差不多,不过它只有一个参数(算上this就是两个)

Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day;
}
int main() {Date d1;d1.Print();Date d2(2023,11,28);d2.Print();Date d3(d1);return 0;
}

这里就是把对象d1的成员拷贝给this,this指向实例化的对象,即d3。相比于构造函数,拷贝构造只需在构造函数后面加上拷贝对象就ok了

自定义类型变量传值调用(值拷贝),先通过拷贝构造函数进行拷贝,然后才调用函数

编译器生成的默认拷贝构造函数,对内置类型完成值拷贝,对自定义类型,调用其拷贝构造函数

对象成员若全为内置类型,那直接值拷贝没有问题;不过如果有自定义类型,这时候还值拷贝,就会导致无穷递归了

🍌无穷递归

在这里插入图片描述

d1传参,得先生成一份临时拷贝,然后传给形参,这就要调用拷贝构造函数,我们假设这份临时拷贝叫d4,那就有Date d4(d1),但是为了生成d4这份拷贝,d1又得再传参,就得再生成一份临时拷贝,假设这份拷贝叫d5,那就要Date d5(d1)……这样下去子子孙孙无穷匮也,陷入死循环了

所以传值是不行滴,得传引用(因为你要拷贝,拷贝的对象已经存在),为防止拷贝对象被更改,记得加个const修饰

Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day;
}

🍌浅拷贝

上面讲的无穷递归便是浅拷贝带来的问题,除此之外,指针如果浅拷贝的话会出现共用空间的情况,会导致同一块空间多次析构的问题。
比如对于一个栈(如下图)
在这里插入图片描述
因为是浅拷贝,所以d1和d2的int*的值是一样的,说明指向同一个数组。当d1的生命周期结束后,自动调用析构函数清理这块空间,然后d2同样调用析构函数,会free释放相同的空间,后果很严重。
(如果free函数释放的指针不是通过malloc等函数动态申请的内存空间,或者已经释放过了,会导致不可预料的后果,比如程序崩溃或者产生非法操作,因为系统无法判断这块内存是否可以被释放。)

除了重复析构之外,如果d2通过指针向该空间存储数据的话,可能会把d1的数据覆盖掉

🍌构造与拷贝构造

写拷贝构造函数的前提是已经存在构造函数。如果只写拷贝构造函数的话,那这个类根本就没法创建对象(因为定义对象和初始化对象这两步是绑定在一起的)


🍉运算符重载

在讲第四个默认函数——赋值重载函数之前,得先了解下运算符重载
C++为了增强代码的可读性引入了运算符重载,它使得自定义类型可以直接使用运算符
运算符重载是具有特殊函数名的函数,既然是函数,那就有返回值、函数名和参数列表,它的返回值和参数列表与普通函数差不多
有区别的点在于它的函数名,函数名为:关键字operator后面接需要重载的运算符符号比如想比较两个自定义类型是否相等,那就是operator=,具体名字要根据我们的需求起的,而且要有实际意义
注意:

①不能重载非运算符。如operator@
②重载操作符必须有一个类类型的参数
③运算符重载如果作为类的成员函数,那么它的形参会比操作数少1个,因为形参部分有一个隐藏的this
.*::(域作用限定符)sizeof? :(三目操作符).(访问成员操作符)这五个运算符不能重载(笔试题常考)

🍌一元运算符重载

一元运算符分为前置和后置两种形式,比如++a和a++,在参数位置分别用无参数和一个int参数来区分前置和后置

Date operator++();  //前置
Date operator++(int); //后置

🍌二元运算符重载

类的对象默认占据第一个位置(this),所以对象只能作为左操作数

🍌运算符重载和函数重载的关系

二者毫无关系,只是称呼相近而已

🍌全局运算符重载

运算符重载如果是全局的,那么我们就需要让成员变量都是公有的,那这样的话该如何保证封装性呢?
有两个办法:

①使用友元函数
在函数声明前加上friend就是友元函数了,这样类外的函数定义就可以使用类的成员了,下篇文章中将详细讲解
②直接在类中重载为成员函数


🍉赋值重载函数

了解运算符重载的机制之后,学习赋值重载就很简单了
如果现在有一个对象d1,我们想创建和它一样的对象d2,那就用拷贝构造函数Date d2(d1),但如果d1和d2都已经存在,那只要赋值拷贝就行了

Date& operator=(const Date& d)  //加const防止不小心修改了右值{if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;  //传引用返回,提高返回效率}

返回值设为Date&而非void的目的是确保有返回值,以支持连续赋值

🍌默认赋值重载函数

前面讲到的构造和析构函数都有编译器生成的默认函数,而赋值重载它也有默认函数,这个函数对于内置类型采用值拷贝,对于自定义类型会调用它的赋值函数

对于日期类这种类型,显然值拷贝就够用了,但如果对于栈这种需要动态开辟内存的类就不行了(值拷贝会发生重复析构的问题),这时候就要进行深拷贝

然后有一个重要的结论:因为类中不显式实现赋值运算符重载的话,编译器会生成默认的赋值重载,此时你在类外实现一个全局的赋值运算符重载就会和默认生成的产生冲突,所以赋值运算符只能重载成类的成员函数,不能重载成全局函数


🍉两个取地址重载函数

这两个函数指的是普通对象取地址重载函数和const对象取地址重载函数,由于编译器生成的默认函数能覆盖大部分使用场景,所以一般很少自己实现这两个函数
普通对象的取地址和结构体的取地址差不多,重点来讲const对象
我们将const修饰的成员函数称为const成员函数,const修饰类的成员函数,实际修饰的是该成员函数隐藏的this指针,表明在这个成员函数中不能对类的任何成员进行修改。

	 const Date* operator&()const {  //返回值类型为const Date*;修饰this的const加在参数部分的括号后面return this;}

(这里只是示例,一般不用自己实现)

🍌const与非const

const对象可以调用非const成员函数吗?
非const对象可以调用const成员函数吗?
const成员函数内可以调用其它非const成员函数吗?
非const成员函数内可以调用其它const成员函数吗?

解决这些问题的关键在于分析权限的变化,即权限是被放大还是被缩小,亦或是不变(权限平移)。记住权限只能缩小或平移,不能被放大
先看第一个问题,const对象权限小,而成员函数的this没有const修饰,权限比较大,所以不能调用
第二个问题你就自己分析咯,答案是可以
第三、四问,const成员函数一定不会修改对象的状态,所以可以放心调用非const成员函数;而非const成员函数可能修改成员状态,所以不能调用const成员函数


🍉写在最后

以上就是本篇文章的全部内容,如果你觉得本文对你有所帮助的话,那不妨点个小小的赞哦!(比心)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++基础 -28- 友元

友元用于访问类中的所有数据成员 类中的私有成员,类外不可访问 定义友元的格式(友元函数必须要在类内,声明) friend void show(person &b); 使用友元访问类的所有成员 #include "iostream"using namespace std…

深入理解Docker容器核心技术

文章目录 1. Linux命名空间(Namespaces)1.1 示例:PID命名空间 2. 控制组(cgroups)2.1 示例:内存控制组 3. 联合文件系统(UnionFS)3.1 示例:查看镜像的分层结构 4. Docker…

二、ZooKeeper集群搭建

目录 1、概述 2、安装 2.1 第一步:下载zookeeeper压缩包 2.2 第二步:解压 ​​​​​​​2.3 第三步:修改配置文件 ​​​​​​​2.4 第四步:添加myid配置 ​​​​​​​2.5 第五步:安装包分发并修改myid的值…

2023_Spark_实验二十四:SparkStreaming读取Kafka数据源:使用Direct方式

SparkStreaming读取Kafka数据源:使用Direct方式 一、前提工作 安装了zookeeper 安装了Kafka 实验环境:kafka zookeeper spark 实验流程 二、实验内容 实验要求:实现的从kafka读取实现wordcount程序 启动zookeeper zk.sh start# zk.sh…

【方案】智慧林业:如何基于EasyCVR视频能力搭建智能林业监控系统

随着人类进程的发展。城市化范围的扩大,森林覆盖率越来越低,为保障地球环境,保护人类生存的净土,森林的保护与监管迫在眉睫。TSINGSEE青犀智慧林业智能视频监控系统方案的设计,旨在利用现代科技手段提高林业管理的效率…

2023年AI时代中小企业智能化发展报告

今天分享的是AI系列深度研究报告:《2023年AI时代中小企业智能化发展报告》。 (报告出品方:创业邦) 报告共计:47页 AI——中小企业的智能化增长利器 继蒸汽机、电气化、信息化时代之后,由第四次工业革命开…

[HTML]Web前端开发技术6(HTML5、CSS3、JavaScript )DIV与SPAN,盒模型,Overflow——喵喵画网页

希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,佬佬会看到更多有趣的博客哦!!! 喵喵喵,你对我真的…

【U8+】用友U8修改凭证提示:外部凭证在总账系统中不能修改。

【问题描述】 用友U8中,在总账模块中修改凭证的时候,提示: 外部凭证在总账系统中不能修改。 【原因分析】 在软件中,使用其他模块的情况下, 其他模块生成的凭证都会传到总账模块中,进而这些由其他模块生成…

【开源】基于JAVA语言的考研专业课程管理系统

项目编号: S 035 ,文末获取源码。 \color{red}{项目编号:S035,文末获取源码。} 项目编号:S035,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 考研高校模块2.3 高…

Tkinter 面向对象框架《二》

一、说明 Tkinter 教程 开发完整的 Tkinter 面向对象应用程序开发完整的 Tkinter 面向对象应用程序。 即使OOP的高手,也未必对面向对象全部掌握。至于 Tkinter的OOP编程,其实高手们也是在摸索实践中。 为了面向对象和Tkinter参与本教程。如果你来这里纯…

测评补单助力亚马逊,速卖通,国际站卖家抢占市场,提升转化和评分

想要快速提升商品的销量,测评补单这种方法见效是最快的。特别是新品上线,缺少用户评价,转化率不好,很多商家新品上线都会做测评补单,搞些商品好评,不但可以提升转化,同时在平台也可以获得更多展…

向光有光megauging使用说明书(二,轻量级的visionpro)

测试程序暂时支持80万(包含1024*768)以上的gige工业相机 我们程序中使用注意力机制,其实就是感兴趣区域(roi,你看过我前面博文,就应该明白)精神的延伸,我们只处理全景图中的1024*76…