【C++】类与对象(中上)(难点部分)

目录

 💕1.类的默认成员函数

 💕2.构造函数 

 💕3.析构函数 

 💕4.缺省值 

 💕5.拷贝构造函数 


 

(最新更新时间——2025.1.14)

这世间没有绝境

只有对处境绝望的人

 💕1.类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。⼀个类,我们什么都不写的情况下编译器会默认⽣成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载不重要,我们稍微了解⼀下即可


 💕2.构造函数 

在C++中,构造函数是特殊的成员函数,这种函数的意义是在对象实例化时初始化对象,替代我们之前所写的Init函数,并且构造函数可以自动调用,构造函数⾃动调⽤的特点就完美的替代的了Init


构造函数该如何书写?


构造函数的特点:(可以不看,后面逐一讲解)

1. 函数名与类名相同。

2. 无返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)

3. 对象实例化时系统会⾃动调⽤对应的构造函数。

4. 构造函数可以重载,但不推荐,推荐构造函数全写成全缺省。

5. 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦用户显式定义编译器将不再⽣成。

6. ⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认⽣成那个叫默认构造,实际上⽆参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调用的构造就叫默认构造。

7. 我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。

8.对于类中的自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。


我们接下来会逐一讲解->:

A.首先注意构造函数的写法,函数名与类名相同,无返回值。这也是1,2点所讲的

B.对象实例化时系统会⾃动调⽤对应的构造函数,如图,我们在写student s1时,没有调用无参构造函数,但它也运行了无参构造函数,说明对象实例化时会自动调用对应的构造函数

同时,构造函数分为无参构造函数和有参构造函数,


C:构造函数可以重载,但不推荐,推荐构造函数全写成全缺省

我们如图所见,可以看到,s1报错了,为什么?其实这就是因为函数的重载,我们可以看到,在写构造函数时,我们创建了一个全缺省函数,这就会导致调用歧义,在对象实例化时,s1不知道该调用哪个构造函数,因为两个构造函数都可以不传参


D:如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦用户显式(也叫自己写)定义编译器将不再生成。

但是,说是会自动生成一个无参的构造函数,但是其实这个无参的构造函数对内置类型是不做处理的,什么意思?


编译器生成的构造函数不会对类中的内置类型/基本类型(int,char,…指针)处理

它只会对类中的自定义类型处理(class/struct)类型,什么意思,我们来分析下面的代码->:

 #include<iostream>
#include<stdlib.h>
using namespace std; 
//A类
class A
{
public:
	A()
	{
		_a = 0;
		cout << "A()" << ' ' << endl;
	}

private:
	int _a;
};
//Time类
class Time
{
private:
	int _hour;
	int _minute;
	int _second;

	A _aa;
};
//日期类
class Date
{
private:
	int _year;
	int _month;
	int _day;

	// 自定义类型调用默认构造函数
	Time _t;
};

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

我们首先自定义一个Date类对象d2,对象d2没有构造函数,那么系统就会自定义生成一个构造函数,系统自动生成的函数不会对类中的基本类型进行处理,但是会对自定义类型Time _t进行处理,这里的对自定义类型的处理就意味着会调用Time _t 的构造函数,那么对Time _t进行处理时,发现Time类也没有构造函数,那么Time类就会生成一个编译器自动生成的默认构造函数,Time类的默认构造函数并不会处理Time类的基本类型,而是去处理Time类中的自定义类型,也就是A_aa,处理A_aa时,就会调用A类的构造函数,发现A类中有构造函数,那么就不会生成默认构造函数,也就是调用我们所写的A类中的构造函数,也就会打印出字符串 "A()"


总结->:

编译器自动生成的构造函数:

对于内置类型成员变量,没有规定要不要处理(有些编译器会处理)

对于自定义类型的成员变量才会调用它的不传参就可以调用的函数(包括全缺省函数)


E : 无参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。如图->:

