C++修炼之筑基期第二层——构造函数与析构函数

简介: C++修炼之筑基期第二层——构造函数与析构函数

000000000000000000000000000000、.png

目录


默认成员函数

构造函数

引例

构造函数的概念及特性

析构函数

析构函数的特性


文章导读


本章节我们将学习类的6个默认成员函数中的构造函数析构函数,并对比C语言阶段的内容来学习它们的各自的特性。


正文


默认成员函数


上一章中我们谈到,如果一个类中什么成员也没有,那么这个类就叫作空类。其实这么说是不太严谨的,因为一个类不可能什么都没有

当我们定义好一个类,不做任何处理时,编译器会自动生成以下6个默认成员函数

  • 默认成员函数:如果用户没有手动实现,则编译器会自动生成的成员函数。

11.png

  • 构造函数:主要完成初始化工作;
  • 析构函数:主要完成清理工作;
  • 拷贝构造:使用一个同类的对象初始化创建一个对象;
  • 赋值重载:把一个对象赋值给另一个对象;
  • 取地址重载普通对象取地址操作;
  • 取地址重载(const):const对象取地址操作;
  • 本章我们将学习两个默认成员函数——构造函数析构函数


构造函数


引例


在C语言阶段,我们实现的数据结构时,有一件事很苦恼,就是每当创建一个stack对象(之前叫作定义一个stack类型的变量)后,首先得调用它的专属初始化函数StackInit来初始化对象。

typedef int dataOfStackType;
typedef struct stack
{
  dataOfStackType* a;
  int top;
  int capacity;
}stack;
void StackInit(stack* ps);
//...
 int main()
 {
   stack s;
   StackInit(&s);
   //...
   return 0;
 }

这不免让人觉得有点麻烦。在C++中,构造函数为我们很好的解决了这一问题。


构造函数的概念及特性


构造函数是一个特殊的成员函数。构造函数虽然叫作构造,但是其主要作用并不是开辟空间创建对象,而是初始化对象

构造函数之所以特殊,是因为相比于其它成员函数,它具有如下特性

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时,编译器自动调用对应的构造函数
  4. 构造函数可以重载

举例

