上一次我们说到了C++的类,我们还知道在C++的类中可以写函数,而且这个函数也属于这个类,我们可以直接使用这个类的对象去调用这个函数。
今天我们来看一下C++的六个默认成员函数(这里说是6个,但是重要的只有4个)
构造函数
在C语言中,如果我们定义了一个对象,我们想初始化它,那么我们就需要自己写一个Init()函数,去初始化它,但是在我们的C++中,我们有一个函数叫做构造函数,它的功能就是初始化对象,在我们定义一个对象的时候它会自动调用对应的构造函数,来完成初始化。
下面我们来看一下
我们现在看到,如果我们现在想初始化这个 Date类型的对象,我们需要手动的去调用这个类里面的Initial()函数,我们来看一下
我们来看一下是否可以完成初始化
我们看到,我们已经成功的初始化了,不过这个是C语言的方式,我们刚才说了,我们在C++中有默认的构造函数,但是什么是构造函数呢??
构造函数就是函数名和类名相同,且没有返回值,这就是构造函数,我们C++中如果没有写构造函数,那么编译器会给你自动生成一个,如果写了,那么编译器就不会自动生成,默认构造函数,就是不需要传参就可以调用的函数,就叫做默认构造函数,默认构造函数有三种,1. 编译器自动生成 2.自己写一个无参的构造函数,3.写一个全缺省的构造函数,这几个构造函数,都是可以不传参就可以调用的,所以称之为默认的构造函数,而构造函数在对象定义的时候会自动调用,所以我们不用担心创建对象的售后忘记调用初始化函数。
这个是我们自己写的一个默认的构造函数,我们看一下在创建对象的时候是否会调用它
我们看到确实自动调用了构造函数
那么我们刚开始说了,构造函数就是Init()函数,那么我们想知道如果我们自己不写构造函数,编译器自动生成的构造函数,会帮我们干什么呢?
我们看到,它里面的值是乱的,在我们现在看来就是什么都没有干,那么它真的就什么都没有干吗?
其实不是的,在我们的C++中我们有的类型大概可以分为两种,第一种是内置类型(C++里面本来就拥有的,例如:int double 指针等), 还有一种就是自定义类型(struct/class)定义的类型,我们就称之为是自定义类型,而而我们的编译器自动生成的构造函数,对于内置类型不处理,而我们的自定义类型,会调用它自己的构造函数,我们来看一下
我们现在有一个 stack的类,我们想定义一个myqueue,里面有两个stack的对象
那么我们来看一下,我们的这个myQueue的对象的编译器自动生成的默认构造函数 ,会不会有用呢??
我们看到,我们确实调用了两次stack的构造函数,那么我们的构造函数到底需不需要写呢??
我们在看一下默认构造函数可以干什么,我们想一下,默认构造函数,对内置类型不处理,对自定义类型会调用它自己的构造函数,所以,如果我们一个类中都是自定义类型的成员变量,那么我把就不需要写对应的构造函数,编译器自动生成的就可以了,但是却如果里面有我们的内置类型,并且我们需要对内置类型需要完成初始化,我们就需要自己实现构造函数。
析构函数
在C语言中我们的变量销毁的时候,如果有资源需要清理,我们需要调用对应的destroy函数,来释放对应的资源,但是在C++中,我们还有一个默认成员函数,叫做析构函数
我们先说一下析构函数是什么,析构函数,就是在对象声明周期结束的时候会自动调用的函数,如果我们不写编译器会自动生成一个,析构函数的函数名是类名前面加~,并且也没有返回值,也没有参数,所以无法重载
我们来看一下
我们来看一下,是否会自动调用我们的析构函数,在对象生命周期结束的时候
我们看到,它足额是调用了对应的析构函数,那么我们还是上一个问题,如果我们不写,默认生成的析构函数会做什么呢??
我们编译器自动生成的析构函数,对内置类型不做处理,对自定义类型进行调用它自己的析构函数,这个是不是和上面的构造函数很相似
我们来看一下
我们继续看一下
该类中有两个stack的对象,在我们muQueue对象的生命周期结束的时候,我们的satck的析构函数会不会调用
我们看到,调用了对应的析构函数
那么我们的析构函数需要自己写吗?或者是什么时候不需要自己写
1. 类中的成员变量都是自定义类型
2. 类中的成员变量,在生命周期结束的时候没有需要释放的资源
这两种时候我们不需要自己实现析构函数,编译器自动生成也可以,但是如果里面有需要自己释放的资源那么我们就需要自己实现析构函数
就像我们的stack类,它里面有_a,它需要自己开辟空间,在对象生命周期结束的时候需要释放掉malloc的内存,所以这个时候我们需要自己写析构函数
拷贝构造
我们在说拷贝构造之前,我们先说一个语法(也就是规定),在C++中我们的内置类型的传参都是拷贝,而我们的自定义类型的传参都是拷贝构造,所以我们的自定义类型的传参都是拷贝构造的
现在我们说一下什么是拷贝构造,拷贝构造也是构造函数的一种,不过拷贝构造的参数只能是自己的类型的引用
如果是我们的日期类的话我们就需要这样写,那么我们为什么要是引用呢?
我们来看一下如果不是引用会怎么样?
我们看到,报错了
但是为什么呢?
我们在刚开始的时候说了,在我们的C++中,我们的自定义类型的传值,就是拷贝构造,那么而我们想一下 请看下面
如果我们想要用d1 来拷贝构造 d3那么我们就会调用拷贝构造函数,所以我们会把d1传值给d3,但是我们的传值又是拷贝构造,而拷贝构造又需要传值,所以我们就陷入了死递归中,但是由于这里的语法就已经报错了,所以我们没办法看到这样的情景,所以我们如果拷贝构造不加引用的话就会报错,其实地址也可以,但是地址不好看。
那么如果我们不写拷贝构造,编译器自动生成的拷贝构造会干什么??
如果我们不写编译器自动生成的拷贝构造会完成,值拷贝(也就是浅拷贝),所以我们的Date类型的对象其实不写拷贝构造也可以,但是如果我们又开辟了内存的类型,我们就要自己写,因为默认的拷贝构造,会对对象进行按照字节序的浅拷贝,如果我们开了空间的话,我们的经过我们的浅拷贝,我们开辟的同一块空间就会被两个对象同时使用
我们来看一下
我们现在将Date类的拷贝构造屏蔽掉,我们来看一下,是否能完成拷贝
我们看到,我们确实对d3完成了拷贝,那么如果是我们需要开辟空间的呢??
我们还是刚才的stack类,我们并没有写该类对应的拷贝构造函数,如果我们之间拷贝会怎么样呢??
我们看到崩溃了,可是为什么呢??
我们想一下,我们用s1 对s2进行浅拷贝,那么我们的s2里面的_a变量也指向s1里面_a指向的那块空间,但是在我们对象生命周期结束的时候我们会调用改对象的析构函数,所以我们会对s1 和s2析构由于我们析构完s2后我们的_a指向的空间已经被释放了,但是我们又对s1进行析构,我们再一次对s1中_a指向的空间进行释放,所以,我们对同一块空间释放了两次,所以我们的进程奔溃,我们来看一下我们是否完成的是浅拷贝
我们的_a指向的空间都是相同的,所以我们如果开辟了空间,就不能进行浅拷贝,需要深拷贝,这个我们之后会说,所以这里同一块空间被释放两次导致崩溃。
今天先只说这三个默认函数,剩下的我们下次说!