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

C++_CH18_构造函数与析构函数

C++_CH18_构造函数与析构函数

1 类的默认成员函数

在编写类的时候,C++编译器会默认生成6个默认的函数,但是不显示出来:
在这里插入图片描述
需要关注以下两个方面:

第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?

2 构造函数

构造函数是一种特殊类型的方法,他在类的实例化时被使用。

2.1 一个例子来说明

创建一个Entity类,并给他写一个method,Print(),这样实例化后,调用Print就可以将x,y的值打印到控制台:

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e;e.Print();std::cin.get();return 0;
}

在低版本的编译器中,得到的输出是两个随机值,当然现在的编译器得到的结果是

0,0

因为X、Y是public的,因此我们可以打印X,Y试试

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e;std::cout<<e.X<<e.Y<<std::endl;e.Print();std::cin.get();return 0;
}

还是对于高版本的编译器,输出为

00
0,0

但是对于低版本的编译器,则会报错:未初始化局部变量。

2.2 诞生需求

默认为低版本编译器。我们需要在创建实例时,就把X和Y初始化为0,0而不是一个随机值。要一个新的方法。

2.3 Init()方法

在class中添加Init(),来初始化

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点void Init(){X = 0.0f;Y = 0.0f;}void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e;e.Init();std::cout<<e.X<<e.Y<<std::endl;e.Print();std::cin.get();return 0;
}

此时,在低版本编译器下输出也为:

00
0,0

成功完成了初始化。

但是:这样我们每创建一个实例,都需要调用一次Init,这样十分的麻烦。

2.5 诞生新需求

新需求:我们创建实例的时候就自动完成了Init()类似的操作,不需要额外的代码

2.6 构造函数的诞生

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

e.g.

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点Entity(){X = 0.0f;Y = 0.0f;}void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e;std::cout<<e.X<<e.Y<<std::endl;e.Print();std::cin.get();return 0;
}

output:

00
0,0

此为一个不带参数的构造函数。

2.6.2 带参数的构造函数

以下为带参数构造函数。注意,有两个构造函数,名字都是Entity,此为函数重载。但是构造函数没有重载。

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点Entity(){}Entity(float x,float y){X = x;Y = y; //用参数给成员变量赋值。}void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e(10.0f,11.0f);std::cout<<e.X<<e.Y<<std::endl;e.Print();std::cin.get();return 0;
}

output

1011
10,11

2.6.3不想要默认的构造函数

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点Entity() = delete;void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e;std::cout<<e.X<<e.Y<<std::endl;e.Print();std::cin.get();return 0;
}

因为没有默认的构造函数,此时会报错。

3 析构函数

析构函数是构造函数的对立。它用于销毁实例。

3.1 析构函数的特点

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

3.2 实例

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点Entity(){X = 0.0f;Y = 0.0f;std::cout<<"完成对象创建"<<std::endl;}~Entity(){std::cout<<"销毁对象完成"<<std::endl;}void Print(){std::cout<<X<<','<<Y<<std::endl;}
};int main()
{Entity e;std::cout<<e.X<<e.Y<<std::endl;e.Print();std::cin.get();return 0;
}

output

完成对象创建
00
0,0
销毁对象完成

析构函数是在对象的生命结束时运行,这个例子无法看出析构函数实在main函数结束时运行的,我们稍加修改:

#include <iostream>class Entity
{
public:float X,Y; //二维坐标下的(x,y)点Entity(){X = 0.0f;Y = 0.0f;std::cout<<"完成对象创建"<<std::endl;}~Entity(){std::cout<<"销毁对象完成"<<std::endl;}void Print(){std::cout<<X<<','<<Y<<std::endl;}
};void func()
{Entity e;e.Print();
}int main()
{func();std::cout<<"HellO"<<std::endl;std::cin.get();return 0;
}

只要hello是在“销毁对象完成”之后打印的,就证明析构函数是在函数作用域结束的时候调用的。

output:

完成对象创建
0,0
销毁对象完成
HellO

成功证明。
当然用visual studio调试也可以证明。

总之析构函数就防止内存泄露的。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • PyQt5库学习之QFileDialog.getExistingDirectory函数
  • Qt Linguist 短语书批量导入翻译.ts导入.qph
  • 我Github的问题解决了!
  • WPF 所有的控件和每个控件的主要作用和应用场景
  • Docker笔记-Docker Dockerfile
  • 有关JS下隐藏的敏感信息
  • Vue.js 的 Mixins
  • 极狐GitLab CI/CD 功能合集(超详细教程)
  • 在Linux服务器上如何实现自动化部署?
  • Activiti7《第三式:破刀式》——工作流中的刀锋利刃
  • AIGC论文查重是什么?
  • Redis 集群策略详解
  • freemobus阅读笔记
  • SpringBoot+Thymeleaf图书管理系统
  • 【ARM】A64指令介绍及内存屏障和寄存器
  • 【笔记】你不知道的JS读书笔记——Promise
  • 【刷算法】从上往下打印二叉树
  • ES6系列(二)变量的解构赋值
  • Golang-长连接-状态推送
  • Java教程_软件开发基础
  • SpiderData 2019年2月16日 DApp数据排行榜
  • SpiderData 2019年2月25日 DApp数据排行榜
  • Spring Boot快速入门(一):Hello Spring Boot
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • - 概述 - 《设计模式(极简c++版)》
  • 手写一个CommonJS打包工具(一)
  • 提醒我喝水chrome插件开发指南
  • 想写好前端,先练好内功
  • 由插件封装引出的一丢丢思考
  • 终端用户监控:真实用户监控还是模拟监控?
  • ionic入门之数据绑定显示-1
  • #1015 : KMP算法
  • #include<初见C语言之指针(5)>
  • #传输# #传输数据判断#
  • (3) cmake编译多个cpp文件
  • (LeetCode) T14. Longest Common Prefix
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (算法)N皇后问题
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • .NET C# 配置 Options
  • .net core 6 集成和使用 mongodb
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .Net Core 微服务之Consul(三)-KV存储分布式锁
  • .net 生成二级域名
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .NET单元测试
  • .NET多线程执行函数
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .NET下的多线程编程—1-线程机制概述
  • .NET运行机制
  • .NET周刊【7月第4期 2024-07-28】
  • /proc/vmstat 详解
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解
  • [BUUCTF 2018]Online Tool