类和对象(中)(构造函数、析构函数和拷贝构造函数)

1.类的六个默认成员函数

任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

//空类
class Date{};

默认成员函数:用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数

2.构造函数

构造函数 是一个 特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
构造函数主要任务不是开空间创建对象,而是初始化对象。
其特征如下:
        ① 函数名与类名相同。
        ② 无返回值。//不需要写void
        ③ 对象实例化时编译器 自动调用 对应的构造函数。
        ④ 构造函数可以重载。
多个构造函数,有多种初始化方式,一般情况,建议每个类,都可以写一个全缺省的构造(好用)
class Date{public://他们俩构成函数重载,但是无参调用时会存在歧义// 1.无参构造函数Date(){_year = 1;_month = 1;_day = 1;}// 2.带参构造函数// 一般情况,建议每个类,都可以写一个全缺省的构造(好用)Date(int year=1, int month=1, int day=1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main()
{//Date d1();  //d1后面不能带括号否则定义对象无法跟函数声明区分开
//Date func();//这就是d1为什么不能带括号Date d1;d1.Print();
//类型 对象(2024,4,2)Date d2(2024, 4, 2);//这里调用构造函数是对象名加参数列表  
//这里可以和函数声明进行区分,如下一行的函数声明所示//Date func(int x, int y, int z);d2.Print();Date d3(2024);d3.Print();Date d4(2024, 4);d4.Print();return 0;
}

        ⑤如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦            用户显式定义编译器将不再生成。

#include<iostream>
using namespace std;
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;int _month;int _day;
};int main()
{// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;//对象实例化的时候自动调用对应的构造函数return 0;
}

如果用户显示定义了构造函数,编译器将不会生成无参的默认构造函数,这时候如果定义一个无参的类如Date d1,会编译失败。

⑥C++ 把类型分成内置类型 ( 基本类型 ) 和自定义类型。内置类型就是语言提供的数据类型,如:int/char.../任意类型指针 ,自定义类型就是我们使用 class/struct/union 等自己定义的类型。
如果我们没写构造函数,编译器自动生成构造函数,对于编译器自动生成的构造函数
对于内置类型的成员变量,编译器没有规定要不要做处理!(有些编译器会处理成0,但是C++标准并没有规定)
对于自定义类型的成员变量,才会调用他的默认成员函数即无参构造,如果没有无参构造会报错。(不传参就可以调用的那个构造,全缺省构造)
#include<iostream>
using namespace std;
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

这里我们发现编译器自动生成的构造函数对于自定义类型调用了它的默认构造函数。

注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在
类中声明时可以给默认值
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
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;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

总结:不传参数就可以调用的函数就是默认构造

Ⅰ一般情况构造函数都需要我们自己显式去实现

Ⅱ只有少数情况下可以让编译器自动生成构造函数类似MyQueue,成员全是自定义类型

3.析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而 对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

析构函数是特殊的成员函数,其特征如下:

①析构函数名是在类名前加上字符 ~
②无参数无返回值类型。
③一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
④对象生命周期结束时,C++ 编译系统系统自动调用析构函数

⑤对于编译器自动生成的默认析构函数,对于自定义类型成员会调用它的析构函数。

跟构造函数类似:

a、内置类型不做处理       

b、自定义类型去调用他的析构

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;
}//对象在这里被销毁后,这里会自动调用析构函数

程序运行结束后输出: ~Time() ,在main 方法中根本没有直接创建 Time 类的对象,为什么最后会调用 Time 类的析构函数?
因为: main 方法中创建了 Date 对象 d ,而 d 中包含 4 个成员变量,其中 _year, _month,
_day 三个是 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t Time 类对 象,所以在d销毁时,要将其内部包含的 Time 类的 _t 对象销毁,所以要调用 Time 类的析构函数。但是:main函数 中不能直接调用Time 类的析构函数,实际要释放的是 Date 类对象,所以编译器会调用 Date 类的析构函数,而Date 没有显式提供,则编译器会给 Date 类生成一个默认的析构函数,目的是在其内部调用Time 类的析构函数,即当Date 对象销毁时,要保证其内部每个自定义对象都可以正确销毁
 main函数中并没有直接调用 Time 类析构函数,而是显式调用编译器为 Date 类生成的默认析 构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date 类;有资源申请时,一定要写,否则会造成资源泄漏,比如 Stack
自动生成的构造函数和析构意义何在?两个栈实现一个队列
Stack.h如下:
#pragma once
#include<stdlib.h>
#include<iostream>
using namespace std;class Stack
{
public:Stack(int n = 4);~Stack();//void Init();//void Destroy();void Push(int x);bool Empty();void Pop();int Top();
private:// 成员变量int* _a;int _top;int _capacity;
};class Queue
{
public:void Init();void Push(int x);};

Stack.cpp如下:

