目录
构造函数
1.概念
2.特性
Q.什么是默认构造函数?自动生成默认构造函数有什么用?
析构函数
1.概念
2.特性
3. 默认生成的析构函数有什么用?
拷贝构造函数
1.概念
2.特性
3.使用默认生成拷贝构造函数需要注意什么
赋值运算符重载
1.什么是运算符重载
2.赋值运算符重载
3.默认生成的赋值运算符重载
取地址重载
const取地址重载
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用。构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用
1. 函数名与类名相同。
2. 没有返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数(只要定义了就不会自动生成了),一旦用户显式定义编译器将不再生成。
6.无参的构造函数和全缺省的构造函数都称为默认构造函数(可以不用传参的构造函数就是默认构造参数),并且默认构造函数只能有一个(你定义一个无参的,再定义一个全缺省的,能通过编译,但是在调用的时候,不传入参数的情况下就会出现冲突)。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(默认就是指不用传参就可以调用的构造函数)。
7.构造函数的主要任务并不是开空间创建对象,而是初始化对象。
默认构造参数(不用传参就可以调用的构造函数就是默认构造函数)有三种:
C++把类型分成内置类型(基本类型包括指针)和自定义类型。
默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理(会调用这个自定义类型成员的默认构造函数)
如果调用了编译器自己生成的默认构造函数,则对内置类型不做处理,对自定义类型会在初始化列表中调用该类型的默认构造函数进行初始化(如果该类型没有默认构造函数,则需要显示地在初始化列表初始化自定义类型,否则编译会报错),如果调用的是自己写的构造函数,则编译器在初始化列表中也会调用该类型的默认构造函数
所以自动生成的默认构造函数的存在还是有必要的,当我们不显示写构造函数的时候,生成默认构造函数的初始化列表会帮助我们默认初始化对象,对内置类型不作处理,对自定义类型会调用自定义类型默认的构造函数
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要我们显式调用,而是在销毁对象时自动执行,完成对象中资源的清理工作(资源清理并不是笼统的指销毁对象,资源清理指的是如下代码对栈的处理,对指针的空间进行释放,将指针置空,将栈的元素数量置0这种操作)。
~Stack() {
free(_a);
_a = nullptr;
_top = capacity = 0;
}
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型(所以自然也就无法重载)。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(和自动生成的构造函数类似,对内置类型不做处理,自定义类型会去调用它的析构函数)。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
如果没有自己写的构造函数,会生成默认的构造函数,它对内置类型不做处理,对象生命周期结束的时候,会调用该自定义类型里面默认的构造函数。
如果对象的成员属性中有指针变量,而且在初始化的时候要new一块空间给它的,在这种需要自己直接对资源进行管理的情况下,需要自己重写析构函数对指针指向的空间进行释放,因为指针属于内置类型,而默认生成的析构函数对内置类型是不做处理的。
拷贝构造函数:是一种特殊的构造函数,在用对象初始化同一类的对象的时候,编译器会自动调用
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
3.类中如果没有涉及资源申请(没有自己在堆上面new一片连续空间)时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
4.拷贝构造函数典型调用场景:(编译器会自己调用拷贝构造函数来做这些事)
一般的类,使用默认自动生成拷贝构造就够用了,它对内置类型的成员会完成值拷贝,浅拷贝,对自定义类型的成员,去调用这个成员的拷贝构造。
只有像stack这样类,内部存在内置类型(Mtype*),而且在初始化的时候需要自己new一块空间进行初始化,需要这样自己直接管理资源的类,我们需要自己实现拷贝构造函数完成深拷贝。
默认生成的拷贝构造函数实际上是memcpy按字节拷贝值过去的,属于浅拷贝,所以如果栈里面存放的是内置类型的数组元素,则也不需要自己写拷贝构造,因为数组元素是存放在栈上的,通过memcpy的浅拷贝可以正确实现拷贝。
如果在需要深拷贝的地方使用了默认生成的拷贝构造函数进行了浅拷贝,会出现两个问题:
1.这两个对象的某个指针成员变量会指向同一片内存区域,这两个对象生命周期结束后调用析构函数的时候会进行两次析构函数,会对同一片内存空间进行两次delete操作,这个时候就会报错
2.像栈这样的对象,在插入元素的时候,两个栈共用一块空间,修改数据会互相影响。
原来内置类型能够使用的运算符,自定义类型无法使用,运算符重载,就是让原本已经存在的运算符有了新的用法和意义,让自定义类型也能够使用这些运算符。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)——参数列表个数由运算符决定
注意:
如果operator定义在类外面,则==需要传入两个运算符,并且访问的时候需要考虑对象里面的成员是私有的情况。
一般来说,operator定义在类里面,定义在里面的时候,需要少写一个参数,因为有一个是* const this(注意这里左操作数是this) d1==d2实际上是d1.operator(&d1,d2)
eg.:
右边的才是正确写法,加上返回值,为了符合连续赋值的定义,因为出了函数作用域以后,对象还在,所以可以使用引用返回,提高效率。
但是这里不能加const引用,如果是const那么(i=j)=k的时候会编译报错。注意右边参数内的&是引用,下面if判断里面的&是取地址
自动默认生成的拷贝构造函数:
1、内置类型的成员会完成值拷贝,浅拷贝。
2、自定义类型的成员,去调用这个成员的拷贝构造
但是如果是用一个对象去初始化另一个对象,即使使用赋值运算符,Date d1=d2,也会识别成d1(d2)这种写法,调用拷贝构造函数。
如果不显示写赋值运算符重载,编译器会为我们自动生成一个,和前面的拷贝构造函数一样,对内置类型会直接赋值,对自定义类型的成员,去调用这个成员的运算符重载。
Date* operator&()
{
return this ;
}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,比如不想让别人拿到真实的地址。
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,比如不想让别人拿到真实的地址。
const Date* operator&()const
{
return this ;
}