C++的六大“天选之子“之“构造“与“析构“

简介: C++的六大“天选之子“之“构造“与“析构“

一、“构造函数"与"析构函数”

1.1 “构造函数”

不知道友友们有没有过这样一段经历.

在写一道数据结构的oj题时,信心满满的提交后,发现,编译居然编译不过,找了半天发现是忘记了进行初始化操作.

很多时候我们经常忘记初始化操作,但是初始化操作每次又是必做的,那么C++的祖师爷(本贾尼大佬)就贴心的给我设计了一个函数,这个函数就是构造函数.

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

69e1b59cd0e54296861f600ede6f6e2c.png

(1) 自动生成的"构造函数"

构造函数编译器会自动调用,那我们不写构造函数会怎样呢?

下面这段代码会报错吗?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;
class Date
{
public:
  void Print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  d1.Print();
  Date d2;
  d2.Print();
  return 0;
}

ca3c2e2cbbdc40f5bd2d8a32666a5500.gif

答案:

并不会报错,因为构造函数即使如果在类中没有显式定义,编译器也会自动生成一个默认的析构函数

运行结果:

-858993460–858993460–858993460
-858993460–858993460–858993460

但是.构造函数对于内置类型(例如:int ,double,指针等等)并不做处理,对于自定义类型(结构体,类,联合,枚举等等),会调用自定义类型自己的构造函数.

#include <iostream>
using std::cin;
using std::cout;
using std::endl;
class Time
{
public:
  Time()//Time类的默认构造函数
  {
    cout << "Time的构造函数" << endl;
    _hour = 0;
    _minute = 0;
    _second = 0;
  }
  void Print()
  {
    cout << _hour << "-" << _minute << "-" << _second << endl;
  }
private:
  int _hour;
  int _minute;
  int _second;
};
class Date
{
public:
  void Print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
    _time.Print();//打印时间
  }
private:
  int _year;
  int _month;
  int _day;
  Time _time;
};
int main()
{
  Date d1;
  d1.Print();
  Date d2;
  d2.Print();
  return 0;
}

运行结果:

Time的构造函数
-858993460–858993460–858993460
0-0-0
Time的构造函数
-858993460–858993460–858993460
0-0-0

解释:

虽然Date类我们没有写构造函数,但是编译器自动生成了一个隐藏的构造函数并且对自定义类型(这里是Time类)会调用自己的构造函数,所以Time类中的成员已经初始化了.

(2) 自定义"构造函数"

前面提到,内置类型,默认构造函数是不进行处理的,而这显然是设计的不合理的,所以在C++11中,打了个补丁,内置类型成员变量在类中声明时可以给默认值(缺省值),记住这里是默认值,并不是真的存储变量,因为声明是没有空间存储变量的.类只是图纸,并不能住人,只有实例化成对象后,才可以住人.

示例:下面这段代码并没有显示定义构造函数.

#include <iostream>
using std::cin;
using std::cout;
using std::endl;
class Date
{
public:
  void Print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  int _year=2020;//类中只是声明,并不存储变量,这里给出的是缺省值(很重要)
  int _month=10;
  int _day=1;
};
int main()
{
  Date d1;
  d1.Print();
  printf("\n");
  Date d2;
  d2.Print();
  return 0;
}

运行结果:

2020-10-1
2020-10-1

常见的三种默认构造函数:

1.无参构造函数

2.全缺省构造函数

3.带参构造函数


当然,学过函数重载的友友 们一定知道全缺省和无参会存在调用不明确的情况.

class Date
{
public:
  Date()//无参构造函数
  {
    _year = 2020;
    _month = 1;
    _day = 1;
  }
  Date(int year=2020,int month=1,int day=1)//全缺省构造函数
  {
    _year = year;
    _month = month;
    _day = day;
  }
  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;
};

运行结果:

总结:

1.构造函数的函数名与类名相同。无返回值(不是void)。

2.对象实例化时编译器自动调用对应的构造函数。

3.构造函数可以重载。(为了解决满足多样的初始化要求)

