[C++] 类与对象(中)类中六个默认成员函数(1)上

简介: [C++] 类与对象(中)类中六个默认成员函数(1)上

1、类的六个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。


2、构造函数

2.1 构造函数的概念

我们这里来看看日期类的初始化:

class Date
{
public:
    void Init(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 d1;
    d1.Init(2022, 7, 5);
    d1.Print();
    return 0;
}

运行结果:

我们刚接触C++,一定会这样初始化。

如果我们实例化的对象太多了,忘记初始化对象了,程序运行出来的结果可能就是随机值了,也可能出问题。

这里C++祖师爷想到了,为我们设计了构造函数。

我们先来看一下忘记初始化直接打印的结果:

这里是随机值,那这是为什么呢?我们接着往下看。

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

1. 函数名与类名相同。

2. 无返回值(不是void,是不用写)。

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

4. 构造函数可以重载。


我们先写一个日期类的构造函数来看看:

class Date
{
public:
  Date()//构造函数,无参构造
  {
    cout << "Date()" << endl;
    _year = 1;
    _month = 1;
    _day = 1;
  }
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day;
  }
private:
  int _year;
  int _month;
  int _day;
};

我们测试看一下:

我们main函数里没有调用构造函数,但是这里打印了我们做的标记,这里我们实验出来了实例化对象时构造函数是自动调用的。

我们再来看将我们写的构造函数注释掉会发生什么:

我们能看到,注释掉后,仍然能打印出来,只不过是随机值。因为当我们不写,编译器会自动生成默认的构造函数,并自动调用。


C++将类型分为内置类型(基本类型):如int,char,double,int*……(自定义类型*也是);


自定义类型:如class,struct,union……。


并且这里我们能看出来,对于内置类型的成员不会处理,在C++11,支持成员变量给缺省值,算是补漏洞了。

2.2.1 构造函数的重载:

class Date
{
public:
  Date()
  {
    cout << "Date()" << endl;
    _year = 1;
    _month = 1;
    _day = 1;
  }
  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 d1;
  d1.Print();
  Date d2(2023, 8, 1);//这里初始化必须是这样写,这是语法
  d2.Print();
    return 0;
}

运行结果:

注意:我们在实例化对象的时候,当调用的构造函数没有参数,不能在对象后加括号,语法规定。

如果这样写,编译器分不清这到底是函数声明还是在调用。d2不会混淆是因为有传值,函数声明不会出现那样的写法。

2.2.2 全缺省的构造函数:

我们其实可以将上面的两个构造函数合并为一个

class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
    Date d1;
  d1.Print();
  Date d2(2023, 8, 1);
  d2.Print();
  Date d3(2023, 9);
  d3.Print();
    return 0;
}

运行结果:

全缺省构造函数才是最适用的。无参构造与全缺省可以同时存在,但是不建议这样写,虽然不报错,但是在调用全缺省时我们不想传参,编译器不知道我们到底想调用哪个构造,会产生二义性。

我们再看用两个栈实现队列的问题:

class Stack
{
public:
  Stack(int n = 4)
  {
    if (n == 0)
    {
      _a = nullptr;
      _size = -1;
      _capacity = 0;
    }
    else
    {
      int* tmp = (int*)realloc(_a, sizeof(int) * n);
      if (tmp == nullptr)
      {
        perror("realloc fail");
        exit(-1);
      }
      _a = tmp;
      _size = -1;
      _capacity = n;
    }
  }
  void Push(int n)
  {
    if (_size + 1 == _capacity)
    {
      int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
      int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
      if (nullptr == tmp)
      {
        perror("realloc fail:");
        exit(-1);
      }
      _a = tmp;
      _capacity = newcapacity;
    }
    _a[_size++] = n;
  }
  int Top()
  {
    return _a[_size];
  }
  void Pop()
  {
    assert(_size > -1);
    _size--;
  }
  void Destort()
  {
    free(_a);
    _a = nullptr;
    _size = _capacity = 0;
  }
  bool Empty()
  {
    return _size == -1;
  }
private:
  int* _a;
  int _size;
  int _capacity;
};
class MyQueue
{
private:
  Stack _pushst;
  Stack _popst;
};

一般情况下都需要我们自己写构造函数,决定初始化方式,成员变量全是自定义类型,可以考虑不写构造函数。会调用自定义类型的默认构造函数。


总结:无参构造函数、全缺省构造函数、我们不写编译器默认生成的构造函数,都可以认为是默认构造函数,并且默认构造函数只能存在一个(多个并存会产生二义性)。

3、析构函数

3.1 析构函数的概念

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

3.2 特性

析构函数是特殊的成员函数,其特征如下:

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

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(对内置类型不做处理,自定义类型会调用它自己的析构函数)。注意:析构函数不能重载。

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


我们先来看日期类的析构:

class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
        cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
  ~Date()
  {
    cout << "~Date()" << endl;
    _year = 0;
    _month = 0;
    _day = 0;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  Date d2;
    return 0;
}

运行结果:

我们这里可以看出析构函数也是自动调用的。

我们不写,编译器自动生成默认的析构函数。

析构函数的调用顺序跟栈类似,后实例化的先析构。

相关文章
|
21天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
21 4
|
21天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
19 4
|
21天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
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个默认成员函数)
23 3
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
23 2
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
18 1
|
1月前
|
C++
C++番外篇——日期类的实现
C++番外篇——日期类的实现
76 1