C++——类和对象(2):构造函数、析构函数、拷贝构造函数

2. 类的6个默认成员函数

        我们将什么成员都没有的类称为空类,但是空类中并不是什么都没有。任何类中都会存在6个默认成员函数,这6个默认成员函数如果用户没有实现,则会由编译器默认生成。

        6个默认成员函数包括:负责初始化工作的构造函数;负责清理工作的析构函数;在用同类对象对创建对象进行初始化时用到的拷贝构造;用于对象之间赋值的赋值操作符重载;还有两个很少自己实现的取地址操作符重载const修饰的取地址操作符重载

2.1 构造函数

        构造函数是一个特殊的成员函数,帮助我们对新创建的对象进行初始化。

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//void Init(int year = 2000, int month = 1, int day = 1)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
};
int main()
{
	Date d1;
	Date d2(2024, 2, 26);
	//d1.Init(2024, 2, 26);
	d1.Print();
	d2.Print();
}

构造函数说明

        对于构造函数,我们需要对其进行一些说明与强调:

①构造函数属于默认成员函数,当发现我们没有写时编译器会默认生成一个,当我们写了编译器就不再会生成

②我们写构造函数时需要注意:构造函数的函数名应于类名相同;构造函数没有返回值返回类型的void不写;构造函数可以重载

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date()
	{}
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
};
int main()
{
    Date d1();  //error 调用无参构造函数时,如果跟上()就成了函数声明
	Date d1;
	Date d2(2024, 2, 26);
}

③构造函数会在创建对象的时候自动调用,且在该对象的生命周期内仅调用这一次。

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
};
int main()
{
	Date d1(2024, 2, 26);
	d1.Print();	//2024-2-26
}

④构造函数的任务不是开辟空间创造对象,而是对创建的对象进行初始化。

⑤编译器默认生成的构造函数,对内置类型(int、char等)不会做处理对自定义类型(类等)会调用该自定义类型的构造函数

class Time
{
private:
	int _hour;
	int _minute;
	int _second;
public:
	Time()
	{
		cout << "Time()" << endl;
	}
};
class Date
{
private:
	int _year;
	int _month;
	int _day;
	Time _t;
public:
	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
};
int main()
{
	Date d1;
	d1.Print();
	
	//output
	//Time()
    //随机值
}

⑥C++11规定:内置类型可以在类中给默认值。

class Date
{
private:
	//在类中给默认值
	int _year = 2000;
	int _month = 1;
	int _day = 1;
public:
	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}
};
int main()
{
	Date d1;
	d1.Print();	//2000-1-1
}

2.2 析构函数

        构造函数是在对象创建后默认调用,用于初始化的。析构函数则是在对象的生命周期结束时调用自动析构函数,用来销毁对象,完成对象中资源的清理工作。

析构函数说明

①析构函数也属于默认成员函数,所以当我们没有写时编译器会默认生成一个,当我们写了编译器就不再会生成

②注意析构函数形式:析构函数的函数名为 ~类名 ;析构函数没有返回值和返回类型;析构函数不可以重载

class Stack
{
private:
	int* _arr;
	int _capacity;
	int _top;
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	void Push(int x)
	{
		//CheckCapacity();
		_arr[_top++] = x;
	}
	~Stack()
	{
		free(_arr);
		_arr = nullptr;
		_capacity = 0;
		_top = 0;
	}
};

③析构函数会在对象生命周期结束的时候自动调用

④编译器默认生成的析构函数,对内置类型不做资源清理,系统会自动将其内存回收;对自定义类型会调用该自定义类型的析构函数

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
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;
	//output:
	//~Time()
}

 ⑤如果类中成员变量没有申请资源时,析构函数可以不需要写,使用默认即可。但是如果存在资源申请(如malloc),需要自己写出对应的析构函数来正确释放空间。

⑥在此处我们总结一下对于局部、全局、静态对象调用构造和析构的顺序问题:

class Date
{
public:
	Date(int year = 1)
	{
		_year = year;
		cout << "Date()->" << _year << endl;
	}

