---- 整理自狄泰软件唐佐林老师课程
1. 关于动态内存分配
new和malloc的区别是什么?
delete和free的区别又是什么?
1.1 问题一:new和malloc的区别
1.1.1 new关键字和malloc函数的区别
new关键字是C++的一部分 | malloc是由C库函数提供的函数 |
---|---|
new以具体 类型 为单位进行内存分配 | malloc以 字节 为单位进行内存分配 |
new在申请内存空间时可进行初始化 | malloc仅根据需要申请定量的内存空间 |
new在所有C++编译器都被支持 | malloc在某些系统开发中是不能调用的 |
new能够触发构造函数的调用 | malloc仅分配需要的内存空间 |
对象的创建只能使用new | malloc不适合面向对象开发 |
1.1.2 编程实验:new和malloc的区别一
下面的代码输出什么?为什么?
1.1.3 编程实验:new和malloc的区别二
下面的代码输出什么?为什么?
1.2 问题二:delete和free的区别
delete在所有C++编译器中都被支持 | free在某些系统开发中是不能调用的 |
---|---|
delete能够触发析构函数的调用 | free仅归还之前分配的内存空间 |
对象的销毁只能使用delete | free不适合面向对象开发 |
2. 关于虚函数
2.1 问题一
构造函数是否可以成为虚函数?
析构函数是否可以成为虚函数?
-
构造函数 不可能 成为虚函数
在构造函数执行结束后,虚函数表指针才会被正确的初始化
-
析构函数 可以 成为虚函数
建议在设计类时将析构函数声明为虚函数
-
编程实验:构造、析构、虚函数
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
Base()
{
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();
// ...
delete p;
return 0;
}
2.2 问题二
构造函数中是否可以发生多态?
析构函数中是否可以发生多态?
-
构造函数中 不可能 发生多态行为
在构造函数执行时,虚函数表指针 未被 正确初始化
-
析构函数中 不可能 发生多态行为
在析构函数执行时,虚函数表指针 已经被销毁
-
构造函数和析构函数中调用虚函数不可能发生多态行为,只调用 当前类中 定义的函数版本
3. 关于继承中的强制类型转换
-
继承中如何正确的使用强制类型转换?
-
dynamic_cast是与继承相关的类型转换关键字
-
dynamic_cast要求相关的类中必须有虚函数
-
用于直接或者间接继承关系的指针(引用)之间
-
指针:
转换成功:得到目标类型的指针
转换失败:得到一个 空指针 -
引用:
转换成功:得到目标类型的引用
转换失败:得到一个 异常操作信息
-
-
编译器会检查dynamic_cast的使用是否正确
-
类型转换的结果只可能在 运行阶段 才能得到
-
-
编程实验:dynamic_cast的使用
注解:
#include <iostream>
using namespace std;
class A {
public:
virtual ~A() { cout<<"~A()"<<endl; }
};
class B : public A{
public:
virtual ~B() { cout<<"~B()"<<endl; }
};
class C : public B{
public:
virtual ~C() { cout<<"~C()"<<endl; }
};
class D :public C {
public:
virtual ~D() { cout<<"~D()"<<endl; }
};
int main(){
A* pA;
B* pB;
C* pC;
D* pD = new D;
pA = dynamic_cast<A*>(pD); //向上转型成功
if (pA == NULL){
cout<<"Upcasting failed: D* to A*"<<endl;
} else {
cout<<"Upcasting successfully: D* to A*"<<endl;
}
pB = dynamic_cast<B*>(pD); //向上转型成功
if (pB == NULL){
cout<<"Upcasting failed: D* to B*"<<endl;
} else {
cout<<"Upcasting successfully: D* to B*"<<endl;
}
pC = dynamic_cast<C*>(pD); //向上转型成功
if (pC == NULL){
cout<<"Upcasting failed: D* to C*"<<endl;
} else {
cout<<"Upcasting successfully: D* to C*"<<endl;
}
cout << endl;
delete pD;
return 0;
}
#include <iostream>
using namespace std;
class A {
public:
virtual ~A() { cout<<"~A()"<<endl; }
};
class B : public A{
public:
virtual ~B() { cout<<"~B()"<<endl; }
};
class C : public B{
public:
virtual ~C() { cout<<"~C()"<<endl; }
};
class D :public C {
public:
virtual ~D() { cout<<"~D()"<<endl; }
};
int main(){
A* pA = new A;
B* pB;
C* pC;
D* pD;
pB = dynamic_cast<B*>(pA); //向下转型失败
if (pB == NULL){
cout<<"Downcasting failed: A* to B*"<<endl;
} else {
cout<<"Downcasting successfully: A* to B*"<<endl;
}
pC = dynamic_cast<C*>(pA); //向下转型失败
if (pC == NULL){
cout<<"Downcasting failed: A* to C*"<<endl;
} else {
cout<<"Downcasting successfully: A* to C*"<<endl;
}
pD = dynamic_cast<D*>(pA); //向下转型失败
if (pD == NULL){
cout<<"Downcasting failed: A* to D*"<<endl;
} else {
cout<<"Downcasting successfully: A* to D*"<<endl;
}
cout << endl;
delete pA;
return 0;
}
- 上述代码中,类的继承顺序是 A==>B==>C==>D,
- 当pA是指向A类型的对象时(
pA = new A;
),向下转型失败,pA不能转换为B*、C*、D*类型。 - 当pD是指向D类型的对象时(
pD = new D;
),向上转型成功,pD可以转换为A*、B*、D*类型。 - 原因:因为每个类都会在内存中保存一份类型信息,编译器会将存在继承关系的类的类型信息使用指针“连接”起来,从而形成一个继承链,如下所示。
当使用 dynamic_cast 对指针进行类型转换时,程序会先找到该指针指向的对象,再根据对象找到当前类(指针指向的对象所属的类)的类型信息,并从此节点开始沿着继承链向上遍历,如果找到了要转化的目标类型,那么说明这种转换是安全的,就能够转换成功,如果没有找到要转换的目标类型,那么说明这种转换存在较大的风险,就不能转换。
- 当pA是指向A类型的对象时(
4. 小结
- new / delete会触发构造函数或者析构函数的调用
- 构造函数不能成为虚函数
- 析构函数可以成为虚函数
- 构造函数和析构函数中都无法产生多态行为
- dynamic_cast是与继承相关的专用关键字