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

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

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


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


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


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

  🔝🔝


1. 前言

本章重点:

本篇文章着重讲解类中的

两个默认函数,分别为:

构造函数,析构函数

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

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

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

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

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

于是C++引入了这几个函数

可以有效的解决这些问题!


2. 构造函数

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

特性:

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

需要注意的点:

  • 构造函数是特殊的成员函数
    不能将它与普通函数对比
  • 构造函数的任务是初始化对象
    而不是开辟空间创造对象

举例说明:

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. 构造函数的特性

如果使用者没有显示写构造函数

系统就会自动生成一个默认构造函数

比如:

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类(日期类)

这个新概念有什么用?

  • 编译器自动生成的构造函数
    不会处理内置类型,它们是随机值
  • 然而自动生成的构造会处理自定义类型
    它会去调用自定义类型的默认构造

举例说明:

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. 显示写的全缺省的构造函数

请看下面的代码:

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. 对析构函数的理解

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

那你可能会问:

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

答案是: 不!

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

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

请看以下代码:

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. 对默认析构函数的理解

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

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

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 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

例如:

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


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

相关文章
|
10天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
37 4
|
11天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
35 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
24 3
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
19 1