当前位置: 首页 > backend >正文

【C++】类和对象——默认成员函数(中上)

类的默认成员函数

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

一个类,我们不写的情况下编译器会默认生成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载我们稍微了解⼀下即可。默认成员函数很重要,也比较复杂,我们要从两个方面去学习:

• 第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。

• 第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?

我们先通过一张图看来简单的了解一下这六个默认成员函数,以及它们的功能。

1、构造函数、

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时用来完成对象的初始化。

构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,同时构造函数会在对象实例化的同时自动调用,这也避免了我们在平时写代码时忘记初始化而导致程序崩溃,这样就完美的替代的了Init。

1.1、构造函数的特点

1、函数名与类名相同

2、无返回值。(返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)

3、对象实例化时系统会自动调用对应的构造函数

4、构造函数可以重载。(无参构造函数和全缺省构造函数)

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

6、无参构造函数全缺省构造函数、我们不写构造函数时编译器默认生成的构造函数,都叫做默认构造函数。  

还要注意两点:

1、这三个函数有且只有一个存在,不能同时存在。因为无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。

2、很多人会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调用的构造就叫默认构造

7、我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决。

下面我们通过代码来实际感受构造函数的特点和它的好处。

1.2、带参构造函数

class Date
{
public: //成员函数//带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;//cout << this->_year <<"/"<<this->_month <<"/"<<this->_day << endl;//this指针}
private: //成员变量int _year;int _month;int _day;
};

对于带参的构造函数,在实例化对象的时候,要注意在对象的后面加上(实参1,实参2,...),否则编译器会报错。

int main()
{//调用的实例化写法Date d1(2025,8,15);d1.Print();return 0;
}

1.3、默认构造函数

1、全缺省构造函数(给所有的参数一个缺省值)

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;}
private:int _year;int _month;int _day;
};

全缺省构造函数在实例化对象时,实际有两种实例化方式:类+对象名类+对象名(实参1,实参2...)。注意不能类+对象名( ),编译器同样会报错。

int main()
{Date d1;Date d2(2025);Date d3(2025, 8);Date d4(2025, 8, 15);d1.Print();d2.Print();d3.Print();d4.Print();return 0;
}

2、无参构造函数

class Date
{
public://无参构造函数Date(){_year = 1;_month = 1;_day = 1;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};

如果通过无参构造函数创建对象时,对象后面不用跟括号,否则编译器报错,因为无法区分这里是函数声明还是实例化对象。

int main()
{//实例化对象的正确写法Date d1;d1.Print();return 0;
}

3、编译器默认生成的构造函数,当我们不显式定义构造函数时。

class Date
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};

但是,我们会发现,在很多时候编译器自动生成的默认构造函数并不能达到我们对实例化对象初始化的目的,所以,很多时候还需要我们自己实现构造函数。

下面我们再看一个能够用编译器默认生成的构造函数实现初始化的例子;就是用栈实现队列

typedef int STDataType;
class Stack
{
public://全缺省构造函数Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}// ...
private:STDataType* _a;size_t _top;size_t _capacity;
};// 两个Stack实现队列
class MyQueue
{
public://编译器默认生成MyQueue的构造函数调⽤了Stack的构造函数,完成了两个成员的初始化private:Stack pushst;Stack popst;
};int main()
{MyQueue mq;return 0;
}

通过调试就可以很清楚的看到,当实例化对象st和mq的同时,实例化对象中的成员变量被初始化了。

2、析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧中的, 函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。

析构函数的功能类比我们之前Stack实现的Destroy(栈的销毁)功能,而像Date没有 Destroy,因为Date中的成员函数中局部对象在函数结束栈帧的同时就销毁了,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。

2.1、析构函数的特点

1析构函数名是在类名前加上字符 ~ 。

2、 无参数无返回值。(这里跟构造类似,也不需要加 void )

3、 一个类只能有一个析构函数若未显式定义,系统会自动生成默认的析构函数

但是,我们会发现,在很多时候编译器自动生成的默认析构函数并不能达到我们对实例化对象内部资源释放的目的,所以,很多时候还需要我们自己实现析构函数。

4、 对象生命周期结束时,系统会自动调用析构函数

5、 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。

内置类型成员指的是一个类或结构体中,类型为语言自带的基础数据类型的成员变量。

