[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;
}

运行结果:

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

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

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

相关文章
|
8天前
|
C++
C++(十一)对象数组
本文介绍了C++中对象数组的使用方法及其注意事项。通过示例展示了如何定义和初始化对象数组,并解释了栈对象数组与堆对象数组在初始化时的区别。重点强调了构造器设计时应考虑无参构造器的重要性,以及在需要进一步初始化的情况下采用二段式初始化策略的应用场景。
|
8天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
8天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
8天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
8天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
29天前
|
C++ 容器
C++中自定义结构体或类作为关联容器的键
C++中自定义结构体或类作为关联容器的键
30 0
|
29天前
|
存储 算法 搜索推荐
【C++】类的默认成员函数
【C++】类的默认成员函数
|
29天前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)
|
21天前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
|
27天前
|
编译器 C++
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决