一个类在我们不写任何内容时,系统也会自动生成一些成员函数,它们被称为"默认成员函数"。
本文我们将介绍其中的析构函数与构造函数。
一.构造函数
构造函数类似于我们在实现Stack时的Init,具有初始化实例对象的功能,其特点有以下这些:
1.名字与类相同
2.无返回值
3.对象实例化时系统自动调用
4.能够重载
5.如果类中没有显式定义构造函数,那C++编译器会自己生成一个无参的默认构造函数(注:不传实参都可以叫默认构造,而非特指系统生成的)
6.编译器生成的构造函数对于内置类型成员变量的初始化没有要求,而对于自定义类型,则会调用这个成员变量的默认构造函数,如果它没有,那就会报错。
cpp
//自定义类型1:默认构造
class HasDefault
{
public:
HasDefault()
{
cout << "HasDefualt的默认构造被调用" << endl;
}
};
//自定义类型2:带参构造
class NoDefault
{
public:
NoDefault(int x)
{
cout << "NoDefault的带参构造被调用, x = " << x << endl;
}
};
//自定义类型3:编译器生成默认构造函数
class Outer
{
public:
private:
int _a;
double _b;
HasDefault _hd;
//NoDefault _nd; //此自定义成员变量无默认构造,加上此行会报错
};
int main()
{
Outer o;
cout << "Outer o 创建成功" << endl;
return 0;
}
二.析构函数
析构函数的功能类似于Stack中的Destroy,通常负责对象中的资源释放工作。但需要注意,析构函数并非是销毁对象本身。
析构函数特点如下:
1.函数命是在类名前加上~
2.一个类中只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
3.对象生命周期结束时,系统会自动调用
4.无参无返回值(无需加上void)
5.与构造函数类似的,不写析构函数时,编译器自动生成的对内置类型成员不做处理,但对于自定义成员,不论我们写不写,自定义类型成员会调用它的析构函数
6.一个局部域的多个对象,C++规定:后定义的先析构
第六点我们详细说说。
"后定义的先析构"原因在于局部对象存在栈上,比如下面这个例子:
cpp
void func() {
A a; // ① 先定义
B b; // ② 后定义
C c; // ③ 最后定义
} // 出作用域:c 先析构 → b → a(后定义的先析构)
如果包含全局对象和static修饰的对象呢?
cpp
Global g;
void func()
{
static StaticLocal sl;
Local l1;
Local l2;
}
int main()
{
func();
return 0;
}
由于刚刚那句话只在同类对象之间成立**,而static的生命周期是程序级,应该归到程序级销毁**,全局对象最晚。
因此,顺序应该是l2 - l1 - sl - g
如果func被调用两次呢?
static局部是"第一次执行到才会构造,之后不再构造,程序结束时才析构"
三.拷贝构造函数
浅拷贝
拷贝构造函数是一种特殊的构造函数,其用途是对自定义类型对象进行各种拷贝操作:
cpp
Point a(1,2); //调用普通构造函数
Point b = a; //调用拷贝构造,将a值拷贝给b
passByValue(b);; //传值传参触发拷贝构造,将实参数值拷贝给形参
Point c = passByValue(); //传值返回触发拷贝构造,把返回值拷贝给c
如果这里自己写拷贝构造,那就是这样:
cpp
class Point
{
int _x, _y;
public:
//普通构造函数
Point(int x = 0, int y = 0)
{
_x = x;
_y = y;
cout << "构造 Point( " << _x << ", " << _y << " )" << endl;
}
//拷贝构造函数
Point(const Point& other)
{
_x = other._x;
_y = other._y;
cout << "拷贝构造:复制了(" << other._x << "," << other._y << ")" << endl;
}
void show () const
{
cout << "(" << _x << ',' << _y << ")" << endl;
}
};
你会发现,拷贝构造的参数和普通构造不同------拷贝构造函数的第一个参数 必须使用类类型对象的使用,使用传值方式,编译器会直接报错。
原因在于,传值这个行为也需要调用拷贝构造,结果调用的拷贝构造函数参数仍然是传值,结果又调用,以此类推,无限递归调用。
拷贝构造函数可以有多个参数,但是第一个参数外,后面的必须有缺省值。
深拷贝
上面所述称之为"浅拷贝",基本和赋值差不多,一般用编译器自己生成的函数没啥问题,但深拷贝是一个大坑。在动态申请内存空间的情景中,一定要自己去写拷贝构造。
比如这个场景:
cpp
int main()
{
Stack st1;
st1.Push(1);
st1.Push(2);
Stack st2 = st1;
return 0
};
st1有自己的空间,如果使用浅拷贝,那st1空间的地址就会被简单赋值给st2,导致二者共用同一块空间,导致增删数据混乱,析构时也会析构两次,程序崩溃。
这个问题也很好解决,只需要手写拷贝时,申请一份新空间给st2,把数据复制到这个空间就行了。
cpp
//拷贝构造
Stack(const Stack& st)
{
_a = (STDataType*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror("malloc");
return;
}
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_top = st._top;
_capacity = st._capacity;
}
赋值与拷贝构造的区别
请看示例
cpp
#include <iostream>
using namespace std;
class Demo {
int _id;
public:
Demo(int id = 0) : _id(id) {
cout << "构造 " << _id << endl;
}
Demo(const Demo& other) : _id(other._id) {
cout << "拷贝构造 " << _id << endl;
}
Demo& operator=(const Demo& other) {
_id = other._id;
cout << "赋值 operator= " << _id << endl;
return *this;
}
};
int main() {
Demo a(1);
Demo b(2);
Demo c = a;
c = b;
Demo d(a);
return 0;
}
在主函数中,Demo c = a是拷贝构造,因为c是"新对象",没内容,用a初始化,而后续的c = b就是赋值,因为c已经存在了,所以把b的值拷贝给c。(注:内置的"="是没办法这样用的,这里是使用了符号重载,专门"增加了"这个功能)