10.C++-构造函数初始化列表、显示初始化和隐式初始化、类const成员、对象构造顺序、析构函数
首先回忆下,以前学的const
单独使用const修饰变量时,是定义的常量,比如:const int i=1;
使用volatile const修饰变量时,定义的是只读变量
使用const & 修饰变量时,定义的是只读变量
在类中是否可以定义const成员?
直接来写代码:
#include <stdio.h> class Test { private: const int ci; public: // Test() // { // ci=10; // } int getCI() { return ci; } }; int main() { Test t; printf("%d\n",t.getCI()); return 0; }
编译出错:
test.cpp: In function ‘int main()’: test.cpp:21: error: structure ‘t’ with uninitialized const members
从编译信息看出,由于结构体t的const成员没有初始化,所以执行printf()出错.
接下来取消上面示例的屏蔽,使用上章学习的构造函数来初始化const
编译还是出错:
test.cpp: In constructor ‘Test::Test()’: test.cpp:8: error: uninitialized member ‘Test::ci’ with ‘const’ type ‘const int’ test.cpp:10: error: assignment of read-only data-member ‘Test::ci’
从编译信息看出, Test::Test()构造函数里,不能直接初始化const变量.
所以,在C++中,便引入了构造函数初始化列表(除了可以给成员变量初始化,还可以对const成员初始化)
初始化列表位于构造函数名右侧,以一个冒号开始,接着便是需要初始化的变量,以逗号隔开,例如:
class Example { private: int i; float j; const int ci; int *p; public: Test(): j(1.5),i(2),ci(10) //初始化i=2,j=1.5,ci=10 { p=new int; *p=3; } };
注意:
-列表成员的初始化顺序只与成员的声明顺序相同,与初始化列表的位置无关
比如上个示例,初始化列表初始化的顺序为: i=2,j=1.5,ci=10
-调用构造函数初始化时,会先执行初始化列表,再执行构造函数里的内容.
构造函数-显示初始化和隐式初始化
我们已经学会了初始化列表怎么写的,其实初始化列表,也被称为显式初始化。
所以我们之前写的就是隐式初始化,比如:
ClassA::ClassA() { A=1; B=2; ...... }
这样写,其实在执行构造函数函数体内语句前,已经隐式调用了默认构造函数对变量进行初始化,函数体内语句其实是进行了一次赋值。
所以:使用显示初始化,除了能初始化const变量外,还可以提高效率问题
那class类里的const成员是常量还是只读变量?
参考以下示例:
#include <stdio.h> class Test { private: const int ci; public: Test():ci(10) { } int getCI() { return ci; } void setCI(int val) { int *p=const_cast<int *>(&ci); *p=val; } }; int main() { Test t; t.setCI(5); printf("%d\n",t.getCI()); return 0; }
编译运行:
5
所以class类里的const成员, 定义的是只读变量
对象的构造顺序
C++中的类可以定义多个对象,那么对象构造的顺序又是怎么样的?
对于局部对象(栈)
-程序执行到对象的定义语句时,便进行构造
对于通过new创建的对象(堆)
-和局部对象一样,程序执行到new语句时,便进行构造
对于全局对象(静态存储区)
-对象的构造顺序是不确定的,所以要尽量避免多个全局对象之间的相互依赖.
对象的销毁-析构函数
之前我们学习过创建对象时,有构造函数进行初始化.
同样的,对象被销毁前也应该要有一些清理工作,所以,C++中引入了一个特殊的清理函数-析构函数
- 析构函数的功能与构造函数相反,在对象被摧毁时自动调用
- 析构函数没有参数,也没有返回值类型声明
定义为: ~class_name(),例如:
class Test{ public: Test(){ } //构造函数 ~Test(){ } //析构函数 };
注意:
- 在类里,当定义了析构函数,编译器就不会提供默认的构造函数了,所以还要自己定义一个构造函数。
- 使用new创建的对象变量,在不使用时,需要使用delete,才能调用析构函数
参考以下示例:
#include <stdio.h> class Test { int val; public: Test(int i) { val=i; printf("Test() val=%d\n",val); } ~Test() { printf("~Test() val=%d\n",val); } }; int main() { Test t1(1); Test* t2 = new Test(2); // delete t2; return 0; }
编译运行:
Test(1) Test(2) ~Test(1)
从打印结果可以看出,t2的析构函数没有打印,所以只打印了:~Test(1)
取消屏蔽后再次运行:
Test(1) Test(2) ~Test(2) ~Test(1)
总结:
当类中有成员需要内存申请,文件打开,链接数据库等时,则需要定义析构函数,进行回收资源
(和拷贝构造函数类似)
人间有真情,人间有真爱。