4.默认构造函数是对内置类型不进行处理,C++11中,打了个补丁,内置类型成员变量在类中声明时可以给默认值(缺省值)

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成,所以无参,全缺省,默认构造函数只能有一个.

1.2 “析构函数”

同样,我们设计构造函数的目的是帮助我们自动调用初始化操作,因为初始化操作经常忘记且又是必须的.那析构函数呢?

析构函数是很多时候我们经常忘记销毁操作,如果是动态申请的空间,很容易导致内存泄漏,那么我们亲爱的C++的祖师爷(本贾尼大佬)就又给我们设计了一个函数,这个函数就是析构函数.

析构函数:

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

构造函数名与类名相同,那析构函数呢?

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

示例:

#include <iostream>
using std::cin;
using std::cout;
using std::endl;
typedef int DataType;
class Stack
{
public:
  Stack(int capacity=5)//全缺省构造函数
  {
    cout << "Stack" << endl;
    _array = (DataType*)malloc(sizeof(DataType) * capacity);
    if (NULL == _array)
    {
      perror("malloc申请空间失败!!!");
      return;
    }
    _capacity = capacity;
    _size = 0;
  }
  void Push(DataType data)//压栈操作
  {
    CheckCapacity();
    _array[_size] = data;
    _size++;
  }
  ~Stack()//析构函数
  {
    cout << "~Stack"<< endl;
    if (_array)
    {
      free(_array);
      _array = NULL;
      _capacity = 0;
      _size = 0;
    }
  }
private:
  void CheckCapacity()
  {
    if (_size == _capacity)
    {
      int newcapacity = _capacity * 2;
      DataType* temp = (DataType*)realloc(_array, newcapacity *
        sizeof(DataType));
      if (temp == NULL)
      {
        perror("realloc申请空间失败!!!");
        return;
      }
      _array = temp;
      _capacity = newcapacity;
    }
  }
private:
  DataType* _array;
  int _capacity;
  int _size;
};
int main()
{
  Stack s;
  s.Push(1);
  s.Push(2);
  s.Push(3);
  s.Push(4);
  return 0;
}

运行结果:

Stack
~Stack

我们不难发现,即使我们不写函数调用,构造函数和析构函数都会被自动调用.不愧是祖师爷的亲儿子,两个函数都有特权.

总结:

1.析构函数名是类名前加上字符 ~。无参数无返回值类型。

2.与构造函数不同的是,析构函数不支持函数重载,所以一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

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

4.编译器生成的默认析构函数对自定义类型会调用它自己的析构函数,析构函数对于内置类型,不会有任何影响,因为内置类型的对象没有需要释放的资源。

5.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。但是呢,牛牛建议还是都写吧,否则忘记写就可能发生内存泄漏,反正不难.


本篇文章只提到了"构造"与"析构"函数这两个天选之子,还有四个没有登场哦!敬请期待吧!

目录
相关文章
|
2月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
67 1
|
7月前
|
设计模式 编译器 C++
C++中的构造方法和析构方法详解
C++中的构造方法和析构方法详解
50 0
|
4月前
|
JavaScript Java C语言
面向对象编程(C++篇3)——析构
面向对象编程(C++篇3)——析构
38 2
|
4月前
|
JavaScript 前端开发 Java
面向对象编程(C++篇2)——构造
面向对象编程(C++篇2)——构造
35 0
|
7月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
42 1
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
|
6月前
|
C++ 容器
C++之deque容器(构造、赋值、大小、插入与删除、存取、排序)
C++之deque容器(构造、赋值、大小、插入与删除、存取、排序)
|
6月前
|
C++ 容器
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
|
7月前
|
存储 安全 C语言
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(上)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
39 2
|
7月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(下)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
49 1
|
6月前
|
算法 C++ 容器
C++之vector容器操作(构造、赋值、扩容、插入、删除、交换、预留空间、遍历)
C++之vector容器操作(构造、赋值、扩容、插入、删除、交换、预留空间、遍历)
303 0