	~Date()
	{
		cout << "~Date()->" << _year << endl;
	}
private:
	int _year;
};

void func()
{
	Date d3(3);
	static Date d4(4);
}

Date d5(5);
static Date d7(7);
Date d6(6);
static Date d8(8);

int main()
{
	Date d1(1);
	Date d2(2);
	func();

	return 0;
}

         我们创建了一批对象,再令main函数正常结束,观察输出,可以总结出构造函数和析构函数调用的顺序:

构造函数:按照代码执行顺序进行创建。①顺序执行全局(包括全局静态)对象的创建;②进入main函数,顺序执行;③遇到函数,进入后顺序执行。

析构函数:按照声明周期结束时间进行调用。局部对象(后定义先析构)->全局和静态对象(后定义先析构)。

2.3 拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,也是在初始化的时候发挥作用。拷贝构造函数只有一个由const修饰的本类类型对象的引用作为参数,在用已存在的类类型对象创建新对象时自动调用。

拷贝构造函数说明

①拷贝构造函数实际上是构造函数的一个重载形式,也是一个默认成员函数。当我们写了构造函数没有写拷贝构造函数时,编译器会默认生成一个拷贝构造函数

当我们写了拷贝构造函数而不写构造函数时,编译器会认为拷贝构造函数属于构造函数,就不会再生成构造函数。这个时候再创建新对象就会报错找不到默认构造。这时除了自己写一个构造函数外,还可以使用default关键字,强制让编译器生成一个默认构造函数。

class Time
{
private:
	int _hour;
public:
	Time() = default;	//强制编译器生成默认构造
	Time(const Time& t)
	{
		_hour = t._hour;
	}
};
class Date
{
private:
	int _year;
public:
	Date(int year)
	{
		_year = year;
	}
};
int main()
{
	Date d1(2024);
	Date d2(d1);	//只有构造函数,会生成拷贝构造
	Time t1;	//error 不存在默认构造  只有拷贝构造函数,不会生成构造函数
	Time t2(t1);
}

②注意拷贝构造函数形式:拷贝构造函数是构造函数的重载形式,所以除了参数和构造函数一样。拷贝构造函数的参数只有一个,是const修饰的本类类型对象的引用。但是我们了解了this指针的存在,所以要注意分清调用的形式,实参应该是已经拷贝的“原件”。

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
//构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
};
int main()
{
	Date d1(2024,2,27);
	d1.Print();
	Date d2(d1);
	d2.Print();
}

③对于参数的说明:拷贝构造的一个参数必须是类类型对象的引用,如果不传引用而传值则会导致无穷递归调用引发报错。

void func(Date d)
{}
int main()
{
	Date d1(2024,2,27);
	func(d1);
}

        对于以上代码,在func函数使用了值传递,实际上就是将d1的值传给参d。类似于C语言中的结构体传值调用,C++的自定义类型值传递时都需要调用拷贝构造,在执行到这里的时候编译器将d1的值拷贝到d中,所以这时就需要调用Date类的拷贝构造函数。

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

        那么如果在实现拷贝构造函数的时候传值,那么在调用拷贝构造函数的时候,参数为了拿到值会再去调用拷贝构造,这次的拷贝构造仍然是值传递,为了获取参数的值会去调用下一次拷贝构造,因而产生无穷递归。

④编译器默认生成的拷贝构造函数,对内置类型是对内存存储中字节完成拷贝,这种拷贝称为浅拷贝,或值拷贝。对于自定义类型则是调用它的拷贝构造函数

class Time
{
public:
	Time()
	{
		_hour = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 2000;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
	//oupput:
	//Time::Time(const Time&)
}

⑤深拷贝和浅拷贝:浅拷贝仅仅是对字节进行逐一拷贝,这种拷贝方式在面对正常情况并无问题,但是一旦遇到申请资源的成员,浅拷贝便会产生问题。

class Stack
{
private:
	int* _arr;
	int _capacity;
	int _top;
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
};
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