全缺省构造函数与无参构造函数不能同时存在,因为会存在调用歧义,除非你传一个值,而如果两者都不写编译器就会自动生成一个默认构造函数,所以三者不能同时存在


F:对于类中的自定义类型成员变量,编译器会优先调用这个成员变量的默认构造函数初始化

如图所见,先调用的是Time类的构造函数

 


 💕3.析构函数 

析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,⽐如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作

析构函数的特点:

1. 析构函数名是在类名前加上字符 ~

2. ⽆参数⽆返回值 (这⾥跟构造类似,也不需要加void)

3. ⼀个类只能有⼀个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数

4. 对象⽣命周期结束时,系统会⾃动调⽤析构函数

5. 跟构造函数类似,我们不写,编译器会自动生成析构函数,自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数

6. 还需要注意的是我们显示写的析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调⽤析构函数(与构造函数相同)


我们举一个代码的例子

#include<iostream>
#include<stdlib.h>
using namespace std;
class student
{	
public:
	~student()
	{
		if (pa != nullptr)
		{
			free(pa);
		}
	}
	int _age;
	int _number;
	int* pa;
};
int main()
{
	student s1;
	s1.pa = (int*)malloc(sizeof(100));
}

我们实例化对象s1,向s1中的pa开辟100个字节,然后再在类中写一个析构函数,这样在对象的生命周期结束时,编译器就会紧接着自动运行类中的析构函数,各位可以自己复制代码调试一下,即使我们不去主动调用它,编译器也会自动调用的


 💕4.缺省值 

在C++中,引入了缺省值的存在,缺省值的存在是为了弥补编译器自动生成的默认构造函数不会对内置类型初始化的补丁,代码如下->:

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

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 给缺省值
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d2(2024, 4, 9);
	d2.Print();

	return 0;
}

我们可以看到,在类的基本类型中我们给它进行了缺省值的存在,为什么叫缺省值不叫初始化呢?

因为类中的成员变量属于声明,并没有定义出实例化,所以在C++中规定为给缺省值而不是进行初始化


 💕5.拷贝构造函数 

拷贝构造函数的定义->:

1.拷贝构造函数是构造函数的一种重载

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

3.若未显示定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数按内存存储,按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

浅谈一下浅拷贝->:

浅拷贝是指在复制对象时,对于对象中的数据成员,基本数据类型会直接复制其值,而对于指针类型的数据成员,只会复制指针的值(即内存地址),而不会复制指针所指向的实际数据。

我们接下来进行逐一讲解->:

我们先举一个简单的构造函数例子->:

#include<iostream>
#include<stdlib.h>
using namespace std;
class Date
{
public:
	Date(int year = 2000,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "Date(int year = 2000,int month = 1,int day = 1)" << endl << _year << ' ' << _month << ' ' << _day << endl;
	}
    //拷贝构造函数
	Date(Date& s)
	{
		_year = s._year;
		_month = s._month;
		_day = s._day;
		cout <<"Date(Date& s)" <<endl<< _year << ' ' << _month << ' ' << _day << endl;
	}
public:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date s1(2025, 1, 14);
	Date s2(s1);
    Date s2 = s1;//这也是拷贝构造
}

为什么说拷贝构造函数是构造函数的一种重载,如代码所见,拷贝构造函数与构造函数的写法只有传入的值不同,拷贝构造函数需要传入对象的别名

在创造对象s2时,如果要调用拷贝构造函数,则编译器不会再进行s2的构造函数,也就是说直接调用拷贝构造函数而不去调用构造函数


提出一个疑问?编译器自动生成的拷贝构造函数是什么样的?


class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "Date(int year = 2000,int month = 1,int day = 1)" << endl << _year << ' ' << _month << ' ' << _day << endl;
	}
	//拷贝构造函数
	/*Date(Date& s)
	{
		_year = s._year;
		_month = s._month;
		_day = s._day;
		cout << "Date(Date& s)" << endl << _year << ' ' << _month << ' ' << _day << endl;
	}*/
public:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date s1(2025, 1, 14);
	Date s2(s1);
	cout << endl;
	s2._year = 2000;
	cout << s1._year << ' ' << s1._month << ' ' << s1._day << endl;//输出2025 1 14
	cout << s2._year << ' ' << s2._month << ' ' << s2._day << endl;//输出2000 1 14
	//s2的值的改变并不会改变s1的值,间接说明所以说明s2与s1的空间是不同的
}

我们这里把显现的拷贝构造函数注释掉了,那么编译器就会生成默认的拷贝构造函数,默认的拷贝函数依旧会对基本类型进行拷贝,也就是说明s2依旧是s1拷贝出来的,我们进行s2数据的改变,s1的数据不会变,这也说明了s2与s1的内存空间是不同的

第2个问题-:>:

为什么说拷贝构造函数的参数只有一个且必须是类类型对象的引用,如果使用传值方式编译器会直接报错,因为会引发无穷递归调用

我们可以作图来思考一下直接传对象的情况(代码如下->:)

class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "Date(int year = 2000,int month = 1,int day = 1)" << endl << _year << ' ' << _month << ' ' << _day << endl;
	}
	//拷贝构造函数直接传值(这里编译器会直接报错)
	Date(Date s)
	{
		_year = s._year;
		_month = s._month;
		_day = s._day;
		cout << "Date(Date& s)" << endl << _year << ' ' << _month << ' ' << _day << endl;
	}
public:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date s1(2025, 1, 14);
	Date s2(s1);
	cout << endl;
}

在C++中规定了,类类型的传值传参要优先调用该对象的拷贝构造函数,再去传给参,那么当我们不利用引用作为参数时,就会形成如下图的样子

每一次传值传参都要调用它的拷贝构造函数,那么这里就会变成一个无穷递归,也就是死循环,所以拷贝构造函数要将对象的引用作为形参

第3个问题->: 

浅拷贝更清晰的区分->;

