news 2026/7/2 2:46:22

一篇拿下!C++:类和对象(中)构造函数与析构函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一篇拿下!C++:类和对象(中)构造函数与析构函数
  • 第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
  • 第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?


二、构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数自动调用的特点就完美的替代的了Init。

构造函数的特点:
  1. 函数名与类名相同。
  2. 无返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  6. 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多人会认为默认构造函数仅仅是编译器默认生成的那个。实际上无参构造函数、全缺省构造函数也是默认构造函数,总结一下就是不传实参就可以调用的构造就叫默认构造。
  7. 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决,初始化列表,我们后续再来学习了解。

特别说明:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的原生数据类型, 如:int/char/double/指针等,自定义类型就是我们使用class/struct等关键字自己定义的类型。

举例说明(注释):

代码语言:javascript

AI代码解释

#include<iostream> using namespace std; class Date { public: //如果这里不自己显示构造函数,编译器会自动生成一个无参的默认构造函数 //默认构造函数:无参构造函数,全缺省构造函数,编译器默认生成的构造函数 //三种默认构造函数只能同时存在一个(第三个的原因不用多说,前两个是因为构成重载但传参啥都不传时会歧义) //1.自己实现的无参数的构造函数(默认构造函数的一种) Date()//函数名和类名相同,无返回值 { _year = 1; _month = 1; _day = 1; } //2.带参构造函数(可以和1同时存在)(不能和3同时存在,函数签名相同,构成了重定义)(不属于默认构造函数) Date(int year, int month, int day) { _year = year; _month = month; _day = day; } ////3.全缺省构造函数(很好用,结合了1,2的功能)(但是不能和1或者2同时存在)(默认构造函数的一种) //Date(int year = 1, int month = 1, int day = 1) //{ // _year = year; // _month = month; // _day = day; //} //总结:两种使用方案:1+2或者直接用3 void Print() { cout << _year << "/" << _month << "/" << _day << '\n'; } private: int _year; int _month; int _day; }; int main() { Date d1; //会自动初始化,调用1或者3,不能同时存在(我上面给3注释掉了,因为2和3也不能同时存在) Date d2(2025, 9, 1);//会调用3,不会有歧义 //Date d3();//这种写法是错误的 // 参考一下 Data func();你觉得它是对象还是函数呢? //注意:如果通过无参构造函数创建对象时,对象后面不用跟括号。 //否则编译器无法区分这里是函数声明还是实例化对象 d1.Print();//1,1,1 d2.Print();//2025,9,1 return 0; }

我们不写,编译器默认生成的构造对内置类型初始化没要求:(看注释)

代码语言:javascript

AI代码解释

