【C++】类和对象(中)--上篇

在这里插入图片描述
个人主页~
类和对象上


类和对象

  • 一、类的六个默认成员函数
  • 二、构造函数
    • 1、构造函数基本概念
    • 2、构造函数的特性
  • 三、析构函数
    • 1、析构函数的概念
    • 2、特性
  • 四、拷贝构造函数
    • 1、拷贝构造函数的概念
    • 2、特征

一、类的六个默认成员函数

如果有个类中什么成员都没有,那么被称为空类

由编译器自动生成的成员函数称为默认成员函数

空类中会自动生成六个默认成员函数,这六个默认成员函数在每个类中都会自动生成

①初始化功能的构造函数
②清理功能的析构函数
③使用同类对象初始化创建对象的拷贝构造
④把一个对象赋值给另一个对象的赋值重载
⑤对普通对象取地址重载
⑥对const对象取地址重载

这六个默认成员函数主要将操作对象分为内置类型自定义类型,对二者有不同的操作

二、构造函数

1、构造函数基本概念

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

2、构造函数的特性

构造函数是特殊的成员函数,主要任务就是初始化对象
(1)函数名与类名相同
(2)无返回值
(3)对象实例化时编译器自动调用
(4)可以重载

class Date
{
public:
	//无参构造函数
	Date()
	{}
	//带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

无参调用不用加括号:

//无参调用
Date d1;
//有参调用
Date d2(2024,6,22);

(5)如果类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,如果有显式定义,编译器将不再生成

class Date
{
public:
	//无参构造函数
	Date()
	{}
private:
	int _year;
	int _month;
	int _day;
};

在这里插入图片描述
由编译器自己生成:
在这里插入图片描述
编译器会自动生成一个无参的默认构造函数,初始化给的是随机值
(6)行文至此有人会觉得编译器给的无参的默认构造函数很垃圾,初始化给的是随机值,没有什么意义,他对于自定义类型来说是有很大的意义的,它可以调用自定义类型的默认构造函数

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	int _year;
	int _month;
	int _day;

	Time _t;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述

内置成员变量在类中声明时可以给默认值

class Date
{
private:
	int _year = 1970;
	int _month = 1;
	int _day = 1;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述
(7)无参的构造函数、全缺省的构造函数、不写而编译器自动生成的构造函数都叫默认构造函数

全缺省:

class Date
{
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述

三、析构函数

1、析构函数的概念

析构函数是与构造函数功能相反的一个函数,对象在销毁时会自动调用析构函数,完成资源清理

2、特性

(1)析构函数名是在类名前加上字符~
(2)无参数无返回类型
(3)一个类只能有一个析构函数,未显式定义则自动生成
(4)生命周期结束时自动调用

构造函数+析构函数改造栈:

class Stack
{
public://公共访问,但在类中可以访问private的内容,只是在类外不能直接访问
	Stack(size_t newcapacity = 4)//缺省
	{
		int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
		if (tmp == nullptr)
		{
			perror("realloc fail");
			exit(-1);
		}
		a = tmp;
		capacity = newcapacity;
		top = 0;
	}
	void Push(int x)
	{
		a[top++] = x;
	}
	void Pop()
	{
		top--;
	}
	int Top()
	{
		return a[top - 1];
	}
	bool Empty()
	{
		return top == 0;
	}
	~Stack()
	{
		free(a);
		a = nullptr;
		capacity = top = 0;
	}
	//自己写的析构函数,在程序执行的最后执行
private://隐私访问
	int* a;
	int top;
	int capacity;
};

编译器自动生成的析构函数不能对内置类型进行资源回收,但可以调用自定义类型的析构函数

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}//Time的析构函数
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	int _year = 1970;
	int _month = 1;
	int _day = 1;

