「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成员函数


🍉写在最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式---第三篇

系列文章目录 文章目录 系列文章目录前言一、模板方法模式二、知道享元模式吗?三、享元模式和单例模式的区别?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一…

mac修改默认shell为bash

1. 打开系统偏好设置 2. 点击用户群组 3. 按住ctrl,点击用户名 4. 点击高级选项,修改登录shell 参考:在 Mac 上将 zsh 用作默认 Shell - 官方 Apple 支持 (中国)

js 搜索记录

背景: 移动端的搜索记录,不可能通过调取接口来记录瑟,所以通过在某某.js一个文件定义和处理逻辑。 代码: //某某.js var yumingSearch {init: function () {initF7.GloblalF7.onPageInit("yumingSearch", function …

go开发之个微机器人的二次开发

简要描述: 下载消息中的语音 请求URL: http://域名地址/getMsgVoice 请求方式: POST 请求头Headers: Content-Type:application/jsonAuthorization:login接口返回 参数: 参数名必选类型…

Tkinter 面向对象框架《二》

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

三、Zookeeper数据模型

目录 1、Znode兼具文件和目录两种特点 2、Znode具有原子性操作 3、Znode存储数据大小有限制 4、Znode通过路径引用 如下图中的每个节点称为一个Znode, 每个Znode由3部分组成: ZooKeeper的数据模型,在结构上和标准文件系统的非常相似,拥有…

力扣15题 三数之和 双指针算法

15. 三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三…

013 OpenCV copyMakeBorder(padding)

目录 一、环境 二、原理 三、完整代码 一、环境 本文使用环境为: Windows10Python 3.9.17opencv-python 4.8.0.74 二、原理 cv.copyMakeBorder是OpenCV库中的一个函数,用于在图像周围添加边框(padding)。这个函数可以用于图…

Mongodb 开启oplog,java监听oplog并写入关系型数据库

开启Oplog windows mongodb bin目录下找到配置文件/bin/mongod.cfg,配置如下: replication:replSetName: localoplogSizeMB: 1024双击mongo.exe 执行 rs.initiate({_id: "local", members: [{_id: 0, host: "localhost:27017"}]})若出现如…

人工智能|机器学习——感知器算法原理与python实现

感知器算法是一种可以直接得到线性判别函数的线性分类方法,它是基于样本线性可分的要求下使用的。 一、线性可分与线性不可分 为了方便讨论,我们蒋样本增加了以为常数,得到增广样向量 y(1;;;...;),则n个样本的集合为&a…

[论文精读]利用大语言模型对扩散模型进行自我修正

本博客是一篇最新论文的精读,论文为UC伯克利大学相关研究者新近(2023.11.27)在arxiv上上传的《Self-correcting LLM-controlled Diffusion Models》 。 内容提要: 现有的基于扩散的文本到图像生成模型在生成与复杂提示精确对齐的图像时仍然存在困难,尤其是需要数值和…

ansible模块

目录 一、ansible的command模块 1.ad-hoc 2.playbook 3.command模块 二、ansible的shell模块 1.shell模块帮助 2.shell模块支持的参数和解释 3.简单试验 4.批量远程执行脚本 三、script模块 1.script模块帮助 2.shell模块支持的参数和解释 3.实践 四、ansible文件…

【pytorch】从yolo的make_grid理解torch.meshgrid、torch.stack

文章目录 简述1、torch.meshgrid 创建行列坐标2、torch.stack 结合行列坐标3、通过view函数扩展维度 简述 yolo检测 make_grid创建网格代码如下,那么什么是torch.meshgrid? def _make_grid(nx20, ny20):yv, xv torch.meshgrid([torch.arange(ny), torch.arange(…

C++基础 -28- 友元

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

深入Spring Security魔幻山谷-获取认证机制核心原理讲解(新版)

文/朱季谦 这是一个古老的传说。 在神秘的Web系统世界里,有一座名为Spring Security的山谷,它高耸入云,蔓延千里,鸟飞不过,兽攀不了。这座山谷只有一条逼仄的道路可通。然而,若要通过这条道路前往另一头的…

html实现各种好看的鼠标滑过图片特效模板

文章目录 1.鼠标悬浮效果1.1 渐动效果1.2 渐变效果1.3 边框效果1.4 线行效果1.5 图标效果1.6 块状效果1.7 边线效果1.8 放大效果1.9 渐出效果1.10 痕迹效果1.11 交叉效果1.12 着重效果1.13 详展效果1.14 能动效果1.15 明细效果 2.主要源码2.1 源代码 源码下载 作者:…

基于python的FMCW雷达工作原理仿真

这篇文章将介绍如何使用python来实现FMCW工作原理的仿真,第1章内容将介绍距离检测原理,第2章内容会介绍速度检测原理。 第1章 第1部分: 距离检测原理 调制的连续波雷达通常也被叫做调频连续波(FMCW)雷达是一个使用频率调制来测量…

鸿蒙(HarmonyOS)应用开发——容器组件(Grid组件)

前言 前面一篇文章中,已经说了List组件。那么接下来就是容器组件中的Grid组件 #mermaid-svg-oz1b7w45ASmMlZFa {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oz1b7w45ASmMlZFa .error-icon{fill:#5522…

Docker-compose的在线与离线安装方式及问题解决

文章目录 一、在线方式1、GitHub2、daocloud.io 二、离线方式(推荐)三、验证 一、在线方式 1、GitHub curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/d…

springboot缓存技术-Ehcache-Redis-memcached

springboot缓存技术-Ehcache-Redis-memcached 文章目录 springboot缓存技术-Ehcache-Redis-memcachedspring缓存使用方式手机验证码案例缓存供应商变更Ehcache变更缓存供应商Redis缓存供应商变更memcached下载安装memcachedSpringBoot整合memcached spring缓存使用方式 导缓存…