第24课 经典问题解析二
1. 关于析构的疑问
(1)单个对象创建时构造函数的调用顺序
①调用父类的构造函数(后续课程中讲解)
②调用成员变量的构造函数(调用顺序与声明顺序相同)
③调用类自身的构造函数
▲析构函数与对应构造函数的调用顺序相反:即类自身的析构→成员变量→父类析构
(2)多个对象的析构:析构函数顺序与构造函数顺序相反
【实例分析】构造与析构顺序 24-1.cpp
#include <stdio.h> class Member { private: const char* ms; public: Member(const char* s) { printf("Member(const char* s): %s\n", s); ms = s; } ~Member() { printf("~Member(): %s\n", ms); } }; class Test { private: Member mA;//成员函数是按声明顺序构造的 Member mB; public: Test():mB("mB"), mA("mA") { printf("Test()\n"); } ~Test() { printf("~Test()\n"); } }; Member gA("gA"); int main() { Test t; return 0; }
//输出结果(先构造全局对象gA,进入main构造t对象)
//Member(const char* s): gA
//Member(const char* s): mA
//Member(const char* s): mB
//Test();
//~Test() //注意析构顺序与构造顺序是相反的!
//~Member(): mB
//~Member(): mA
//~Member(): gA
(3)不同存储区对象的析构
①栈对象和全局对象的析构:类似于入栈与出栈的顺序,最后构造的对象被最先析构
②堆对象的析构:发生在使用delete的时候,与delete的使用顺序相关
2. 关于const对象的疑问
(1)const关键字能够修饰对象,使之能为只读对象,即成员变量不允许被改变。
(2)只读对象是编译阶段的概念,在运行时无效。
3. const成员函数
(1)const成员函数中不能直接改写成员变量的值。
(2)const对象只能调用const成员函数。如拷贝构造函数里只能调用const成员函数
(3)const成员函数只能调用const成员函数。
(4)const成员函数的定义:
Type ClassName::func(Type p) const{};//声明与定义都必须带const关键字
【编程实验】类的const函数初探 24-2.cpp
#include <stdio.h> class Test { private: int mi; public: Test(int i); Test(const Test& t);//t是const对象(的引用),函数内只能调用const成员函数 int getMI() const; //const成员函数 void print() //非const成员函数 { printf("mi = %d\n", mi); } void show() const { //print(); //错误,const成员函数不能调用非const的成员函数 printf("mi = %d\n", mi); } }; Test::Test(int i) { mi = i; } Test::Test(const Test& t) { } int Test::getMI() const { //mi = 2; //const成员函数不能改变成员变量的值! return mi; } int main() { const Test t(1); //const对象 t.getMI(); //正确,const对象只能调用const成员函数 t.show(); //正确,const对象只能调用const成员函数 //t.print(); //错误,const对象不能调用非const成员函数 return 0; }
4. 关于类成员的疑问
(1)从面向对象的角度看,对象由属性(成员变量)和方法(成员函数)构成
(2)从程序运行的角度看,对象由数据和函数构成,其中数据位于栈、堆或全局数据区中,而函数只能位于代码段
(3)结论:
①每一个对象拥有自己独立的属性(成员变量)
②所有的对象共享类的方法(成员函数)
③方法能够直接访问对象的属性
④方法中隐藏参数this指针,用于指代当前对象
【编程实验】成员函数的秘密 24-3.cpp
#include <stdio.h> class Test { private: int mi; public: int mj; Test(int i); Test(const Test& t); int getMI(); void print(); }; Test::Test(int i) { mi = i; printf("Test(int i)\n"); } Test::Test(const Test& t) { //mi = t.getMI();//错误,t是const对象,不能调用非const成员函数 mi = t.mi; //这里t.mi为private属性,但编译仍然能通过。是因为编译器开了个后门, //允许类的成员函数直接访问由该类创建的任何对象的局部变量。 printf("Test(const Test& t)\n"); } int Test::getMI() { return mi; } void Test::print() { printf("&mi = %p\n", &mi); printf("this = %p\n", this); } int main() { //以下演示3种初始化方法 Test t1(1); Test t2 = 2; Test t3 = Test(3);//手动调用构造函数,由Test(3)产生的临时对象会被编译器优化掉 //Test(int i); printf("\n"); //t1、t2、t3的内存地址各不相同。各自的mi地址也不同。但print函数地址是一样的 printf("t1.getMI() = %d\n", t1.getMI()); //1 t1.print(); printf("&t1 = %p\n", &t1); printf("&t1.print() = %p\n", &t1.print); printf("\n"); printf("t2.getMI() = %d\n", t2.getMI()); //2 t2.print(); printf("&t2 = %p\n", &t2); printf("&t2.print() = %p\n", &t2.print); printf("\n"); printf("t3.getMI() = %d\n", t3.getMI()); //3 t3.print(); printf("&t3 = %p\n", &t3); printf("&t3.print() = %p\n", &t3.print); return 0; }
5. 小结
(1)对象的析构顺序与构造顺序相反
(2)const关键字能够修饰对象,得到只读对象
(3)只读对象只能调用const成员函数
(4)所有对象共享类的成员函数
(5)隐藏的this指针用于表示当前对象