	Time _t;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述

因为d中包含着四个成员变量,除了_year _month _day 外还有一个Time类,内置类型成员在销毁时不被资源清理,但自定义类型需要调用析构函数回收,但是main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,编译器默认生成析构函数的可以对自定义成员Time调用它的析构函数,即当Date销毁时,Time也会销毁

如果类中没有申请资源时,也就是没有在堆上申请空间时,析构函数可以不写,直接使用编译器生成的默认析构函数,有申请资源的话一定要写,防止资源泄露

四、拷贝构造函数

1、拷贝构造函数的概念

只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建对象时,由编译器自动调用

2、特征

(1)拷贝构造函数是构造函数的一个重载形式
(2)拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,原因是会引发无穷递归调用

传引用调用:

class Date
{
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	//函数名与析构函数一样,都是类名,所以拷贝构造函数是构造函数的一个重载形式
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

在这里插入图片描述
传值调用:
在这里插入图片描述
我们知道传值调用形参会开辟一块空间,成为实参的临时拷贝,这样会创建一个Date,因为类会自动调用里面的六个默认成员函数,拷贝构造函数也是其中之一,这样一来,又会创建一个Date,以此类推,无限循环
(3)若未显式定义,编译器会生成默认的拷贝构造函数,这个默认的拷贝构造函数是值拷贝

在编译器生成的默认拷贝函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time(const Time & t)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

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

int main()
{
	Date d1;
	Date d2(d1);//这里是用已有的d1拷贝构造d2
	return 0;
}

在这里插入图片描述
在这里插入图片描述
(4)编译器默认生成的拷贝构造函数可以拷贝像Date类这样的类,但对于某些类来说我们要显式定义

class Stack
{
public:
	Stack(size_t newcapacity = 4)
	{
		int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
		if (tmp == nullptr)
		{
			perror("realloc fail");
			exit(-1);
		}
		a = tmp;
		capacity = newcapacity;
		top = 0;
	}
	void Push(int x)
	{
		a[top++] = x;
	}
	void Pop()
	{
		top--;
	}
	int Top()
	{
		return a[top - 1];
	}
	bool Empty()
	{
		return top == 0;
	}
	~Stack()
	{
		free(a);
		a = nullptr;
		capacity = top = 0;
	}
private:
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	Stack s2(s1);
	return 0;
}

该程序中没有显式定义拷贝构造函数吗,是调用的编译器自动生成的拷贝构造函数
在这里插入图片描述
在执行析构函数的时候出现了错误,这里的原因是数组a已经被释放了,再次释放产生错误

因为编译器自动生成的拷贝构造函数是值拷贝,所以在生成s2时,s2中的指针a指向的数组与s1中的指针指向的数组相同,在程序结束时,调用析构函数释放了s2,对应的这块数组空间也被释放,然后调用析构函数释放s1,已经被释放的空间不能被再次释放,所以出现了这样的错误,所以我们需要自己显式定义一个拷贝构造函数

深拷贝:

Stack(const Stack& s)
{
	cout << "Stack(const Stack& s)" << endl;
	int* a = (int*)malloc(sizeof(int) * capacity);
	if (a == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}
	memcpy(a, s.a, sizeof(int) * s.top);
	top = s.top;
	capacity = s.capacity;
}

在这里插入图片描述
(5)拷贝构造函数的使用场景:

已存在的对象建立新对象

函数参数为类类型对象

函数返回值为类类型对象

class Date
{
public:
	Date(int year, int month, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	Date(const Date & d)
	{ 
		cout << "Date(const Date& d):" << this << endl;
	}
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

因为我所使用的编译器为VS2022,是一种新的编译器,会对程序有一定的优化
我们来分析一下,如果编译器不优化,打印出的结果是什么
①创建d1类,调用构造函数,打印"Date(int,int,int):"和d1的地址

②以d1拷贝构造形参d,调用拷贝构造函数,打印"Date(const Date& d):"和d的地址

③以d拷贝构造形参temp,调用拷贝构造函数,打印"Date(const Date& d):"和temp的地址(这一步在编译器中被优化

④返回temp时,会拷贝一份构造临时对象返回,调用拷贝构造函数,打印"Date(const Date& d):"和临时对象的地址

⑤依次调用递归函数销毁temp(这一步在编译器中被优化),d,临时对象,d1

在这里插入图片描述

传参时,能够进行引用传参的尽量使用引用传参,因为引用传参不需要再拷贝占用空间,提高程序运行效率


今日分享结束~

在这里插入图片描述

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

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

相关文章

Python从0到100(三十六):字符和字符集基础知识及其在Python中的应用

1. 字符和字符集概述 字符(Character)是构成书面语言的基本元素&#xff0c;它包括但不限于各国家的文字、标点符号、图形符号和数字。字符集(Character set)则是一个包含多个字符的系统&#xff0c;用于统一管理和编码不同的字符。 常见字符集 ASCII&#xff1a;最早的字符…

Truenas scale入坑

家里有一台刚上大学时配的电脑&#xff0c;看着无用武之地&#xff0c;又还能用&#xff0c;于是想那它来搞个私有云nas。 一、选择想要入的坑 一开始对这块没什么了解和概念&#xff0c;最早是在旧主机上安装了个Ubuntu&#xff0c;然后再安装CassOS小尝试了下。可能CassOS里…

【Apache Doris】周FAQ集锦:第 9 期

【Apache Doris】周FAQ集锦&#xff1a;第 9 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和…

eNSP-VLAN虚拟局域网

一、出现Vlan的原因 同一个局域网&#xff0c;使用交换机连接&#xff0c;虽然比集线器相连&#xff0c;降低了广播风暴&#xff0c;但是局域网主机数量够多的时候&#xff0c;正常的广播数据(arp、dhcp)也很影响网络性能&#xff0c;所以还需要进一步降低广播风暴——VLAN (图…

双指针算法:快速排序模拟实现

目录 1.思路解析 2&#xff1a;代码展示 1.思路解析 使用双指针pre和cur 指针cur用于检测符合条件的数据 cur和pre数据发生交换用于将符合条件的数据&#xff08;比key小&#xff09;向左扔 一轮循环结束时&#xff0c;以pre为分界点&#xff0c;除去key&#xff0c;pre左边的…

SpringMVC 的工作流程和详细解释

Spring MVC&#xff08;Model-View-Controller&#xff09;框架是基于经典的 MVC 设计模式构建的&#xff0c;用于开发 Web 应用程序。下面是 Spring Boot MVC 的工作流程和详细解释&#xff1a; 1.客户端发起请求 1.客户端&#xff08;通常是浏览器&#xff09;发起 HTTP 请求…

VUE Pinia状态持久化

效果 实现方法 插件&#xff1a;pinia-plugin-persistedstate 链接地址 具体操作 安装 npm i pinia-plugin-persistedstate 添加到 pinia 实例上 import { createPinia } from pinia import piniaPluginPersistedstate from pinia-plugin-persistedstateconst pinia cre…

自动化设备上位机设计 一

目录 一 设计原型 二 后台代码 一 设计原型 二 后台代码 namespace 自动化上位机设计 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){}} }namespace 自动化上位机设计 {partial class Fo…

PDM与ERP物料编码技术在产品设计中的区别与应用

01 概 述 产品是企业赖以生存的基础&#xff0c;产品数据是企业最基本的也是最重要的数据&#xff0c;产品数据存在于产品设计、采购、生产、销售、服务、库存管理等全过程中。通过对产品设计数据进行编码&#xff0c;并增加采购、库存、生产、制造等属性信息&#xff0c;可以…

2.5 C#视觉程序开发实例1----设计一个IO_Manager

2.5 C#视觉程序开发实例1----设计一个IO_Manager 第一步目标&#xff1a; 1 实现获取IO触发信号Trig0 2 能够实现程序切换 3 图像处理后能够输出一个脉冲 1 IO 引脚定义 1.1 输入信号定义 1.2 输出信号定义 2 IO时序图 2.1 触发时序 2.2 切换程序时序图 3 IO_Manager.cs …

Android系统集成和使用FFmpeg

文章目录 前言FFmpeg源码下载交叉编译NDK下载x264编译源码下载编译 FFmpeg编译脚本 AOSP继承FFmpeg 前言 原生AOSP中并未继承FFmpeg&#xff0c;所以要想在android上使用&#xff0c;需要自己编译集成。 FFmpeg源码下载 git clone https://git.ffmpeg.org/ffmpeg.git目前最新…

云桌面运维工程师

一 深信服驻场工程师 1 深信服AC、AF、AD、NGAF、WOC Atrust、WAF项目实施经验者优先考虑。 负责云桌面POC测试 部署和配置&#xff1a;设置云桌面基础设施&#xff0c;包括虚拟化平台、云桌面管理软件和相关组件。确保正确配置网络、存储和安全设置。 用户体验&#xff1…

oracle用户过期/设置无限期用户/ORA-28001:the password has expired

oracle默认情况下&#xff0c;新建的账户只有180天的有效期&#xff0c;在有效期到期前一周就会报警。而一旦过了有效期&#xff0c;账户就会被锁定无法登录。所以为了方便起见&#xff0c;要修改oracle用户的有效期为无限。 1.查看用户密码的有效期设置&#xff0c;一般默认的…

如何解决滑块验证码 | 最佳滑块拼图验证码解决方案

你是否曾经在遇到滑块验证码时感觉像一个拼图大师&#xff1f;那种需要将拼图块完美地匹配到槽位中以证明你是人类的验证码&#xff1f;我也曾多次遇到过这些棘手的测试&#xff0c;虽然有点挑战性&#xff0c;但它们也是网络安全世界的一个迷人一瞥。在本指南中&#xff0c;我…

能保存到相册的风景视频在哪下载?下载风景视频网站分享

在当今以视觉为核心的时代&#xff0c;高清美丽的风景视频不仅能够丰富我们的日常生活&#xff0c;还能提供心灵上的慰藉。无论是为了制作视频项目&#xff0c;还是仅仅想要珍藏一些精美的风景画面&#xff0c;获取高质量的风景视频素材显得尤为重要。许多人可能会问&#xff1…

班迪录屏(Bandicam)7.0下载以及安装教程

最近有小伙伴私信我&#xff0c;问我有没有好用的录屏工具&#xff0c;今天给大家分享一个我一直在使用的录屏工具&#xff0c;也是解锁了V1P版本&#xff0c;绿色版打开就可以使用~ Bandicam录屏&#xff08;PC&#xff09; Bandicam录屏是一款专为捕捉屏幕精彩瞬间而设计的…

使用 iconfont.ttf文件保存多个图标文件,并且像文字一样使用代码绘制出来

先看演示效果 这里的多个图标其实是存储在 iconfont.ttf文件中 这个文件里面的图标对应的编码 显示代码 void CMFCApplication3Dlg::OnBnClickedOk() {// 加载字体文件CString fontPath = _T("C:\\Users\\35497\\Desktop\\test\\MFCApplication3\\font\\iconfont.ttf&qu…

测试引擎模拟接口实战

在上一章的内容中&#xff0c;我简单介绍了整个微服务的各个子模块&#xff0c;还封装了一些工具类。 当然&#xff0c;若还没完成上次内容的也可以点击右侧的传送门------传送门 EngineApplication 在开发测试引擎模拟接口之前&#xff0c;还需要给xxx-engine创建一个Sprin…

langchain框架轻松实现本地RAG

一 什么是RAG? RAG&#xff08;Retrieval-Augmented Generation&#xff09;是一种结合了检索和生成模型的方法&#xff0c;主要用于解决序列到序列的任务&#xff0c;如问答、对话系统、文本摘要等。它的核心思想是通过从大量文档中检索相关信息&#xff0c;然后利用这些信息…