> 文章列表 > C++类和对象-3

C++类和对象-3

C++类和对象-3

承接上一篇博客中内容,讲述完类和对象中构造函数内容之后,这篇博客我们来讲述类和对象中,析构函数的内容。

目录

1.析构函数

2.拷贝构造函数

3.浅拷贝与深拷贝

1.析构函数

在类和对象的构建当中,类中的对象会通过构造函数来完成初始化,与之相应的存在析构函数来对类中的对象进行销毁(释放),它的特点是:

  1. 只能被声明为公有(public);
  2. 析构函数同类名,不过前加会加上~;
  3. 析构函数无任何参数,不能被重载;
  4. 一个类中只能存在一个析构函数;
  5. 无相应的返回类型;
  6. 析构函数在释放一个对象中会被自动调用;

同样的,当一个类中不存在析构函数,系统会默认生成一个析构函数来完成对类中对象的释放。

#include<iostream>class Point {
private:int x;int y;
public:Point() {};Point(int xvalue, int yvalue) :x(xvalue), y(yvalue) {}void Print() {std::cout << "(" << x << "," << y << ")" << std::endl;}~Point() {std::cout << "析构函数被调用\\n";}
};int main() {Point a(1, 1);a.Print();return 0;
}

当我们执行上述代码,得到结果如下:

我们可以很明显的看出,当我们主函数退出之前,会释放程序创建资源。那么对于类而言,便会调用它的析构函数来释放它所创建的对象,于是我们便在结果上观察到“析构函数被调用”的打印内容。

2.拷贝构造函数

拷贝构造函数也是为我们提供的一种初始化类中对象的方式,它可以用一个已经存在的对象去初始化另外一个对象,并且拷贝构造函数必须为构造函数的一个重载类型,其-【-特点如下:(正如它的名字,通过拷贝来构造对象。)

  1. 拷贝构造函数名字与类相同,不能指定返回类型;
  2. 拷贝构造函数只能由一个参数,并且该参数是该类中其他某一对象引用;
  3. 拷贝构造函数不能被显示调用。

我们照例在Point类中进行书写:

编译并执行得到结果如下:

可以很明显看出,当创建对象a时,会调用构造函数;当通过a来初始化b时,会调用拷贝构造函数。最后在函数退出时,会释放资源,调用两次析构函数,对a和b全部进行释放。

最后,当出现以下三种情况时,拷贝构造函数会被自动调用来初始化对象:

  1. 当用类中一个已经存在的对象去初始化另外一个对象时;
  2. 当函数的形参时类的对象,进行形参和实参结合时;
  3. 当函数的返回值是类的对象,函数执行完成返回调用者时。

对于上述第3条,我们进行一个格外补充说明,当我们设计程序如下这种形式:

对于不同的编译器会存在不同的打印结果,使用vs一些较老版本会将”拷贝构造函数被调用“打印三次。这是因为函数返回值是对象,需要返回给调用者时,程序会默认生成一个临时对象来调用拷贝构造函数,并完成对象的初始化。

对于gcc编译器而言,它会将函数返回对象进行优化,即函数return时并不会创建额外的临时对象和调用拷贝构造函数,所以我们只能在结果中看到”拷贝构造函数被调用“只打印了两次。分别是函数调用之前,和函数执行语句中的两次。

3.浅拷贝与深拷贝

对于类的构造函数,析构函数和拷贝构造函数,当我们不去主动设计它们,系统也会默认为我们生成一份。对于前两者的函数参数列表和函数体都是空的,并不存在内容;不过对于系统默认生成的拷贝构造函数,其中参数列表会具备const引用的类,函数体中会存在对参数类的对象初始化。

对于系统默认生成的拷贝构造函数,在对数据成员进行逐一初始化时,通常并不会存在问题。但是出现一些系统难以完成拷贝赋值的数据类型(如指针……)时,系统默认生成的拷贝构造函数执行时就会发生错误。

于是我们首先对拷贝进行分为,将系统默认生成的拷贝构造函数对成员数据进行逐一初始化的过程,称为“浅拷贝”;对于需要进行我们自己设计拷贝构造函数,来对额外数据成员进行拷贝初始化时,称为“深拷贝”。

我们编写代码如下:

#include<iostream>typedef int DataType;class Stack {
private:DataType*_array;size_t _size;size_t _capacity;
public:Stack(size_t capacity = 10) {_array = (DataType*)malloc(capacity * sizeof(DataType));if (_array == nullptr) {std::cout << "malloc failed\\n";return;}_size = 0;_capacity = capacity;}void Push(const DataType& data) {_array[_size] = data;_size++;}~Stack() {if (_array) {free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
};int main() {Stack s1;s1.Push(1);s1.Push(2);Stack s2(s1);return 0;
}

通过执行上述代码,编译器便会报错,因为我们通过对象s2来对对象s1进行拷贝,s2和s1中的内容便会完全一致。于是当函数运行结束退出时,会调用两次析构函数来分别对s1和s2进行释放,可是s1和s2所指向的内存空间是同一块,所以会导致同一块内存空间被销毁两次,程序崩溃。

总结:当我们类中对象涉及到资源的申请时,使用系统默认的拷贝构造函数函数进行拷贝过后,在析构函数进行资源释放时会造成同一块内存销毁多次,导致程序崩溃。浅拷贝是行不通的,那么一旦类中涉及到资源申请,我们一定要设计自己的拷贝构造函数,进行深拷贝。