一、构造函数和析构函数的由来
类的数据成员不能在类的声明时候初始化,为了解决这个问题? 使用构造函数处理对对象的初始化。构造函数是一种特殊的成员函数,与其他函数不同,不需要用户调用它,而是创建对象的时候自动调用。析构函数是对象不再使用的时候,需要清理资源的时候调用。
二、类的构造函数
(1)初识类的初始化
C++支持两种初始化形式:复制初始化和直接初始化,对于类直接初始化直接调用实参匹配的构造函数,复制初始化总是调用复制构造函数。
(2)类的初始化和构造函数的关系
常用的类的初始化方式大概有以下5种:
ClassTest ct1("ab");
ClassTest ct2 = "ab";
ClassTest ct3 = ct1;
ClassTest ct4(ct1);
ClassTest ct5 = ClassTest();
那么以上五种方式类的初始化,又是什么初始化呢,以及调用了哪些构造函数呢?上代码:
上述代码中,将复制构造函数delete了,所以初始化需要调用复制构造函数就会报错,因此我们验证有哪些初始化报错了即可。下面是编译器中报错提示。大概意思就是复制构造函数被删除了。结果显示只有第一种初始化方式不需要调用复制构造函数。
因此可以得出结论:
ClassTest ct1("ab"); //直接初始化
ClassTest ct2 = "ab"; //复制初始化
ClassTest ct3 = ct1; //复制初始化
ClassTest ct4(ct1); //复制初始化
ClassTest ct5 = ClassTest(); //复制初始化
这里面有必要解释下“=”,类的初始化过程中的“=”,是隐式调用复制构造函数,而不是调用赋值运算符函数。
“当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象”
从上面的经典说法中,我们也可以知道,直接初始化不一定要调用复制构造函数,而复制初始化一定要调用复制构造函数。实际是不是这样呢,请看下面代码。
结果输入为:
看样子结果有些矛盾,复制初始化一定要调用复制构造函数,但是执行发现并没有调用,反而却和直接初始化调用方式一样。但是把复制构造函数删除却还报错。
(3)矛盾结果产生的原因
产生上面的运行结果的主要原因在于编译器的优化,而为什么把复制构造函数声明为delete,就能把这个假象去掉呢?主要是因为复制构造函数是可以由编译默认合成的,而且是公有的(public),编译器就是根据这个特性来对代码进行优化的。然而如里你自己定义这个复制构造函数,编译则不会自动生成,虽然编译不会自动生成,但是如果你自己定义的复制构造函数仍是公有的话,编译还是会为你做同样的优化。然而当它是delete时,编译器就会有很不同的举动,因为你明确地告诉了编译器,你明确地拒绝了对象之间的复制操作,所以它也就不会帮你做之前所做的优化,你的代码的本来面目就出来了。
三、类的析构函数
类的析构函数和构造函数作用相反,释放对象使用的资源,并销毁非static成员。
(1)内存泄漏
下面代码有何隐患?
回想我们在函数体内定义一个非static的变量,那么在函数执行之后变量就会被销毁,那么如果我们指向了动态开辟的一块空间的指针,我们需要手动free掉,否则就会出现内存泄漏。
其实类也是一样的,上述代码就会有内存泄漏的风险。如何解决呢?看下述代码。
在上述代码中,我们在析构函数中,添加delete函数。解决了内存泄漏的问题,但是还存在其他问题。
(2)多次释放资源
上代码:
在上述代码中,用ct初始化了ct1。由于默认复制构造函数都是浅拷贝,所以对象ct中的p和对象ct1中的p都是指向同一块内存空间。在mian函数执行完毕之后,ct和ct1分别调用析构函数,所以delete两次同一块内存空间,所以程序会崩溃。下面的输出结果也验证了我们的猜想是正确的。建议在这种情况下采用深拷贝进行操作。
四、总结
1、类的复制初始化无优化的调用方式,复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。
2、对于类的复制初始化的构造函数的调用方式,编译器已经将其作为普遍方法而不是作为一种优化。
3、类的初始化过程中的“=”,是隐式调用复制构造函数,而不是调用赋值运算符函数。
4、当析构函数中存在手动释放资源的时侯,一定要注意之前是否释放过,以及以后是否有其他操作会释放。