一、类的六个默认成员函数
在C++中创建一个类,如果我们什么都不写,编译器也会自动生成六个各司其职的函数,他们分别是:
1.1负责初始化和清理的构造函数和析构函数
本文主要介绍这两个函数以及显示去写它们时要注意的问题。
1.2负责进行拷贝,复制的拷贝构造函数,赋值重载函数
本文主要介绍的有进行拷贝的拷贝构造函数以及显示写时的问题。
1.3负责进行取地址重载的普通对象取地址函数,const对象取地址函数
这两个一般很少自己实现,大多靠编译器直接生成。
二、构造函数
2.1构造函数的主要任务
构造函数主要完成对类中对象的初始化(类似C语言中Init函数的功能),而不是开空间创造对象。
2.2构造函数的特征
①构造函数的名字与类名相同
②构造函数无返回值,也不需要写void
③构造函数在对象实例化的时候默认调用
④构造函数可以进行重载
注:例如实例化Date类的对象,在主函数中要像下例中的d1或者d2,而不能像d3一样只写括号。
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 3, 10);
Date d2;
Date d3();//这一个是错的
return 0;
}
注:全局对象优先于局部对象进行构造,而局部对象构造按照出现顺序来,无论是否为static
2.3不写构造函数,只靠默认生成时的情况
编译器默认生成的构造函数
①对于内置/基本类型如int,char,doubole,指针等等不做处理,会生成随机值。
②对于自定义类型如class,struct等等会直接调用它们相对应的无参构造函数。(若对应类型没有无参构造函数,即显示写构造函数却没设置无参对应情况,会直接报错)
2.3补
这种对于内置类型的处理显然是有些不好用,所以C++11中特意做了补充,我们可以在定义类的参数列表时为内置类型提供缺省值。
如:
class MyQueue
{
Stack _st1;
Stack _st2;
int _size=0;
};
其中的int类型缺省值在编译器自动生成构造函数中就会被读到,并赋值为0.
2.4默认构造函数的定义
默认构造函数不是特指编译器自动生成的那个,而是在这个类中不传参数就可以调用的那个构造函数,它可以是全缺省,可以是自定义无参函数,也可以是编译器自动生成的函数。
注:①上述三种可能性只会同时存在一个。
②若在其他类中包含了此类的对象或是在主函数中创建了如
Date d1;
这种没有传参的对象,都必须有默认构造函数。
③默认构造函数的意义在于可以为对象列表只有自定义类型的类提供便利。
2.5小结
①一般情况下构造函数都需要我们自己去显示实现
②只有少数情况下可以让编译器自动生成构造函数,如2.3中例子MyQueue。
三、析构函数
3.1析构函数的主要任务
主要任务不是完成对对象本身的销毁,而是在要销毁对象时自动调用该函数。
3.2析构函数的特征
①析构函数的名字是在函数名前加一个~
②无返回值无参数
③有资源需要主动处理时才需要,在对象生命周期结束的时候会自动调用
④一个类只能有一个析构函数,不可以重载
⑤析构函数可以进行显式调用,但是构造函数不可以
注:析构函数调用顺序一般按照构造相反顺序来,但static会改变对象生命周期和作用域,导致延后析构。
3.3不写析构函数,只靠默认生成时的情况
与构造函数类似,内置/基本类型不做处理,自定义类型自动调用他们的析构函数。
3.4小结
①内置类型有资源需要清理的时候,需要显示写析构函数,如Stack,List
②下列两种情况可以靠自动生成
a.没有资源需要清理
b.内置类型没有资源需要清理,其他都是自定义类型成员,如MyQueue
四、拷贝构造函数
4.1拷贝构造函数的两个作用
①完成用同类型对象进行初始化
②在函数传参的时候,若是传了自定义类型,生成形参的过程会自动调用拷贝构造函数
(只要传引用就可以避免调用了,最好是传引用)
4.2拷贝构造函数的特征
①是构造函数的一个函数重载
②拷贝构造函数有且仅有一个参数,就是类的类型对象的引用类型(若是传值调用编译器会直接报错,因为形参创建这一过程会导致无穷递归)
注:参数最好加上const,因为是引用类型要避免被改
③拷贝构造函数的调用有通俗的写法(其实d3就是拷贝构造函数中的this指针)
Date d3(d2);
//等价于
Date d3=d2;
4.3自动生成拷贝构造函数的效果
在自动拷贝构造函数中,内置类型按字节的方式直接给,自定义类型会调用其拷贝构造函数完成拷贝。
如果不显示写,只靠编译器自动生成,会进行浅拷贝(又名值拷贝),会经历把每个字节挨个传过去的过程。
4.3.1问题描述
但这样会导致一些问题,例如有一个Stack的类,其中private部分如下
private:
STDataType* _array;
int _size;
int _capacity;
我们创建了两个该类的类型对象
Stack st1;
Stack st2;
若是st1中的栈我们压入了四个值,那么在执行
st2=st1;
这一步骤的时候,我们会发现两者全部有一个对象"_array"指向同一个空间内数组首元素,但是在析构函数中,他们由于是两个变量,所以会释放两次空间,这是不行的。
4.3.2解决方案
因此在这一类空间重复的问题中,我们需要进行深拷贝,即遇到资源管理问题另行申请空间。
Stack(const Stack& st)
{
_array = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (_array == NULL)
{
perror("malloc失败!");
return;
}
//写完这些还不够,因为根本无法复制数组内容还
memcpy(_array, st._array, sizeof(STDataType) * st._size/*st._capacity->3小时*/);
_capacity = st._capacity;
_size = st._size;
}
4.4小结
①如果没有进行资源管理,一般不写拷贝构造函数,如Date
②如果都是自定义类型,而内置类型没有进行指向资源(如没有指针),一般也不写拷贝构造,如MyQueue
③一般来说,不需要写析构就不要写拷贝构造函数
④相对的,如果内部有指针或是有一些值指向资源,需要显示写析构函数的时候,一般要写拷贝构造完成深拷贝,如Stack,Queue,List。