class Date
{
public:
  //无参的构造函数
  Date()
  {};
  //带参的构造函数
  Date(int year,int month,int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
void TestDate()
{
  Date d1;//调用无参构造函数(自动调用)
  Date d2(2023, 3, 29);//调用带参构造函数(自动调用)
}

特别注意

  • 创建对象时编译器会自动调用构造函数,若是调用无参构造函数,则无需在对象后面使用()。否则会产生歧义:编译器无法确定你是在声明函数还是在创建对象


错误示例

//错位示例
Date d3();


  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
class Date
{
public:
  //若用户没有显示定义,则编译器自动生成。
  /*Date(int year,int month,int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }*/
private:
  int _year;
  int _month;
  int _day;
};

默认生成构造函数,对内置类型成员不作处理;对自定义类型成员,会调用它的默认构造函数;

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int、char、double…,自定义类型就是我们使用class、struct、union等自己定义的类型。

🌼举例🌼

🌼默认构造函数对内置类型🌼

class Date
{
public:
  //此处不对构造函数做显示定义,测试默认构造函数
  /*Date()
  {}*/
  void print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
void TestDate1()
{
  Date d1;
  d1.print();
}

12.png

  • 如图所示,默认构造函数的确未对内置类型做处理。
  • 🌼默认构造函数对自定义类型🌼
class stack
{
public:
  //此处对stack构造函数做显示定义
  stack()
  {
    cout <<"stack()" << endl;
    _a = nullptr;
    _top = _capacity = 0;
  }
private:
  int* _a;
  int _top;
  int _capacity;
};
class queue
{
public:
  //此处不对queue构造函数做显示定义,测试默认构造函数
  /*queue()
  {}*/
private:
  //自定义类型成员
  stack _s;
};
void TestQueue()
{
  queue q;
}


15.png

如图所示,在创建queue对象时,默认构造函数对自定义成员_s做了处理,调用了它的默认构造函数stack()。

这一波蜜汁操作让很多C++使用者感到困惑与不满,为什么要针对内置类型和自定义类型做不同的处理呢?终于,在C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:


内置类型成员变量在类中声明时可以给默认值;

举例🌼

class Date
{
public:
//...
  void print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  //使用默认值
  int _year = 0;
  int _month = 0;
  int _day = 0;
};
void TestDate2()
{
  Date d2;
  d2.print();
}

88.png

  • 默认值:若不对成员变量做处理,则使用默认值。
  1. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

🌼举例🌼


class Date
{
public:
  //无参的默认构造函数
  //Date()
  //{
  //}
  //全缺省的默认构造函数
  Date(int year = 0, int month = 0, int day = 0)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
private:
  int _year = 0;
  int _month = 0;
  int _day = 0;
};


析构函数


析构函数与构造函数的特性相似,但功能有恰好相反。构造函数是用来初始化对象的,析构函数是用来销毁对象的。


需要注意的是,析构函数并不是对对象本身进行销毁(因为局部对象出了作用域会自行销毁,由编译器来完成),而是在对象销毁时会自动调用析构函数,对对象内部的资源做清理(例如stack _s中的int* a)。

同样,有了析构函数,我们再也不用担心创建对象(或定义变量)后由于忘记释放内存而造成内存泄漏了。


🌼举例🌼

class Stack
{
public:
  Stack()
  {
    //...
  }
  void Push(int x)
  {
    //...
  }
  bool Empty()
  {
    // ...
  }
  int Top()
  {
    //...
  }
  void Destory()
  {
    //...
  }
private:
  // 成员变量
  int* _a;
  int _top;
  int _capacity;
};
void TestStack()
{
  Stack s;
  st.Push(1);
  st.Push(2);
  //过去需要手动释放
  st.Destroy();
}


析构函数的特性


  1. 析构函数名是在类名前加上字符 ~
  2. 无参数
  3. 无返回值
  4. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
  5. 析构函数不能重载

举例


class Date
{
public:
  Date()
  {
    cout << "Date()" << endl;
  }
  ~Date()
  {
    cout << "~Date()" << endl;
  }
private:
  int _year = 0;
  int _month = 0;
  int _day = 0;
};
void TestDate3()
{
  Date d3;
  //d3生命周期结束时自动调用构造函数
}

432.png

为便于观察,我们可以在析构函数内部写点儿东西。

  1. 编译器生成的默认析构函数,对自定类型成员调用它的析构函数

举例

class stack
{
public:
  //此处对stack构造函数做显示定义
  stack()
  {
    cout <<"stack()" << endl;
    _a = nullptr;
    _top = _capacity = 0;
  }
  ~stack()
  {
    cout << "~Stack()" << endl;
    free(_a);
    _a = nullptr;
    _top = _capacity = 0;
  }
private:
  int* _a;
  int _top;
  int _capacity;
};
class queue
{
public:
  //此处不对queue构造函数做显示定义,测试默认构造函数
  /*queue()
  {}*/
private:
  //自定义类型成员
  stack _s;
};
void TestQueue1()
{
  queue q;
}

12、.png

这里可能有小伙伴会好奇:为什么析构函数不像构造函数那样区分内置类型与自定义类型呢?

答案是:因为内置类型压根不需要我们担心清理工作,在其生命周期结束时会自动销毁。而自定义类型需要担心,因为自定义类型里可能含有申请资源(例如:malloc申请内存须手动释放)。

如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如stack类。

本章到这里就结束了,下一章节我们将学习剩余的4个默认成员函数~


目录
相关文章
|
7天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
33 5
|
14天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
45 4
|
2月前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
75 30
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
20 1
|
1月前
|
C++
C++构造函数初始化类对象
C++构造函数初始化类对象
21 0
|
1月前
|
C++
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
21 0
|
3月前
|
编译器 C++
C++的基类和派生类构造函数
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。 在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。 这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数。 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数:
|
4月前
|
C++ 运维
开发与运维函数问题之析构函数在C++类中起什么作用如何解决
开发与运维函数问题之析构函数在C++类中起什么作用如何解决
44 11
|
4月前
|
编译器 C++
【C++】详解构造函数
【C++】详解构造函数
|
5月前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。