class Date { public: void Print() { cout << _year << '/' << _month << '/' << _day << '\n'; } private: //内置类型 int _year; int _month; int _day; }; int main() { Date d1; d1.Print();//打印出来的结果是随机值 return 0; }

我们不写,编译器默认生成的构造,对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化:(注意注释)

代码语言:javascript

AI代码解释

//我们不写,编译器默认生成的构造,对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化 //比如之前我们写过的两个栈实现队列 #include<iostream> using namespace std; class Stack { public: Stack(int n=4)//在这里还是要自己实现一个的,不然会出跟上面一样的问题,我们来定义一个全缺省的 { _a = (int*)malloc(n * sizeof(int)); if (_a == nullptr) { perror("malloc fail!"); exit(1); } _top = 0; _capacity = n; } private: //内置类型 int* _a; int _top; int _capacity; }; class MyQueue { public: //编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化 private: //自定义类型 Stack _pushst; Stack _popst; //内置类型,但很奇怪,混在这里它却能处理,这里大家可以自己去试试 //int size = 0; }; int main() { MyQueue q; return 0; }

三.析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能,就像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。

析构函数的特点:
  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。 (这里跟构造类似,也不需要加void)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数。
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。
  6. 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
  7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时(而且这里涉及内置类型的处理),一定要自己写析构,否则会造成资源泄漏,如Stack。
  8. 一个局部域的多个对象,C++规定后定义的先析构。
举例说明:(注意看注释)

代码语言:javascript

AI代码解释

#include<iostream> using namespace std; class Stack { public: Stack(int n=4)//在这里还是要自己实现一个的,不然会出跟上面一样的问题,我们来定义一个全缺省的 { _a = (int*)malloc(n * sizeof(int)); if (_a == nullptr) { perror("malloc fail!"); exit(1); } _top = 0; _capacity = n; } //析构函数(跟构造函数写法类似,前面加个~) //在这里也需要自己定义,不然他也不处理内置类型,会造成内存泄漏 ~Stack() { if (_a) { free(_a); _a = nullptr; } _top = 0; _capacity = 0; } private: //内置类型 int* _a; int _top; int _capacity; }; class MyQueue { public: //编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化 //编译器默认生成MyQueue的析构函数调用了Stack的析构函数,释放的Stack内部的资源 //显示写析构,也会自动调用Stack的析构 /*~MyQueue() {}*/ private: //自定义类型 Stack _pushst; Stack _popst; //内置类型,但很奇怪,混在这里它却能处理,这里大家可以自己去试试 //int size = 0; }; int main() { Stack s; MyQueue q; //析构不用显示写出来,对象生命周期结束时自动调用 //后定义的先析构,所以这里先析构q,再析构s.可以调试观察 return 0; }

调试观察到当对象生命周期结束时确实都销毁掉了

课堂检测:

答案是B,解析如下:


四.括号匹配问题的优化(利用当前阶段所学知识)

对比一下用C++和C实现的Stack解决之前括号匹配问题isValid,我们会发现有了构造函数和析构函数确实方便了很多,不会再忘记调用Init和Destory函数了(下面的两个实现都需要借用一下栈这个数据结构先,我这里就不展现出来了)。

C语言实现:

代码语言:javascript

AI代码解释

bool isValid(char* s) { ST st; STInit(&st); char* pi = s; while (*pi != '\0') { if (*pi == '(' || *pi == '[' || *pi == '{') { STPush(&st, *pi); } else { //右括号取栈顶元素进行匹配 //栈不为空才能取 if (STEmpty(&st)) { STDestory(&st); return false; } char top = STTop(&st); if ((top == '(' && *pi != ')') || (top == '[' && *pi != ']') || (top == '{' && *pi != '}')) { STDestory(&st); return false; } //本次匹配就出栈 STPop(&st); } pi++; } //为空有效,非空无效 bool ret = STEmpty(&st) ? true : false; STDestory(&st); return ret; }

构造和析构的C++版本Stack实现:

代码语言:javascript

AI代码解释

bool isValid(char* s) { Stack st; char* pi = s; while (*pi != '\0') { if (*pi == '(' || *pi == '[' || *pi == '{') { st.Push(&st, *pi); } else { //右括号取栈顶元素进行匹配 //栈不为空才能取 if (st.Empty(&st)) { return false; } char top = st.Top(&st); if ((top == '(' && *pi != ')') || (top == '[' && *pi != ']') || (top == '{' && *pi != '}')) { return false; } //本次匹配就出栈 st.Pop(&st); } pi++; } //栈为空,返回真,说明数量都匹配 左括号多,右括号少匹配问题 return st.Empty(); }

对比图:



版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 0:23:30

一篇拿下!C++:类和对象(上)、封装、实例化和this指针详解

一、类的定义类的定义格式class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面分号不能省略。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。为了区分…

作者头像 李华
网站建设 2026/7/2 1:28:23

板栗矮砧密植:水肥一体化系统的铺设要点指南

认识板栗矮砧密植板栗矮砧密植&#xff0c;简单来说就是选用矮化砧木嫁接的板栗品种&#xff08;Dwarf rootstock variety&#xff09;&#xff0c;通过科学增加种植密度来提高产量的创新栽培模式。就像在有限的果园空间里&#xff0c;精心规划每一寸土地&#xff0c;让更多果树…

作者头像 李华
网站建设 2026/7/2 1:10:16

LeetCode 3606.优惠券校验器:分类 + 排序

【LetMeFly】3606.优惠券校验器&#xff1a;分类 排序 力扣题目链接&#xff1a;https://leetcode.cn/problems/coupon-code-validator/ 给你三个长度为 n 的数组&#xff0c;分别描述 n 个优惠券的属性&#xff1a;code、businessLine 和 isActive。其中&#xff0c;第 i 个…

作者头像 李华
网站建设 2026/7/1 23:40:33

ensp vlan实验作业

一、实验拓扑&#xff08;截自己的拓扑图&#xff0c;附加上地址分配情况&#xff09;二、实验需求1、全网可达&#xff1b;2、使用DHCP获取IP地址&#xff1b;三、配置思路1、在各个交换机上创建vlan2、分析链路类型&#xff0c;配置相应的接口为access口或是trunk口3、配置路…

作者头像 李华
网站建设 2026/7/1 20:15:18

24、网页开发技术综合解析

网页开发技术综合解析 1. 基础概念与环境搭建 在网页开发领域,有众多基础概念和环境搭建的要点需要掌握。首先是互联网相关的概念,互联网地址、IP 协议以及互联网服务提供商(ISP)是网络连接的基础。而在操作系统方面,Linux 是一个重要的选择。Linux 有多种发行版,如 Re…

作者头像 李华
网站建设 2026/7/2 0:20:57

39、SQL Server管理与监控全解析

SQL Server管理与监控全解析 1. 服务器端代码管理 在SQL Server中,创建T - SQL服务器端代码,如存储过程、视图、函数和触发器后,可能需要对其进行修改或删除。每种对象类型都支持与CREATE语法对应的ALTER和DROP T - SQL版本。 当使用ALTER修改服务器端编程对象(如存储过…

作者头像 李华