在我们用C语言实现栈或队列等数据结构时,我们通常要写一个函数 Init() 来初始化它们,每次使用前都要调用一次,在我们使用的过程中容易忘记初始化导致程序错误,于是在C++中有了类的构造函数来帮助我们初始化,编译器会在创建类对象时自动调用构造函数完成初始化,十分方便。
那么构造函数应该怎么定义呢?
1、构造函数的名字与类名相同
2、构造函数没有返回值
3、构造函数可以写多个构成重载
(就像有时我们只要一个空栈,有时需要用数组初始化而成的栈)
4、如果我们不写构造函数,那么编译器会自动生成一个默认的构造函数;
如果我们显式写了构造函数,编译器就不再默认生成,而是用我们写的构造函数初始化.
编译器自动生成的默认构造函数对于内置类型会初始化,对于自定义类型会去调用它的构造函数初始化。
5、我们写的无参数的构造函数、全缺省的构造函数、编译器默认生成的构造函数都叫默认构造函数,默认构造函数只能有一个。
6、默认构造函数都是不用传参数的构造,构造时不能加 () 。如下例所示
7、构造函数一般要写在公有区域(public),如果写在私有(private)那么无法在类外创建类对象
8、默认构造函数不会为我们开辟空间,所以我们写类似顺序表类的类时,如果想初始化就开空间,要自己写构造函数。
以日期类为例:
运行结果:
也可以构成重载,结果与上面一样,无参调用默认构造函数,有参数调用第一个。
我们可以写多个重载。
调用编译器自动生成的默认构造函数时:
新写一个类date,对于自定义类型Date去调用它的构造函数,对于内置类型int默认初始化为随机值。
因此,如果我们有类的成员全是自定义类型,那么我们可以不用写构造函数,直接用默认的
在C语言中,我们顺序表、链表等数据结构通常都要写一个Destroy()函数,帮助我们释放内存,为了避免很多人忘记写这个函数,C++类里面也添加了析构函数帮我们释放空间,就不用手动释放。
析构函数和构造函数类似,一个初始化,一个销毁,都是默认成员函数。
析构函数的特点:
1、无参数、无返回值
2、函数名是 :~类名
3、不能重载(因为无参数)
4、不写的话编译器也会生成默认的析构函数。
(默认析构函数,只释放内置类型的资源,对于自定义类型,会去调用它的析构函数)
5、因此,对于不需要手动释放空间的类(没有开辟空间),没必要写析构函数。
日期类的析构函数: 在类生命周期结束后自动调用
因为我们没有手动开辟空间,所以析构函数里面没必要写,编译器会自动释放空间。
运行结果:
默认析构函数: 只释放内置类型的资源,对于自定义类型,会去调用它的析构函数
析构函数析构的顺序:同一位置 (在相同的域创建) 后构造的先析构,是从后往前的顺序
而全局变量与静态变量最后析构,它们的顺序也是后构造的先析构。
拷贝构造是构造函数的重载,它的参数类型是它所属类的引用。
1、以Date类为例:Date(Date& d){//代码实现}
(参数为什么用引用?
因为用Date的话会形成无限递归,原因是如果不传引用,形参也是实参的拷贝,也要调用拷贝构造,调用拷贝构造又要拷贝实参....一直无限递归。而用Date*没有Date&方便,所以就用Date&了)
2、拷贝构造的使用方法:Date d1(d); d1就成为了d的拷贝。
3、如果不写,编译器会生成默认的拷贝构造
(默认的拷贝构造函数也叫做浅拷贝或值拷贝,它只会把成员变量的值拷贝一份放在新的类中,对于内置类型会直接值拷贝,对于自定义类型会去调用它的拷贝构造)
4、与析构函数类似,对于没有开空间的类没必要写构造函数,编译器默认的就够了,而对于有int*等通过手动开空间的类则必须要写,因为默认的拷贝构造只会进行值拷贝,在析构时就会同一块空间释放两次。
如栈类假设里面有一个成员函数 int* _a; 需要开空间:Stack s1(s);
如果不写,s1._a = s._a ,析构函数运行时会free(s1._a)又会free(s._a)
两者值相等,也就是同一块空间释放两次,就会报错。
5、类似上述栈类的类就要写拷贝构造,手动开辟一块空间,再把值拷贝进去,这样的拷贝也叫做深拷贝。
默认拷贝构造:进行值拷贝
此时s._a与s1._a值相同,析构函数会同一块空间释放两次,然后报错。
加上自己写的拷贝构造:
对于内置类型如:int,我们有 int + int 、int - int 、 ++int、int == int 等等一系列运算符操作
那如果我们想要对日期类进行这些操作,如:两个日期相差多少天,d1-d2,日期是否相等
d1==d2等操作我们应该怎么做到呢,这时候就可以用运算符重载来实现。
运算符重载:
1、使用方法:返回值 operator操作符(参数){//代码实现}
如:bool operator!=(const Date& d) {...};详情请看下面代码
2、运算符重载可以写在类里面,也可以写在类外面(赋值运算符重载例外,它是默认成员函数,只能写在类里面)。
3、对于两个操作数的操作符如:+、- 等在类里面只有一个参数,类外有两个参数,因为运算符重载也是函数,类里面的函数默认第一个参数都是this指针,所以类里面在上层看只有一个参数 。
4、前置++与后置++的重载
前置++是Date& operator++(){}
后置++是Date operator++(int){}
后置++之所以参数要带上int,是因为无法区分前置++与后置++,语法的规定用来辨别的。
5、不可重载的五个运算符
1、"." 点 取类成员的运算符 d._year
2、"::" 域作用限定符 详情可以看我前面的命名空间文章哦
3、".*" 点星 class A{ int* _a }; A a; a.*_a; 这个操作符用于取类里面的指针内容
4、"sizeof" 看占多少空间 sizeof(int);
5、"? : " 三目操作符 a == 0 ? a+1 : a-1;
放一个日期加减天数以及判断日期是否相等的重载,其余的类似实现即可。
前置++与后置++的实现,前置--与后置--类似
赋值运算符的重载与拷贝构造类似,只是使用形式不同
赋值运算符是 d1=d; 实现拷贝,拷贝构造是 d1(d);
1、赋值运算符重载也是默认成员函数,不自己写编译器会默认生成,与拷贝构造类似,但编译器默认生成的是浅拷贝或叫值拷贝,必要时可以自己完成深拷贝。
2、赋值运算符重载只能写在类里面(编译器才能判断你写没写,生不生成默认的)
3、生成方法:以日期类为例,因为可能要连续赋值,所以返回引用
Date& operator=(const Date& d){}详情请看下面代码实现哦。
4、其余特性参考上面的拷贝构造就可以了。
与拷贝构造类似,更多例子可以看看上面的拷贝构造哦
默认成员函数有六个,构造、析构、拷贝、赋值、取地址、const取地址
后两个一般直接用编译器默认生成的。
感谢大家观看!