【C++基础(六)】类和对象(中) --构造,析构函数

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++初阶之路

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++

🔝🔝


类和对象-中

  • [1. 前言](#1. 前言)
  • [2. 构造函数](#2. 构造函数)
  • [3. 构造函数的特性](#3. 构造函数的特性)
  • [4. 对默认构造函数的理解](#4. 对默认构造函数的理解)
  • [5. 对默认构造函数的补充](#5. 对默认构造函数的补充)
  • [6. 析构函数](#6. 析构函数)
  • [7. 对析构函数的理解](#7. 对析构函数的理解)
  • [8. 对默认析构函数的理解](#8. 对默认析构函数的理解)
  • [9. 总结以及拓展](#9. 总结以及拓展)

1. 前言

本章重点:

本篇文章着重讲解类中的

两个默认函数,分别为:
构造函数,析构函数

并且介绍类的六个默认函数

(其他三个在后面章节讲解)

我们平时写数据结构时,比如:栈和队列

经常忘记写或者调用初始化函数
使得栈类中的变量是随机值,易出错

有时忘记调用销毁函数,导致内存泄漏
非常的不方便,不好用!

于是C++引入了这几个函数
可以有效的解决这些问题!


2. 构造函数

构造函数,顾名思义是用于初始化的函数

特性:

  • 函数名与类名相同
  • 无返回值
  • 对象实例化时自动调用对应的构造函数
  • 构造函数可以重载

需要注意的点:

  • 构造函数是特殊的成员函数
    不能将它与普通函数对比

  • 构造函数的任务是初始化对象
    而不是开辟空间创造对象

举例说明:

cpp 复制代码
 class Date
 {
 public:
	  Date(int year, int month, int day)//构造函数
	  {
	       _year = year;
	       _month = month;
	       _day = day;
	  }
	  Date()//无参的构造函数
	  {
	  	_year = 1900;
	  	_month = 1;
	  	_day = 1;
	  }
 private:
      int _year;
      int _month;
      int _day;
 };

int main()
{
	 Date d1; // 调用无参构造函数
	 Date d2(2023, 7, 24);//调用含参的构造
}

注:构造函数是实例化对象时就调用

对象后面跟一个括号来调用!


3. 构造函数的特性

如果使用者没有显示写构造函数
系统就会自动生成一个默认构造函数

比如:

cpp 复制代码
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;
 }

对代码的解释:

屏蔽掉自己写的构造函数时
编译器会自动生成一个,d1在
实例化时就会去调用编译器生成的

然而当放开自己写的构造函数后
会报错,因为自己实现的构造函数
没有缺省值,并且d1实例化时没有传参


4. 对默认构造函数的理解

可能你们会疑惑:
既然编译器会自己生成构造函数
那我是不是写不写构造函数都可以了?

带着此疑问引出一个新概念:

内置类型和自定义类型

  • 内置类型是C++语言提供的类型
    比如: int/char类型

  • 自定义类型是用户使用class类
    定义出来的类型,如:Date类(日期类)

这个新概念有什么用?

  • 编译器自动生成的构造函数
    不会处理内置类型,它们是随机值

  • 然而自动生成的构造会处理自定义类型
    它会去调用自定义类型的默认构造

举例说明:

cpp 复制代码
class Time
{
public:
	 Time()//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;
};

对代码的理解:

Date类没有显示写构造函数
所以编译器会自动生成一个构造函数
此构造函数不会处理内置类型
所以成员变量:
year,month,day都是随机值

然而此构造函数会处理自定义类型
它会去调用Time类的默认构造函数
将成员变量_t初始化


5. 对默认构造函数的补充

你可能会疑惑:上面的代码中
Time类显示写了构造函数
为啥还能被称为默认构造函数被调用?

默认构造函数可以是下面的类别:

  1. 编译器自动生成的默认构造
  2. 显示写的无参的构造函数
  3. 显示写的全缺省的构造函数

请看下面的代码:

cpp 复制代码
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;
};

上面两种写法都是默认构造函数!
但是它们不能同时存在
因为当实例化对象时没有传参,系统
不知道是调用全缺省函数还是无参的函数


6. 析构函数

现在我们知道一个对象是怎么被初始化的

那么一个对象又是怎么被销毁的呢?

析构函数的概念:

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

析构函数的特性:

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

  • 析构函数无参数无返回值类型

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

  • 析构函数不能重载!

  • 对象生命周期结束时
    C++编译系统系统自动调用析构函数

注:析构函数和构造函数一样
是特殊的函数,不能将它与普通函数相比


7. 对析构函数的理解

有了前面构造函数的铺垫
析构函数就容易理解了,和我们想的一样
编译器自动生成的默认析构函数
只处理自定义类型,而内置类型不会管

那你可能会问:

既然默认析构函数不会处理内置类型
那么内置类型是不是不会销毁?

答案是: 不!

内置类型会在对象生命周期结束时
将它在栈区的空间还给操作系统
所以析构函数不处理在栈区的变量
也没有问题

但是有些变量的指针指向堆区
有由动态开辟出来的空间
这份空间不会主动还给操作系统
需要我们手动写析构函数来释放!

请看以下代码:

cpp 复制代码
typedef int DataType;
class Stack
{
public:
	 Stack(size_t capacity = 3)//构造函数
	 {
		 _array = (DataType*)malloc(sizeof(DataType) * capacity);
		 if (NULL == _array)
		 {
			 perror("malloc申请空间失败!!!");
			 return;
		 }
		 _capacity = capacity;
		 _size = 0;
	 }
 
	 ~Stack()//析构函数
	 {
		 if (_array)
		 {
			 free(_array);
			 _array = NULL;
			 _capacity = 0;
			 _size = 0;
		 }
 }
private:
	 DataType* _array;
	 int _capacity;
	 int _size;
};
void TestStack()
{
 Stack s;
}

这段代码中,存在在堆区申请的空间
所以不能使用编译器默认生成的析构
而是要用自己写的析构函数去free掉
这块堆区的空间


8. 对默认析构函数的理解

和构造函数一样,默认析构函数
会去调用自定义类型的析构函数

可以用下面这段代码来验证一下:

cpp 复制代码
class 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;
}

当d的生命周期结束时
系统会自动调用析构函数
而Date类没有显示写析构函数
就会使用编译器自动生成的析构

此析构函数会去调用Time的析构函数

所以屏幕上就会打印:~Time()


9. 总结以及拓展

构造函数是析构函数是对立的
一个用于初始化,一个用于销毁对象调用
掌握它们对后面类和对象的学习很重要

拓展1:

类的六个默认函数:

现在已经学了构造和析构函数!


拓展2:

C++11新增内容:

C++11新增了一个功能:

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

例如:

cpp 复制代码
class Time
{
public:
	 Time()
	 {
	 cout << "Time()" << endl;
	 _hour = 0;
	 _minute = 0;
	 _second = 0;
 }
private:
	 int _hour = 1;//声明的时候给缺省值
	 int _minute = 1;
	 int _second = 1;
};

如果用户没有显示传参
那么hour,minute,second
的值都会初始化为1


🔎 下期预告:拷贝构造函数 🔍

相关推荐
奶糖趣多多31 分钟前
Redis知识点
数据库·redis·缓存
阿伟*rui31 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
CoderIsArt2 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck3 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei3 小时前
java的类加载机制的学习
java·学习
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