【C++类和对象】构造函数与析构函数

2024-04-14 16:52

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

💞💞 前言

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:C++入门至进阶
这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖

目录

  • 💞💞 前言
  • 1.类的6个默认成员函数
  • 2.构造函数
    • 2.1概念
    • 2.2构造函数特性
  • 3.析构函数
    • 3.1概念
    • 3.2析构函数特性
  • 4.结语

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

  • 如果一个类中什么成员都没有,简称为空类。
    例如:
class Date {};
  • 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
C++类的默认成员函数包括:

  1. 默认构造函数(Default Constructor):当对象被创建时,如果没有显式地定义构造函数,则会调用默认构造函数来初始化对象。
  2. 拷贝构造函数(Copy Constructor):当对象通过另一个已存在的对象进行初始化时,会调用拷贝构造函数来创建一个新的对象。
  3. 移动构造函数(Move Constructor):在C++11中引入的特性,当对象通过右值(临时对象)进行初始化时,会调用移动构造函数来创建一个新的对象,并且可以直接“窃取”右值对象的资源,避免额外的拷贝开销。
  4. 析构函数(Destructor):当对象被销毁时,会调用析构函数来做一些清理工作,例如释放动态分配的内存或关闭打开的文件等。
  5. 拷贝赋值运算符(Copy Assignment Operator):当一个已存在的对象被赋值给另一个对象时,会调用拷贝赋值运算符来完成赋值操作。
  6. 移动赋值运算符(Move Assignment Operator):在C++11中引入的特性,当一个右值(临时对象)被赋值给另一个对象时,会调用移动赋值运算符来完成赋值操作,并且可以直接“窃取”右值对象的资源,避免额外的拷贝开销。

这些默认成员函数在没有显式地定义时,默认会自动生成,但也可以显式地定义来覆盖默认的实现。

C++类的6个默认成员函数根据它们各自的作用和特性可以被分为以下三类:
在这里插入图片描述

2.构造函数

2.1概念

C++构造函数是用于初始化对象的特殊成员函数。它的名称与类名相同,并且没有返回类型。构造函数在对象创建时自动调用,并且可以接受参数。
例如:
对于以下Date类:

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;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,所以C++提出了构造函数,构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.2构造函数特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特性如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载:
class Date
{
public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};void TestDate(){Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数}

这里需要注意如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明,例如如下的代码:
Date d3();//声明了d3函数,该函数无参,返回一个日期类型的对象
结果如下:
在这里插入图片描述

  1. 如果类中没有显式定义构造函数,则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;int _month;int _day;};int main(){// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;}
  1. 关于编译器生成的默认成员函数,很多人会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?
    例如:
    在这里插入图片描述

d对象调用了编译器生成的默认构造函数,但是d对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

理由如下

  • 首先C++把类型分成内置类型(基本类型)和自定义类型。
    内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型;
  • 而C++生成的默认构造函数对内置类型不做处理,也就是不初始化,对自定义类型则调用它的默认构造函数:

例如:

class Time		//自定义类型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;
}

结果如下:
在这里插入图片描述

从上述例子中,可以看出在实例化d1时,对于它的自定义类型,Date生成的默认构造函数调用了自定义类型Time的默认构造函数

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
例如:

class Date
{
private:// 基本类型(内置类型)int _year = 1;int _month = 2;int _day = 3;// 自定义类型Time _t;
};

结果如下:
在这里插入图片描述

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

例如:

class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1;
}

结果如下:
在这里插入图片描述

3.析构函数

3.1概念

  • 通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

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

3.2析构函数特性

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

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
    例如:
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3)	//默认构造函数{cout << "Stack" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data)	//成员函数{// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack()	//析构函数{cout << "~Stack" << endl;if (_array){free(_array);	//释放空间_array = NULL;	//剩下的可以写可以不写,因为函数调用完成会自动回收形参_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
void TestStack()
{Stack s;s.Push(1);s.Push(2);
}

结果如下:
在这里插入图片描述

我们可以看到在创建对象时自动调用了默认构造函数来初始化,并且在对象销毁时自动调用了析构函数来清理malloc的空间;

  1. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数(与默认构造函数类似对内置类型不做处理,对自定义类型调用该类型的析构函数)。
class Time   //自定义类型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;
}

结果如下:
在这里插入图片描述

对于内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。而Date没有显式提供析构函数,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数

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

4.结语

构造函数和析构函数都是特殊的成员函数,构造函数在对象被创建时自动调用,它的主要作用是初始化对象的数据成员;
而C++析构函数在对象销毁时会自动调用,其主要作用是在对象销毁时执行清理操作,例如释放动态分配的内存和关闭文件。以上就是有关C++构造函数和析构函数的全部内容啦~ 完结撒花 ~🥳🎉🎉

这篇关于【C++类和对象】构造函数与析构函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

SpringBoot项目中Redis存储Session对象序列化处理

《SpringBoot项目中Redis存储Session对象序列化处理》在SpringBoot项目中使用Redis存储Session时,对象的序列化和反序列化是关键步骤,下面我们就来讲讲如何在Spri... 目录一、为什么需要序列化处理二、Spring Boot 集成 Redis 存储 Session2.1

Java实例化对象的​7种方式详解

《Java实例化对象的​7种方式详解》在Java中,实例化对象的方式有多种,具体取决于场景需求和设计模式,本文整理了7种常用的方法,文中的示例代码讲解详细,有需要的可以了解下... 目录1. ​new 关键字(直接构造)​2. ​反射(Reflection)​​3. ​克隆(Clone)​​4. ​反序列化

MySQL 字符串截取函数及用法详解

《MySQL字符串截取函数及用法详解》在MySQL中,字符串截取是常见的操作,主要用于从字符串中提取特定部分,MySQL提供了多种函数来实现这一功能,包括LEFT()、RIGHT()、SUBST... 目录mysql 字符串截取函数详解RIGHT(str, length):从右侧截取指定长度的字符SUBST

C++类和对象之初始化列表的使用方式

《C++类和对象之初始化列表的使用方式》:本文主要介绍C++类和对象之初始化列表的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C++初始化列表详解:性能优化与正确实践什么是初始化列表?初始化列表的三大核心作用1. 性能优化:避免不必要的赋值操作2. 强

C++迭代器失效的避坑指南

《C++迭代器失效的避坑指南》在C++中,迭代器(iterator)是一种类似指针的对象,用于遍历STL容器(如vector、list、map等),迭代器失效是指在对容器进行某些操作后... 目录1. 什么是迭代器失效?2. 哪些操作会导致迭代器失效?2.1 vector 的插入操作(push_back,

Kotlin运算符重载函数及作用场景

《Kotlin运算符重载函数及作用场景》在Kotlin里,运算符重载函数允许为自定义类型重新定义现有的运算符(如+-…)行为,从而让自定义类型能像内置类型那样使用运算符,本文给大家介绍Kotlin运算... 目录基本语法作用场景类对象数据类型接口注意事项在 Kotlin 里,运算符重载函数允许为自定义类型重

Pandas中统计汇总可视化函数plot()的使用

《Pandas中统计汇总可视化函数plot()的使用》Pandas提供了许多强大的数据处理和分析功能,其中plot()函数就是其可视化功能的一个重要组成部分,本文主要介绍了Pandas中统计汇总可视化... 目录一、plot()函数简介二、plot()函数的基本用法三、plot()函数的参数详解四、使用pl

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

Python的time模块一些常用功能(各种与时间相关的函数)

《Python的time模块一些常用功能(各种与时间相关的函数)》Python的time模块提供了各种与时间相关的函数,包括获取当前时间、处理时间间隔、执行时间测量等,:本文主要介绍Python的... 目录1. 获取当前时间2. 时间格式化3. 延时执行4. 时间戳运算5. 计算代码执行时间6. 转换为指