        对于上述栈这个类,如果仅仅是浅拷贝的方式,那么st2的_arr成员的值就会和st1的_arr值相同。但是我们知道这个_arr成员指向的是对上开辟的空间,如果我们的两个栈st1和st2的_arr成员相同,那就意味着是二者共用一块空间,这就出现了问题。

        对于这种申请了资源的情况,我们就需要深拷贝,即对对象下所管理的深层空间也进行拷贝,此时就需要自己实现拷贝构造函数了。

class Stack
{
private:
	int* _arr;
	int _capacity;
	int _top;
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int) * capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	Stack(const Stack& st)
	{
		_arr = (int*)malloc(sizeof(int) * st._capacity);
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		memcpy(_arr, st._arr, sizeof(int) * st._capacity);
		_capacity = st._capacity;
		_top = st._top;
	}
};
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

⑥拷贝构造函数自动调用的场景:a.用存在的对象初始化新对象;b.函数参数类型为类类型对象;c.函数的返回值类型为类类型对象。

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

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

相关文章

怎么调用文心一言的api接口生成一个简单的聊天机器人(python代码)

寒假在学习大模型&#xff0c;但也没弄出多少眉目&#xff0c;电脑性能还有点小问题&#xff0c;大模型总跑不起来&#xff0c;只会简单调用一下现有的大模型的接口&#xff0c;例如&#xff1a;文心一言&#xff0c;下面展示一下代码&#xff1a; import tkinter as tk impor…

Linux中如何在创建子线程的时候设置为分离属性

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<unistd.h> #include <pthread.h> void *mythread(void *arg) {printf("id[%ld]\n",pthread_self()); } int main() { //定义pthread_…

力扣550 游戏玩法分析 IV

目录 题目描述 思路整理 1. 首次登录日期 2. 第二天登录 3. 计算比率 实现思路 完整代码及解释 题目描述 Table: Activity ----------------------- | Column Name | Type | ----------------------- | player_id | int | | device_id | int | | ev…

华为自动驾驶技术详解报告分享

ADS2.0首发搭载问界M5智驾版&#xff0c;城市NCA计划年底全国开通。2023年4月16日华为在智能汽车解决方案发布会上发布了最新的ADS2.0产品&#xff0c;硬件数量减少至27个(11个摄像头12个超声波雷达3个毫米波雷达1个激光雷达,ADS1.0有34个)&#xff0c;车载计算平台改为MDC610&…

苹果ios群控软件开发常用源代码分享!

在移动软件开发领域&#xff0c;苹果设备由于其封闭性和安全性受到了广大开发者的青睐&#xff0c;然而&#xff0c;这也为开发者带来了一些挑战&#xff0c;特别是在进行群控软件开发时。 群控软件是指可以同时控制多台设备的软件&#xff0c;这在自动化测试、批量操作等场景…

网络编程难点之select、poll与epoll详解

前言 为什么需要I/O多路复用技术&#xff1f; 首先&#xff0c;I/O多路复用技术主要被应用在需要高性能的网络服务器程序中。 高性能网络服务器程序需要做的事情就是供多个客户端同时进行连接并处理客户端传送过来的数据请求&#xff1a; 对于这种情况&#xff0c;很多人自然…

二叉树——二叉树所有路径

二叉树所有路径 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5] 输出&#xff1a;["1->2->5","1-…

leetcode刷题日记-合并N个升序链表

题目描述 解题思路 相信大家都做过两个有序链表合并的习题吧。该题的解决思路是建立在两个有序链表合并的基础上。使用的方法是递归。 两个有序链表合并思路 1.如果其中一个链表为空&#xff0c;直接返回另一个链表&#xff0c;因为一个空链表和非空链表的合并结果就是非空链…

在微服务整合dubbo,以为微服务版的若依为例

在微服务整合dubbo&#xff0c;以为微服务版的若依为例 一、环境二、整合过程1、父模块依赖2、生产者3、消费者 三、修改若依的服务调用方式为dubbo1、改造系统模块2、改造认证授权中心 四、整合过程遇到的问题1、出现循环引用2、出现依赖冲突3、启动出现端口号被占用4、出现某…