#include"Stack.h"Stack::Stack(int n)//缺省参数声明和定义不能同时给,规定了只在声明时候给,定义的时候不给
{_a = (int*)malloc(sizeof(int)*n);_top = 0;_capacity = n;
}Stack::~Stack()
{cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;
}//void Stack::Init()//指明类的作用域就指明了类的出处
//{
//	_a = nullptr;
//	_top = 0;
//	_capacity = 0;
//}//任何一个变量都得先定义再使用,不符合语法报语法错误//void Destroy()
//{
//	//...
//}void Stack::Push(int x)
{// ..._a[_top++] = x;
}bool Stack::Empty()
{return _top == 0;
}void Stack::Pop()
{--_top;
}int Stack::Top()
{return _a[_top - 1];
}//void Queue::Push(int x)
//{
//
//}

test.cpp
#icnlude<Stack.h>
class MyQueue
{
private:Stack _pushst;Stack _popst;
};int main()
{MyQueue q;return 0;
}

这里创建MyQuque类q时,会自动调用调用Stack类的默认构造函数,q销毁时,也会自动调用Stack类的默认析构函数。

下面是一个括号匹配问题,利用c和c++实现的比较,可以发现,利用c++的构造和析构特性后,会方便很多

实践中总结:
1、有资源需要显示清理,就需要写析构。如:Stack List
2、有两种场景不需要显示写析构,默认生成就可以了
a、没有资源需要清理,如:Date
b、内置类型成员没有资源需要清理。剩下的都是自定义类型成员
4.拷贝构造函数
拷贝构造函数: 只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存 在的类类型对象创建新对象时由编译器自动调用
特征:
①拷贝构造函数 是构造函数的一个重载形式
拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 因为会引发无穷递归调用。
class Date
{
public:Date(int year = 1970, int month =1, int day=1){_year = year;_month = month;_day = day;}//Date(Date d)错误写法//Date d2(d1);//d1传给了d,d2就是thisDate(const Date& d)//加了const之后d的内容就无法修改{cout << "const Date& d" << endl;//this->_year = d._year;_year = d._year;_month = d._month;_day = d._day;}
//	Date(Date* d)//规定这里不是拷贝构造,就是一个普通构造,编译器会自动生成一个默认的拷贝构造
//	{
//		_year = d->_year;
//		_month = d->_month;
//		_day = d->_day;
//	}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:// 基本类型(内置类型)int _year ;int _month ;int _day ;};
void func(Date d)
{d.Print();
}
int main()
{Date d1(2024,4,18);Date d2(d1);func(d2);return 0;
}

//Date(Date d)是错误写法,

原因:对于自定义类型传值传参要调用拷贝构造完成,这是一种规定,自定义类型的拷贝都要调用拷贝构造才能完成。这里用d1去构建d2,规定需要调用拷贝构造,调用拷贝构造得先传参,先传参形成了一个新的拷贝构造,新的拷贝构造假设去调用,去调用这个拷贝构造,又要先传参,从逻辑上来说就是一个无穷递归

对于
func(d2);
void func(Date d)
{d.Print();
}
Date(const Date& d)
{cout << "const Date& d" << endl;_year = d._year;_month = d._month;_day = d._day;
}

与构造和析构函数一样,内置类型就直接传,自定义类型传值传参就要调用拷贝构造完成,这里调用func之前先传参,传参就会形成一个拷贝构造,d是d2的别名,函数结束回来,传参完成,参数的传递就是完成拷贝构造调用,最后调用调用func函数,结束

也可以从建立函数栈帧的方式去看函数func(d2)的调用

当然也可以采用指针或者引用的方式,这种方式不会调用拷贝构造。

