类之构造函数与析构函数

来源:互联网 发布:js数组添加到指定位置 编辑:程序博客网 时间:2024/05/17 08:14

       类的构造函数与析构函数是类中非常重要的两个成员函数,这两个函数都是由系统自动调用,而且在对象的声明周期中都只调用一次,当没有显示定义这两个函数时,系统就会生成默认的来进行调用,也就是说这两个函数是必须有的,本文则是对这两个函数进行探讨。我们以一个简单的时间类作示例:

class Date{public:        private:int year;int month;int day;};

一、构造函数

       构造函数是一个特殊的成员函数,其名字与类名相同,创建对象时由系统自动调用,而且在对象的整个生命周期内只调用一次以保证对象的每个数据成员都有一个合适的初始值,构造函数通常有三个作用,构建对象、初始化对象和类型转换,构造函数通常有四种,我们来具体探讨一下。

       (1)默认构造函数

        类如果没有显式定义构造函数,编译器会合成一个默认的构造函数,该构造函数中什么工作都不做。只要显式定义了,即使该构造函数什么也不做,编译器也不会为该类合成默认的构造函数,如:

class Date{public:Date(){}private:int hour[10];int *pt;int year;int month;int day;};

此时的构造函数什么都没有做,但是系统不会生成默认构造函数。编译器生成的默认构造函数使用与成员变量初始化相同的规则来初始化成员,具有类类型的成员通过运行各自的默认构造函数来进行初始化。

       内置和复合类型的成员如指针、数组,只对定义在全局作用域中的对象初始化,对于局部作用域中的对象则不进行初始化。我们在类中加入整型数组hour[10]和整型指针pt:

class Date{private:int hour[10];int *pt;int year;int month;int day;};

我们先来看在主函数中创建对象是会不会进行初始化:

int main(){Date d1;return 0;}

进行调试,我们来看一下是否进行初始化:


显然没有进行初始化。那么我们来看看创建全局对象时能否进行初始化呢:

Date d1;int main(){return 0;}

我们来看看调试结果:


显然进行了初始化,在某些情况下,默认构造函数是由编译器隐式使用的。

       (2)初始化列表

        初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。例如:

Date(int _year = 2017, int _month = 9, int _day = 14):year(_year), month(_month), day(_day){year = _year;month = _month;day = _day;}

这就是一个初始化列表,程序执行时,首先执行花括号前面的列表,首先先给类的成员数据分配空间并给定一个初值,然后才执行花括号里面的赋值语句。初始化列表内成员数据的顺序与数据的初始化顺序无关,我们可以通过调试代码来验证这一点,我们先将参数列表换成这样:

Date(int _year = 2017, int _month = 9, int _day = 14): day(_day), year(_year), month(_month){year = _year;month = _month;day = _day;}

改变参数的顺序,然后进行调试,查看成员初始化的顺序:

可见,成员数据的初始化数据与初始化列表没有关系,那么,成员数据的初始化顺序与什么有关呢?我们通过调试观察,可以看出,初始化顺序就是数据定义时的顺序,在初始化列表中数据的顺序没有多大关系,可以自由选择,但是在特定的情况下就不能改变其顺序,比如这样的初始化:

Date(int _year = 2017, int _month = 9, int _day = 14): day(_day), year(month), month(_month){year = _year;month = _month;day = _day;}

我们用成员数据month去初始化year,到底能不能完成目的呢?我们来看看:

可以看见,本该第一个完成初始化的数据year,在month和day完成初始化之后也没有进行初始化,其原因就是程序用了没有进行初始化的month对year进行初始化,导致此结果出现 ,可见,在写参数列表时最好将数据按定义的顺序排列。

        在类中有哪些数据必须进行初始化呢?此类数据必须是在进行初始化后不能更改的数据,比如引用数据成员、const数据成员和类类型成员(该类没有缺省的构造函数),类中有这几类数据存在时必须进行初始化,

        (3)拷贝构造函数

          只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用。如代码:

Date(const Date& d){year = d.year;month = d.month;day = d.day;}

这就是拷贝构造函数,它是构造函数的重载,其参数必须使用同类型对象的引用传递,如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化。拷贝构造函数通常有如下使用场景:1、对象实例化对象,上面的例子就是属于这种情况。2、传值方式作为函数的参数。3、传值方式作为函数返回值。

       (4)类型转换

        我们在主函数中给出如下代码:

int main(){Date d1;d1 = 1;return 0;}

我们调试,观察期变化:

d1是一个对象,1是一个数字,为什么这个代码还可以通过编译呢?通过调试我们看见year初始化为2017后,变成了1,似乎代码中的1被传给了成员数据year,真实情况是怎样的呢?在表面上我们看不出来,其实在系统中,代码中的1经过类型转换,成了一个与d1类型相同的对象,这个对象中成员数据year的值为1,在经过赋值操作后,对象d1中的year的值也变成了1,而这就是这行代码底下所发生的事。

二、析构函数

        析构函数与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作,不过其工作并不是删除对象,其函数名为类名前加一个“~”符号。与构造函数相同的是,没有显示定义析构函数时系统会自动生成一个缺省的析构函数,在对象的生命周期结束时由系统自动调用,我们来写一个析构函数:

~Date(){cout << "over!" << endl;}

在函数体中可以做很多事,比如释放内存等,在此就不一一列举了,我们来看一下这个函数的调用结果:



很明显系统自动调用了此析构函数。

        C++博大精深,类的构造函数与析构函数的用法还有很多,如果会用的话妙用无穷,只有不断学习才能用得更好!


 
原创粉丝点击