windows U盘不能识别

windows U盘不能识别 1、问题描述2、问题分析解决3、把U盘插到windows电脑上试试能不能识别 1、问题描述 windwos u盘不能识别 u盘被拿到mac电脑上做了启动盘之后&#xff0c;就不能被windows识别了。题主很奇怪里面被mac电脑的同学放了什么&#xff0c;因此想到把优盘挂载到L…

【LeetCode周赛】第 386 场周赛

目录 3046. 分割数组 简单3047. 求交集区域内的最大正方形面积 中等3048. 标记所有下标的最早秒数 I 中等 3046. 分割数组 简单 3046. 分割数组 分析&#xff1a; 查看数组内有没有重复超过2次的数即可。 代码&#xff1a; class Solution { public:bool isPossibleToSplit…

类和对象(2)——距离C++又近了一步

目录 一、构造函数 1.1声明和定义构造函数 1.2成员名和参数名 1.3构造函数的使用 1.4初始化列表 二、析构函数 2.1析构函数的概念 2.2析构函数的性质 三、拷贝构造函数 四、赋值运算符重载 4.1运算符重载 4.2赋值运算符重载 一、构造函数 我们知道&#xff0c;C中…

Python:练习:输出int值a占b的百分之几。例如:输入1和4,输出:25%。

案例&#xff1a; 输出int值a占b的百分之几。例如&#xff1a;输入1和4&#xff0c;输出&#xff1a;25%。 思考&#xff1a; 所有的一步步思考&#xff0c;最后综合起来。 首先&#xff0c;确定 输出&#xff0c;那么就用input&#xff0c;而且是int值&#xff0c;所以肯定…

用vivado创建一个赛灵思AXI的IP核

一、新建一个管理IP的任务 二、设置板子&#xff0c;verilog语言和文件位置 三、创建新的IP核 添加一个axi-full的master接口和axi-full的slave接口 四、查看赛灵思AXI代码 第一个是axi的master接口代码&#xff0c;下面的是axi的slave接口代码 五、打包IP核以供后续使用 六、…

请求响应与统一响应结果

1.请求响应 1.安装postman 2.简单的参数 //原始的请求参数的方法RequestMapping("/simoleParam")public String simpleParam(HttpServletRequest request){String name request.getParameter("name");String ageStr request.getParameter("age&quo…

逻辑电路集成块手册

还在查找74XX集成块的数据手册吗,还在找逻辑门电路的手册吗 不用找了,直接打开此电子书,查找就可以了,内部框图,真值表引脚序号都有DOWNLOAD:https://www.ti.com/lit/pdf/scyd013?keyMatchLOGIC%20POCKET%20DATA%20BOOK 失效直接上TI官方网站搜索logic pocket data book即可搜…

【web APIs】5、(学习笔记)有案例!

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、js组成window对象定时器-延迟函数location对象navigator对象histroy对象 二 、本地存储&#xff08;今日重点&#xff09;localStorage&#xff08;重点&am…

FLask会话技术和Flask模板语言

二、FLask会话技术和Flask模板语言 1.会话技术 cookie 客户端的会话技术&#xff1a;让服务器认识浏览器&#xff0c;常用于登录 cookie本身由浏览器保存&#xff0c;通过Response将cookie写到浏览器上&#xff0c;下一次访问&#xff0c;浏览器会根据不同的规则携带cookie过…

【leetcode】链表分割

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 方法1. 不用哨兵位方法2. 用哨兵位 点击查看题目 思路: 将链表分为2个链表list1和list2&#xff0c;…

Cookie、Session和JWT

摘要&#xff1a;Cookie、Session和JWT都不是什么新的技术了&#xff0c;最近用到了就比较和总结下。 我们知道http协议是无状态的&#xff0c;用户登录后如何验证和保存用户状态呢&#xff1f;下面来介绍 1. 使用Cookie和Session验证登录状态 session是保存在服务端的一种数…