「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.mzph.cn/news/189821.shtml

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

相关文章

优化问题,详解静态优化

优化问题,尤其静态优化问题,在控制系统设计中随处可见,例如基于燃油经济性和驾驶体验的多目标优化的汽车发动机 MAP 标定,基于性能指标优化的飞行器结构设计参数优化,以实验数据与模型输出匹配为目标的电池 RC 等效电路…

Python基础学习快速入门

文章目录 Number变量String字符串Def函数Class类List列表Tuple元组Dictionary字典Set集合值与引用类型if条件控制Loop循环 Number变量 python直接赋值,不需要定义变量类型。不需要**,逗号结尾符 使用print**直接进行输出 #赋值 a 1.0 print(a)a 7 print(a)p…

带删除的并查集

Almost Union-Find 支持三种操作 合并 x x x和 y y y所在的集合把 x x x移到 y y y所在的集合求 x x x所在的集合的元素个数和元素之和 操作1和3是基本的并查集的操作. 关键在于操作 2 2 2: 若使用朴素的并查集,把节点 1 1 1合并到 3 3 3所在的集合,会…

ES-ELSER 如何在内网中离线导入ES官方的稀疏向量模型(国内网络环境下操作方法)

ES官方训练了稀疏向量模型,用来支持语义检索。(目前该模型只支持英文) 最好是以离线的方式安装。在线的方式,在国内下载也麻烦,下载速度也慢。还不如用离线的方式。对于一般的生产环境,基本上也是网络隔离的…

SQL注入漏洞的检测及防御方法

SQL注入(SQL Injection)是一种广泛存在于Web应用程序中的严重安全漏洞,它允许攻击者在不得到授权的情况下访问、修改或删除数据库中的数据。这是一种常见的攻击方式,因此数据库开发者、Web开发者和安全专业人员需要了解它&#xf…

盘点68个Android游戏Game源码安卓爱好者不容错过

盘点68个Android游戏Game源码安卓爱好者不容错过 学习知识费力气,收集整理更不易。 知识付费甚欢喜,为咱码农谋福利。 Game下载链接:https://pan.baidu.com/s/1hWnuttrqTfwDKYvuVMuSwQ?pwd8888 提取码:8888 项目名称 2048…

混合使用Windows和Linux子系统的工具和命令

文章目录 在Windows中运行Linux命令使用PowerShell混合使用Linux和Windows命令通过power shell在Windows混合使用Linux工具在Linux中混合使用Windows 工具 推荐阅读 Windows和Linux的工具和命令可以通过WSL互换使用。 可以在Linux子系统中运行Windows命令,也可以在W…

LabVIEW在调用image.cpp或drawmgr.cpp因为DAbort而崩溃

LabVIEW在调用image.cpp或drawmgr.cpp因为DAbort而崩溃 出现下列问题,如何解决? 1. LabVIEW 程序因image.cpp或drawmgr.cpp中的错误而崩溃 2. 正在通过cRIO-9034运行独立的LabVIEW应用程序,但它因drawmgr.cpp中的错误而崩溃 …

SSM项目实战-POJO设计

1、schedule_db.sql CREATE DATABASE schedule_db CHARACTER SET utf8 ;USE schedule_db;CREATE TABLE sys_schedule (sid int NOT NULL AUTO_INCREMENT COMMENT 日程id,uid int DEFAULT NULL COMMENT 用户id,title varchar(50) DEFAULT NULL COMMENT 标题,completed int DEFAU…

Python 中的 FileSystem Connector:打通文件系统的便捷通道

更多Python学习内容:ipengtao.com 大家好,我是涛哥,今天为大家分享 Python 中的 FileSystem Connector:打通文件系统的便捷通道,全文4100字,阅读大约11分钟。 在现代软件开发中,文件系统是不可或…

1+x网络系统建设与运维(中级)-练习题

一.给设备重命名 同理可得&#xff0c;所有交换机和路由器都用一下命令配置 <Huawei>sys [Huawei]sysn LSW1 二.配置VLAN LSW1&#xff1a; [LSW1]vlan batch 10 20 [LSW1]int e0/0/1 [LSW1-Ethernet0/0/1]port link-type access [LSW1-Ethernet0/0/1]port default vlan…

Leetcode 剑指 Offer II 055. 二叉搜索树迭代器

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 实现一个二叉搜索树迭代器类 BSTIterator &#xff0c;表示一个按…

Linux破解用户密码【基于redhat9】

Linux破解用户密码【基于redhat9】 操作步骤&#xff1a; 重启虚拟机&#xff0c;选择第二行&#xff0c;按下e键在倒数第二行的末尾加入 rd.break,按下ctrlx键&#xff0c;进入终端界面重新挂载/sysroot为读写切换到bash修改用户密码创建 /.autorelabel 文件使SELinux安全策略…

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

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

深入理解Docker容器核心技术

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

【数电笔记】逻辑代数的基本定律、常用公式

目录 说明&#xff1a; 逻辑代数的基本定律 1. 常量间的运算 2. 逻辑变量与常量的运算 3. 与普通代数相似的定律 4. 摩根定律&#xff08;反演律&#xff09; 5. 等式证明方法例题 逻辑代数的常用公式 1. 吸收律 2. 冗余律 3. 示例应用 4. 关于异或运算的一些公式 …

Leetcode—2661.找出叠涂元素【中等】

2023每日刷题&#xff08;四十六&#xff09; Leetcode—2661.找出叠涂元素 题意解读 题目意思就是&#xff0c;按照arr数组从左到右的顺序遍历各个arr[i]&#xff0c;涂抹这个值在矩阵中对应位置的网格&#xff0c;一旦你发现它所在的行或者列满员了&#xff0c;就返回这个i…

AT89S52单片机------中断系统

目录 单片机的内部结构 中断请求标志寄存器 (1)TCON寄存器 (2)SCON寄存器 (3)定时器2的控制寄存器T2CON 中断允许与中断优先级的控制寄存器 中断允许寄存器IE 中断优先级寄存器IP 响应中断请求的条件 外部中断响应时间 外部中断的触发方式选择 中断请求的撤销 1.定…

Tomcat目录介绍

目录 1 Tomcat主目录介绍 2 webapps目录介绍 3 Tomcat配置文件目录介绍&#xff08;conf&#xff09; 1 Tomcat主目录介绍 进入Tomcat目录下&#xff0c;我的目录是/application/tomcat/ cd /application/tomcat/ 安装tree命令 yum -y install tree tree -L 1 tree&…

智慧农田可视化大数据综合管理平台方案,EasyCVR助力农业高质量发展

一、背景需求 我国是农业大国&#xff0c;农业耕地面积达到20亿亩。随着物联网、大数据、人工智能等新一代信息技术与农业农村加速融合&#xff0c;以及国家对农业的重视&#xff0c;智慧农业对于我国农业现代化建设和实施乡村振兴战略具有重大引领与推动作用。在传统农田生产…