【类与对象 -2】学习类的6个默认成员函数中的构造函数与析构函数

2024-02-17 18:36

本文主要是介绍【类与对象 -2】学习类的6个默认成员函数中的构造函数与析构函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

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

2.构造函数

2.1概念

2.2特性

3.析构函数

3.1析构函数的概念

3.2特性


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

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。
默认成员函数: 用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

下面我们来分别学习一下这6个函数~

2.构造函数

2.1概念

class Date
{
public:void Init(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 d1;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}

在日常使用时,我们可能会忘记对对象进行初始化,程序报错后反而检查半天才发现问题。在为了避免这种失误出现,C++中引入了构造函数。

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

2.2特性

构造函数在函数名上看起来好像是创建对象,实际上是完成对象的初始化

构造函数特征如下:

①函数名和类名相同;

②没有返回值,不是在前加void,而是压根不需要写;

③对象实例化时编译器自动调用对应的构造函数;

④构造函数可以重载;

上面代码的构造函数形式如下:

(1)不带参数的构造函数,由于编译器自动调用构造函数,在定义对象时就已经初始化了,

(2)带参数的构造函数,在定义对象的时候就要将初始化的值加在后面。

调用带参的构造函数时在括号内加入要初始化的值跟在对象后,那调用无参的构造函数时可以直接跟括号在对象后吗?(为什么调用无参的构造函数时后面不用加括号)

不能。这种形式就成了函数声明。

如下图,声明了一个d3函数,该函数没有参数,返回一个Date类型对象。

以理论上两种形式可以同时存在,构成了函数重载,但是一般情况下我们不会同时写出这两个,因为调用存在歧义。

这两种形式可以合为一种,利用我们之前学过的缺省参数来修改,如下面图中的形式:

⑤如果在类中忘记显式定义构造函数也不用担心,C++编译器会自动生成一个无参的默认构造函数。

我们忘记定义构造函数时执行程序,发现可以正常运行,这是因为编译器会自动生成一个默认构造函数,但是看运行结果截图我们可以发现编译器没有对参数进行初始化,_year、_month、_day都是随机值。那么编译器生成的默认构造函数有什么用呢?

C++中把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,像int/char/double/指针等;自定义类型就是我们使用的struct、class等自己定义的类型。

C++98中规定对默认生成的构造函数对内置类型不作处理,对自定义类型则调用它的默认构造函数。如果自定义类型没有默认构造函数就会报错。

其实自定义类型的尽头就是内置类型。

以上Date类中的_year、_month、_day就是int类型,所以不作处理,是随机值,接下来我们验证一下自定义类型。

•有些编译器可能会对内置类型进行处理(int初始化为0,double初始化为0.0,指针初始化为nullptr),但C++标准并没有规定内置类型要处理。

C++11中对内置类型不处理的缺陷又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

分析一个类型成员和初始化要求,如果需要些构造函数我们就自己写,不需要时就靠编译器自动生成。结论:大多情况下都需要自己实现构造函数

⑥无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

不止是编译器生成的构造函数叫默认构造函数,我们自己显式定义的无参构造函数,全缺省构造函数都是默认构造函数。即不需要传参就能调用的构造函数,就叫默认构造函数

默认生成的构造函数不能与其他两种同时存在;无参构造函数不能与全缺省构造函数同时存在,会产生调用歧义。

一般情况下,我们建议使用全缺省构造函数

3.析构函数

3.1析构函数的概念

前面我们学习了对对象进行初始化构造函数(类似于栈中的初始化函数Init),那么怎样清理资源呢,这就要提到我们接下来要学习的析构函数(栈中的销毁函数Destory)。

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

3.2特性

析构函数特征:

①析构函数名是在类名前加上字符 ~。(C语言中“~”作用是按位取反,析构函数前加表示该函数与构造函数作用相反)
②无参数无返回值类型。
③一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
默认生成的析构函数与构造函数类似,对内置类型不作处理,自定义类型则调用它的析构函数。
(Date类没有显式定义析构函数,系统将自动生成,Date类中定义了自定义类型Time类型的对象t,调用它的析构函数~Time()。)
对象生命周期结束时,C++编译系统系统自动调用析构函数。
以下面的日期类为例,定义一个析构函数~Date()。(其实日期类的析构函数没什么有用的,因为它的_year等都是属于对象,并没有额外开辟空间,也就没有什么资源需要清理,此处只是用来验证它的特性)
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}~Date(){cout << this << endl;cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
void func()
{Date d2;
}
int main()
{func();Date d1;d1.Print();return 0;
}

如果类中没有申请资源时,析构函数可以不写 ,直接使用编译器生成的默认析构函数,比如
Date类; 有资源申请时,一定要写, 否则会造成资源泄漏,比如Stack类。

如果我们写了一个栈,忘记写析构函数会发生内存泄漏。虽然函数结束栈帧销毁,但是在堆上额外开辟了一块空间没有释放。C语言中避免出现内存泄漏需要在函数结束前调用Destory函数来销毁,但是这种方法在使用时极有可能忘记,因而C++中引入了析构函数。

class Stack
{
public:Stack(size_t capacity = 4){_array = (int*)malloc(sizeof(int) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(int data){// CheckCapacity();_array[_size] = data;_size++;}
private:int* _array;int _capacity;int _size;
};
int main()
{Stack S;return 0;
}

对以上代码加上析构函数,结果如下

**先定义的先调用构造函数,后定义的先调用析构函数

class Date
{
public:Date(int year){_year = year;cout << "Date()->" << _year << endl;}~Date(){cout << "~Date()->" << _year << endl;}
private:int _year = 1;int _month = 1;int _day = 1;
};
Date d5(5);
static Date d6(6);
void func()
{Date d4(4);
}
int main()
{Date d1(1);static Date d2(2);Date d3(3);func();return 0;
}

分析上面代码,猜测结果是什么?

我们先分析一下各个对象是全局还是局部变量。d1、d2、d3、d4是局部变量,d4是在函数 func() 内定义,函数 func() 在main函数内调用,所以d4的生命周期先结束。d2又被static修饰(称为静态局部变量),改变了它的存储位置,使得它的生命周期变长,直至程序结束,生命周期才结束,在销毁之前应先将main函数内的局部变量先销毁(后定义先析构)再销毁该静态局部变量。d5、d6是全局变量,虽然d6被static修饰(静态全局变量),但它与全局变量的存储位置没有区别,生命周期仍是整个程序的执行期间,也是后定义的先析构。

所以调用析构函数的顺序是d4-d3-d1-d2-d6-d5

结果如下:

总结:

调用析构函数的顺序:局部对象(后定义的先析构)->静态局部对象->全局对象(后定义的先析构)

        构造函数和析构函数的学习就到这了,下篇我们将一起学习其他的默认成员函数,谢谢观看!

这篇关于【类与对象 -2】学习类的6个默认成员函数中的构造函数与析构函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/718663

相关文章

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁

java中VO PO DTO POJO BO DO对象的应用场景及使用方式

《java中VOPODTOPOJOBODO对象的应用场景及使用方式》文章介绍了Java开发中常用的几种对象类型及其应用场景,包括VO、PO、DTO、POJO、BO和DO等,并通过示例说明了它... 目录Java中VO PO DTO POJO BO DO对象的应用VO (View Object) - 视图对象

vue如何监听对象或者数组某个属性的变化详解

《vue如何监听对象或者数组某个属性的变化详解》这篇文章主要给大家介绍了关于vue如何监听对象或者数组某个属性的变化,在Vue.js中可以通过watch监听属性变化并动态修改其他属性的值,watch通... 目录前言用watch监听深度监听使用计算属性watch和计算属性的区别在vue 3中使用watchE

Java将时间戳转换为Date对象的方法小结

《Java将时间戳转换为Date对象的方法小结》在Java编程中,处理日期和时间是一个常见需求,特别是在处理网络通信或者数据库操作时,本文主要为大家整理了Java中将时间戳转换为Date对象的方法... 目录1. 理解时间戳2. Date 类的构造函数3. 转换示例4. 处理可能的异常5. 考虑时区问题6.

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert