【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

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

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) - 视图对象

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

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

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