	func(d2);
void func(Date& d)
{d.Print();
}
//这种方式也不会调用拷贝构造,d是d2的别名func(&d2);
void func(Date* d)
{d->Print();
}
//这种情况下没有拷贝构造,因为传的是内置类型,把d2地址进行传递,用指针进行接收

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

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::Time(const 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 d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
④深拷贝
编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,这是浅拷贝或者值拷贝,对于日期类不需要自己显示实现,值拷贝就是将一块空间里面的值按照字节一个一个的拷贝过来,有点像memcpy拷贝—样,memcpy就是按字节拷贝
但是如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝,如:Stack Queue List等
//这里如何去掉Stack的拷贝构造会发现下面的程序会崩溃掉,这里就需要深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}Stack(const Stack& st){_array = (DataType*)malloc(st._capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}memcpy(_array, st._array, st._size*sizeof(DataType));_size = st._size;_capacity = st._capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}bool Empty(){return _size == 0;}DataType Top(){return _array[_size - 1];}void Pop(){--_size;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
class MyQueue
{
private:Stack _st1;Stack _st2;int _size = 0;
};
int main()
{Stack st1(10);st1.Push(1);st1.Push(1);st1.Push(1);Stack st2 = st1;st2.Push(2);st2.Push(2);while (!st2.Empty()){cout << st2.Top() << " ";st2.Pop();}cout << endl;//输出1 1 1while (!st1.Empty()){cout << st1.Top() << " ";st1.Pop();}cout << endl;//输出2 2 1 1 1 MyQueue q1;MyQueue q2(q1);//这里自定义类型会去调用栈的拷贝构造(栈的拷贝构造是深拷贝),内置类型完成值拷贝return 0;
}

注意:这里_array按照字节拷贝,相当于然s2的_array指向的空间和s1的_array指向的空间一样,但是这样会导致两个问题:(只要指针指向资源的都会有问题)
1.s1 push后s2上也能看见,因为s1改变的是和s2同一块空间的值,同时s1的size会改变,但是s2的size不会改变
⒉析构的时候,s1会free一次,s2也会free—次,相当于对一块空间free两次,也即析构两次
这里可以用深拷贝来解决,深拷贝就是你的形状和你的空间是什么样子就去开和你—样的空间,放一样的值,复制和你一样的出来。
在类里面加上如下的拷贝构造即可
	Stack(const Stack& st){_array = (DataType*)malloc(st._capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}memcpy(_array, st._array, st._size*sizeof(DataType));_size = st._size;_capacity = st._capacity;}

实践中总结:
1、如果没有管理资源,一般情况不需要写拷贝构造,默认生成的拷贝构造就可以。如:Date
2、如果都是自定义类型成员,内置类型成员没有指向资源,也类似默认生成的拷贝构造就可以。如:MyQueue3、一般情况下,不需要显示写析构函数,就不需要写拷贝构造
4、如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造完成深拷贝。如:Stack Queue List等

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

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

相关文章

OpenMesh 极小曲面(局部迭代法)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 我们的目标是想得到一个曲率处处为0的曲面,具体操作如下所述: 二、实现代码 #define _USE_MATH_DEFINES #include

(助力国赛)数学建模可视化!!!含代码1(折线图、地图(点)、地图(线)、地图(多边形)、地图(密度)、环形图、环形柱状图、局部放大图)

众所周知&#xff0c;数学建模的过程中&#xff0c;将复杂的数据和模型结果通过可视化图形呈现出来&#xff0c;不仅能够帮助我们更深入地理解问题&#xff0c;还能够有效地向评委展示我们的研究成果。   今天&#xff0c;作者将与大家分享8种强大的数学建模可视化图形及其在…

docker 启动时报错

docker 启动时报如下错误 Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details 因为安装docker时添加了镜像源 解决方案&#xff1a; mv /etc/…

【1577】java网吧收费管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java 网吧收费管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0…

网络防火墙技术知多少?了解如何保护您的网络安全

在当前以网络为核心的世界中&#xff0c;网络安全成为了至关重要的议题。网络防火墙是一种常见的保护网络安全的技术&#xff0c;用于监控和控制网络流量&#xff0c;阻止未经授权的访问和恶意活动。今天德迅云安全就带您了解下防火墙的一些相关功能和类型。 防火墙的五个功能…

不需要GPU就可以玩转模型,同时支持本地化部署

简单一款不需要GPU就可以在Win 机器跑的模型&#xff1a;Ollama&#xff1b;用于本地运行和部署大型语言模型&#xff08;LLMs&#xff09;的开源工具 关于Ollama的简要介绍 平台兼容性&#xff1a;Ollama支持多种操作系统&#xff0c;包括macOS、Linux和Windows&#xff0c;…

【Web】2022DASCTF X SU 三月春季挑战赛 题解(全)

目录 ezpop calc upgdstore ezpop 瞪眼看链子 fin#__destruct -> what#__toString -> fin.run() -> crow#__invoke -> fin#__call -> mix.get_flag() exp <?php class crow {public $v1;public $v2;}class fin {public $f1; }class what {public $a; }…

详细分析Mysql常用函数(附Demo)

目录 前言1. 聚合函数2. 字符串函数3. 日期函数4. 条件函数5. 数值函数6. 类型转换函数 前言 由于实战中经常运用&#xff0c;索性来一个总结文 创建一个名为 employees 的表&#xff0c;包含以下字段&#xff1a; employee_id&#xff1a;员工ID&#xff0c;整数类型 first…

动手学大模型LLM应用开发之提示词工程(Prompt Engineering)

目录 一、什么是提示词工程(Prompt Engineering)&#xff1f;二、Prompt 设计的原则及使用技巧原则一&#xff1a;编写清晰、具体的指令1、使用分隔符清晰地表示输入的不同部分2、寻求结构化的输出3、要求模型检查是否满足条件4、提供少量示例 原则二&#xff1a;给模型时间去思…

《Super Simple Skybox》天空盒 -- 创造绝美天空的神奇工具!限时免费!

《Super Simple Skybox》天空盒 -- 创造绝美天空的神奇工具&#xff01;限时免费&#xff01; 前言内容介绍资源特色动态&#xff0c;美丽的天空在几秒钟内即插即用 功能列表领取兑换码 前言 ^^在这个充满创意与想象的世界里&#xff0c;Unity 免费资源犹如一颗璀璨的明珠&…

【创建型模式】原型模式

一、原型模式概述 原型&#xff08;Prototype&#xff09;模式的定义&#xff1a;用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里&#xff0c;原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效&#xf…

Redis的RedisObject和对外可见的5种数据结构

目录 RedisObject Redis的编码方式 对外可见的5种数据结构 1.string string结构的源码 为什么是小于44字节会采用embstr编码&#xff1f; embstr和raw区别 2.list list结构的源码 3.set set结构的源码 4.zset zset结构的源码 5.hash hash结构的源码 Redis中…