我们首先用类来简单模拟一个错误的栈->:

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	// Stack st2 = st1;
	Stack(const Stack& st)
	{
		_array = st._array;
		_size = st._size;
		_capacity = st._capacity;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{

	Stack st1;
	st1.Push(1);
	st1.Push(2);

	// 拷贝构造
	Stack st2(st1);

	return 0;
}

我们提到过浅拷贝是指在复制对象时,对于对象中的数据成员,基本数据类型会直接复制其值,而对于指针类型的数据成员,只会复制指针的值(即内存地址),而不会复制指针所指向的实际数据。

那么执行Stack st2(st1)时,st2与st1中的array所指向的地址,其实是一样的,这就会导致内存重复,如图->:

还需要注意的是,如果st1与st2的_array所指向的内存地址相同,如果在类中加入一个析构函数,就会导致同一块动态内存析构两次,就会报错


正确的写法如下->:

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;

		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	// Stack st2 = st1;
	Stack(const Stack& st)
	{
		_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		memcpy(_array, st._array, sizeof(DataType) * st._size);

		_size = st._size;
		_capacity = st._capacity;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	bool  Empty()
	{
		return _size == 0;
	}

	DataType Top()
	{
		return _array[_size - 1];
	}
	
	void Pop()
	{
		--_size;
	}

	// 其他方法...
	~Stack()
	{
		cout << "~Stack()" << endl;

		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{

	Stack st1;
	st1.Push(1);
	st1.Push(2);

	// 拷贝构造
	Stack st2(st1);

	return 0;
}

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

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

相关文章

Apache Hop从入门到精通 第三课 Apache Hop下载安装

1、下载 官方下载地址&#xff1a;https://hop.apache.org/download/&#xff0c;本教程是基于apache-hop-client-2.11.0.zip进行解压&#xff0c;需要jdk17&#xff0c;小伙伴们可以根据自己的需求下载相应的版本。如下图所示 2、下载jdk17&#xff08;https://www.microsoft…

springboot房屋租赁管理系统

Spring Boot房屋租赁管理系统是一种基于Spring Boot框架构建的&#xff0c;旨在解决传统租房市场中房源信息更新不及时、虚假信息泛滥、交易流程繁琐等问题的信息化解决方案。 一、系统背景与目的 随着城市化进程的加快和人口流动性的增强&#xff0c;租房市场需求急剧增长。…

计算机网络 (35)TCP报文段的首部格式

前言 计算机网络中的TCP&#xff08;传输控制协议&#xff09;报文段的首部格式是TCP协议的核心组成部分&#xff0c;它包含了控制TCP连接的各种信息和参数。 一、TCP报文段的结构 TCP报文段由首部和数据两部分组成。其中&#xff0c;首部包含了控制TCP连接的各种字段&#xff…

鸿蒙-页面和自定义组件生命周期

页面生命周期&#xff0c;即被Entry装饰的组件生命周期&#xff0c;提供以下生命周期接口&#xff1a; onPageShow&#xff1a;页面每次显示时触发一次&#xff0c;包括路由过程、应用进入前台等场景。onPageHide&#xff1a;页面每次隐藏时触发一次&#xff0c;包括路由过程、…

道旅科技借助云消息队列 Kafka 版加速旅游大数据创新发展

作者&#xff1a;寒空、横槊、娜米、公仪 道旅科技&#xff1a;科技驱动&#xff0c;引领全球旅游分销服务 道旅科技 &#xff08;https://www.didatravel.com/home&#xff09; 成立于 2012 年&#xff0c;总部位于中国深圳&#xff0c;是一家以科技驱动的全球酒店资源批发商…

【HarmonyOS NEXT】鸿蒙跳转华为应用市场目标APP下载页

【HarmonyOS NEXT】鸿蒙跳转华为应用市场目标APP下载页 一、问题背景&#xff1a; 如今&#xff0c;大家都离不开各种手机应用。随着鸿蒙系统用户越来越多&#xff0c;大家都希望能在鸿蒙设备上快速找到想用的 APP。华为应用市场里有海量的 APP&#xff0c;但之前从鸿蒙设备进…

JavaScript动态渲染页面爬取之Splash

Splash是一个 JavaScript渲染服务,是一个含有 HTTP API的轻量级浏览器,它还对接了 Python 中的 Twisted 库和 OT库。利用它&#xff0c;同样可以爬取动态渲染的页面。 功能介绍 利用 Splash&#xff0c;可以实现如下功能&#xff1a; 异步处理多个网页的渲染过程:获取渲染后…

Thrustmaster Hotas Warthog飞行操作杆开发

目录 0 摘 要 &#xff1a;简单说一下这篇文章在搞啥 1 背 景 &#xff1a;什么需求以及对开发的背景调查 2 环境配置 &#xff1a;具体需要什么环境&#xff0c;对软件层面的需求 3 硬件测试 &#xff1a;测试遥感器…

算法-查找数组对角线上最大的质数

力扣题目&#xff1a;2614. 对角线上的质数 - 力扣&#xff08;LeetCode&#xff09; 给你一个下标从 0 开始的二维整数数组 nums 。 返回位于 nums 至少一条 对角线 上的最大 质数 。如果任一对角线上均不存在质数&#xff0c;返回 0 。 注意&#xff1a; 如果某个整数大于…

电梯系统的UML文档02

现在我们来回答用UML 设计电梯系统的实践中遇到的问题&#xff1a;“UML 是一种适合于实时系统的建模语言吗?”我们发现基于上段提到的特征&#xff0c;UML 是适合的但有不足。用UML 设计实时系统有以下问题&#xff1a; •特定硬件及它们特征的定义。 •在对象、任务和硬件层…

mysql set age=‘0‘ 和 set age=0的区别?

select case when(t1.business_transfer‘source’)then 0 else t1.settlement_tyy_cash_amount end as tyy from t_settlement_waybill t1 where waybill_sn in (‘2025010700001’); select case when(t1.business_transfer‘source’)then (t1.settlement_tyy_cash_amount‘…

利用Java爬虫按图搜索1688商品(拍立淘)的实践指南

在当今数字化时代&#xff0c;网购已成为人们生活中不可或缺的一部分。而1688作为国内领先的B2B电商平台&#xff0c;汇聚了海量的商品资源。然而&#xff0c;在面对众多商品时&#xff0c;传统的文字搜索方式有时难以满足我们的需求。比如&#xff0c;当我们看到一件心仪的商品…

达梦8-DMSQL程序设计学习笔记1-DMSQL程序简介

1、DMSQL程序简介 DMSQL程序是达梦数据库对标准SQL语言的扩展&#xff0c;是一种过程化SQL语言。在DMSQL程序中&#xff0c;包括一整套数据类型、条件结构、循环结构和异常处理结构等&#xff0c;DMSQL程序中可以执行SQL语句&#xff0c;SQL语句中也可以使用DMSQL函数。 DMSQ…

使用 WPF 和 C# 将纹理应用于三角形

此示例展示了如何将纹理应用于三角形,以使场景比覆盖纯色的场景更逼真。以下是为三角形添加纹理的基本步骤。 创建一个MeshGeometry3D对象。像往常一样定义三角形的点和法线。通过向网格的TextureCoordinates集合添加值来设置三角形的纹理坐标。创建一个使用想要显示的纹理的 …

Git版本控制 - 创建使用Repository

Git版本控制 – 创建使用Repository Version Control with Git - Create and Use Repository By JacksonML 上文提到&#xff0c;Git是一种分布式版本控制系统。作为全球范围内广泛使用的工具&#xff0c;如何将项目分步骤运用到其中呢&#xff1f; 本文简要介绍如何用Git工…

WINFORM - DevExpress -> devexpress版--报表(report)

devexpress report模板 1.安装devexpress(DevExpress 总结【安装、案例】_caoyanchao1的博客-CSDN博客_devexpress) 2.新建vs项目且添加standarReportDesigner控件 涛神设计器注意 3.运行后步骤 点击New Report DetailReport 涛神设计器checkbox(3.复选框只认boolean类型的 b…

【Redis】初识Redis

目录 Redis简介 Redis在内存中存储数据 Redis数据库中的应用 Redis缓存中的应用 Redis消息中间件 尾言 Redis简介 如下是Redis官网中&#xff0c;对Redis的一段描述 在这段描述中&#xff0c;我们提取如下关键要点&#xff1a; Redis主要用于在内存中存储数据Redis可…

YOLOv9改进,YOLOv9自研检测头融合HAttention用于图像修复的混合注意力检测头

参考文章 完成本篇内容,首先完成这篇文章,并把代码添加到 YOLOv9 中: YOLOv9改进,YOLOv9引入HAttention注意机制用于图像修复的混合注意力转换器,CVPR2023,超分辨率重建 下文都是手把手教程,跟着操作即可添加成功 目录 参考文章🎓一、YOLOv9原始版本代码下载🍀🍀…

[读书日志]8051软核处理器设计实战(基于FPGA)第七篇:8051软核处理器的测试(verilog+C)

6. 8051软核处理器的验证和使用 为了充分测试8051的性能&#xff0c;我们需要测试每一条指令。在HELLO文件夹中存放了整个测试的C语言工程文件。主函数存放在指令被分为五大类&#xff0c;和上面一样。 打开后是这样的文件结构。HELLO.c是主文件&#xff0c;这是里面的代码&am…

occ的开发框架

occ的开发框架 1.Introduction This manual explains how to use the Open CASCADE Application Framework (OCAF). It provides basic documentation on using OCAF. 2.Purpose of OCAF OCAF (the Open CASCADE Application Framework) is an easy-to-use platform for ra…