这些内置类型通常包括:

  • 整数类型(如intlongshort等)
  • 浮点类型(如floatdouble
  • 字符类型(如char
  • 布尔类型(如bool

6、 还需要注意的是当我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。

7、 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue(栈实现队列);但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如Stack。

8、一个局部域的多个对象,C++规定后定义的先析构

局部对象是存储在栈上的。当程序执行到定义对象的语句时,对象被压入栈中;当局部域结束时,栈进⾏出栈操作。⽽栈的操作特点是 “后进先出(LIFO)”, 后定义的对象后被压⼊栈,所以在栈弹出(对象析构)时,后压⼊栈的对象就会先被弹出栈,即后定义的对象先析构 。

同样下面我们通过代码来实际感受析构函数的特点和它的好处

2.2、编译器默认析构函数

typedef int STDataType;
class Stack
{
public://全缺省构造函数————初始化Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}
private:STDataType* _a;size_t _top;size_t _capacity;
};

我们通过编译来看编译器默认生成的析构函数是否能够完成对实例化对象内部资源的释放。

并没有达到我们预期的结果,所以还得自己实现,防止内存泄漏。

但是用栈实现队列时,对MyQueue实例化出的对象的内部申请的资源,编译器默认生成MyQueue的析构函数调用了Stack的析构函数,释放的两个成员(pushst和popst)内部的资源。

typedef int STDataType;
class Stack
{
public://全缺省构造函数————初始化Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}//析构函数————内部资源销毁~Stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _top;size_t _capacity;
};// 两个Stack实现队列
class MyQueue
{
public://编译器默认生成MyQueue的构造函数调⽤了Stack的构造函数,完成了两个成员(pushst和popst)的初始化;//编译器默认生成MyQueue的析构函数调⽤了Stack的析构函数,释放的两个成员(pushst和popst)内部的资源。
private:Stack pushst;Stack popst;
};

调试:

2.3、实现析构函数

typedef int STDataType;
class Stack
{
public://全缺省构造函数————初始化Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}//析构函数————内部资源销毁~Stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _top;size_t _capacity;
};

调试:

本期的分享就到此结束,类的默认成员函数也确实是复杂(难搞),实在是有点力不从心了(苦笑),剩下的默认成员函数我们下期继续分享,如果大家觉得内容还不错,请点个赞支持一下吧!!!

http://www.xdnf.cn/news/17918.html

相关文章:

  • OpenCV安装及配置
  • 【C 学习】06-算法程序设计举例
  • 基于51单片机的智能吊灯
  • 零改造迁移实录:2000+存储过程从SQL Server滑入KingbaseES V9R4C12的72小时
  • Obot MCP 网关:用于安全管理 MCP 服务器采用的开源平台
  • 大模拟 Major
  • 《吃透 C++ 类和对象(中):const 成员函数与取地址运算符重载解析》
  • Horse3D游戏引擎研发笔记(六):在QtOpenGL环境下,仿Unity的材质管理Shader绘制四边形
  • 复杂度扫尾+链表经典算法题
  • 《P1194 买礼物》
  • JAVA 关键字
  • OpenCV---getStructuringElement 结构元素获取
  • MySQL知识点(上)
  • LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。
  • 推荐一款高性能状态机管理解决方案
  • 专题三_二分_x 的平方根
  • Linux软件编程(五)(exec 函数族、system、线程)
  • 【Go语言-Day 36】构建专业命令行工具:`flag` 包入门与实战
  • Struts文件泄露漏洞分析与修复方案
  • Swift 实战:用最长递增子序列算法解“俄罗斯套娃信封”问题(LeetCode 354)
  • Unity 实现逼真书本翻页效果
  • Vue响应式系统在超大型应用中的性能瓶颈
  • 深入浅出的 RocketMQ-面试题解析
  • 力扣hot100 | 普通数组 | 53. 最大子数组和、56. 合并区间、189. 轮转数组、238. 除自身以外数组的乘积、41. 缺失的第一个正数
  • LeetCode 面试经典 150_数组/字符串_最长公共前缀(20_14_C++_简单)(暴力破解)(求交集)
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘fairseq’问题
  • 关于Manus AI与多语言手写识别的技术
  • 学习笔记与效率提升指南:编程、记忆与面试备考
  • 中级统计师-会计学基础知识-第一章 账户与复试记账
  • diffusers学习--stable diffusion的管线解析