第55课 经典问题解析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第55课 经典问题解析相关的知识,希望对你有一定的参考价值。
1. 关于动态内存分配
(1)new和malloc的区别
区别 |
new |
malloc |
是否是关键字 |
关键字,是C++的一部分。被所有的C++编译器支持。 |
由C库提供的函数,注意是函数,而不是关键字,要依赖C库(cstdlib),在某些系统开发(如嵌入式)中可能不能调用。 |
分配单位 |
以具体类型为单位 |
以字节为单位 |
内存初始化 |
申请时可进行初始化 |
仅根据需要申请定量的内存,但并不进行初始化。 |
是否触发构造函数的调用 |
会触发。对象的创建只能使用new |
不会,仅分配需要的内存空间,因此不适合面向对象开发 |
(2)delete/free的区别
区别 |
delete |
free |
是否是关键字 |
关键字,是C++的一部分。被所有的C++编译器支持。 |
由C库提供的函数。在某些系统开发(如嵌入式)中可能不能调用。 |
是否触发析构函数的调用 |
会触发.对象的销毁只能使用delete |
不会,仅归之前分配的内存空间,因此不适合面向对象开发 |
【编程实验】new和malloc的区别
#include <iostream> #include <string> #include <cstdlib> //for malloc\free函数。同时说明这两者都是函数,而不是关键字! using namespace std; class Test { int* mp; public: Test() { cout << "Test::Test()" << endl; mp = new int(100); cout <<*mp << endl; } ~Test() { delete mp; cout << "~Test::Test()" << endl; } }; int main() { Test* pn = new Test; //会调用构造函数 Test* pm = (Test*)malloc(sizeof(Test)); //不会调用构造函数 delete pn; //会调用析构函数。如果这里混用free,则会造成内存泄漏! free(pm); //如果这里误用delete来释放pm所指的空间时,会同时调用 //析构函数,但因mp指针所指空间是未知,出现误删除的bug。 return 0; }
2. 关于虚函数
(1)构造函数:
①构造函数本身不可能成为虚函数,因为在构造函数执行结束后,虚函数表指针才被正确的初始化。
②在构造函数中调用其他虚函数时,不会发生多态。因为在构造函数执行期间,虚函数表指针未被正确初始化。所以当调用虚函数时,只会调用本类中定义的那个函数版本。
(2)析构函数:
①析构函数本身可以成为虚函数,建议在设计类时将析构函数声明为虚函数,特别是要作为父类的类。
②析构函数中调用其他虚函数时,也不会发生多态,因为析构阶段虚函数表指针己经被销毁。所以当调用虚函数时,只会调用本类中定义的那个函数版本,即静态绑定了。
【编程实验】构造、析构和虚函数
#include <iostream> #include <string> using namespace std; class Base { public: Base() //不能成为虚函数,哪怕加了virtual; { cout << "Base()" << endl; func(); //不会发生多态,只会调用本类中的函数版本! } virtual void func() { cout << "Base::func()" << endl; } virtual ~Base() //可以成为虚函数 { func(); //不会发生多态,只会调用本类中的函数版本! cout << "~Base()" << endl; } }; class Derived: public Base { public: Derived() { cout << "Derived()" << endl; func(); //不会发生多态,只会调用本类中的函数版本! } virtual void func() { cout << "Derived::func()" << endl; } ~Derived() { func(); //不会发生多态,只会调用本类中的函数版本! cout << "~Derived()" << endl; } }; int main() { Base* p = new Derived(); //注意是父类指针,如果这里直接声明为Derived* p = new Derived() //则delete p直接调用子类的构造函数。但我们本例的目的之一是为了演示 //析构函数的多态,所以声明为父类的针。 //... cout << endl; delete p; //delete会调用析构函数。从这行代码看,如果父类中析构函数没被声明为虚函数的话, //delete一个父类的指针,由于静态绑定,当然调用的是父类的析构函数,此时会造成 //Derived的析构函数没被调用。当然,如果父类中析构函数被声明为虚函数,根据多态 //原理,会调用子类的析构函数,又因析构的特点,会自动先调用父类析构,再调用子类 //自己的析构函数,从而正确的释放内存。 return 0; } /*输出结果 Base() Base::func() //注意,并没有发生多态 Derived() Derived::func() //注意,并没有发生多态 Derived::func() //注意,并没有发生多态 ~Derived() Base::func() //注意,并没有发生多态 ~Base() */
3. 关于继承中的强制类型转换
(1)dynamic_cast是与继承相关的类型转换关键字
(2)dynamic_cast要求相关的类中必须有虚函数
(3)用于直接或间接继承关系的指针(引用)之间
|
指针间的转换 |
引用间的转换 |
转换成功 |
得到目标类型的指针 |
得到目标类型的引用 |
转换失败 |
得到一个空指针 |
得到一个异常操作信息 |
(4)编译器会检查dynamic_cast的使用是否正确
(5)类型转换的结果只可能在运行阶段才能得到。
【编程实验】dynamic_cast的使用
#include <iostream> #include <string> using namespace std; class Base { public: Base() { cout << "Base::Base()" << endl; } virtual ~Base() { cout << "Base::~Base()" << endl; } }; class Derived: public Base { public: }; int main() { Base* p = new Base; //将用父类指针来初始化子类指针(为演示之用,本身这样用就是不完全的!) //注意,这里也提醒我们,可以用dynamic_cast来判断一个类是不是另一个类父类的方法。 // 具体方法Base* p = dynamic_cast(Base* p)(pDerived);即,子类能否转为父类。 // 如果返回值不为NULL,表示有父子关系。否则没有。 Derived* pd = dynamic_cast<Derived*>(p); //注意p所指的类中一定要有虚函数 //本例中,析构函数声明为虚函数。 if(pd != NULL) //转换成功。 { cout << "pd = " << pd << endl; } else //转换失败,返回空指针NULL { cout << "Cast Error!" << endl; } delete p; return 0; } /*输出结果 Base::Base() Cast Error! Base::~Base() */
4. 小结
(1)new/delete会触发构造函数或析构函数的调用
(2)构造函数不能成为虚函数,析构函数可以成为虚函数。
(3)构造函数和析构函数中都无法产生多态行为。
(4)dynamic_cast是与继承相关的专用转换关键字。
以上是关于第55课 经典问题解析的主要内容,如果未能解决你的问题,请参考以下文章