C++——动态内存分配、关于虚函数、关于继承中的强制类型转换

1.动态内存分配

new-delete malloc-free

(1)new关键字和malloc函数的区别

new关键字是C++的一部分,malloc是由C库提供的函数

new以具体类型为单位进行内存分配,malloc以字节为单位进行内存分配

new在申请内存空间时可进行初始化,malloc仅根据需要申请定量的内存空间

复制代码
#include <iostream>
#include<string>
using namespace std;

class Test {
public:
	Test() {
		cout << "Test::Test()" << endl;
	}
	~Test() {
		cout << "~Test::Test()" << endl;
	}
};
int main() {
	Test* pn = new Test;
	Test* pm = (Test*)malloc(sizeof(Test));
	delete pn;
	free(pm);
	return 0;
}

运行结果:

Test::Test()

~Test::Test()

是由new分配时不仅分配了内存,还创建了对象,即调用了构造函数,所以delete时,不仅要释放内存还要摧毁对象;而malloc仅仅是分配内存,free仅仅是将内存归还给系统,所以new、malloc、delete、free不可混用

对象的创建只能使用new,malloc不适合面向对象开发

2.关于虚函数

构造函数和析构函数是否可以成为虚函数?

虚函数依赖虚函数表(vtable)虚表指针(vptr) 实现

(1)构造函数不可能成为虚函数

在构造函数执行结束后,虚函数表指针才会被正确的初始化,如果构造函数是虚函数,调用时需要通过 vptr 找虚表,但此时 vptr 还未初始化,根本无法定位虚函数;

(2)析构函数建议成为虚函数

建议在设计类时将析构函数声明为虚函数,如果基类指针指向派生类对象,当通过基类指针释放对象时,若基类析构函数不是虚函数,编译器只会调用基类的析构函数,派生类的析构函数不会执行,导致子类的资源(堆内存)无法释放,造成内存泄漏;

例如:

如果析构函数不为虚函数,则子类对象不会被释放,会有内存泄漏

复制代码
#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base构造" << endl; }
    // 非虚析构函数
    ~Base() { cout << "Base析构" << endl; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived构造" << endl; }
    ~Derived() { cout << "Derived析构" << endl; } // 不会被调用
};

int main() {
    Base* ptr = new Derived(); // 基类指针指向派生类对象
    delete ptr; // 仅调用Base析构,Derived析构未执行
    return 0;
}

运行结果:

Base构造 Derived构造 Base析构

如果是虚函数:

复制代码
#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base构造" << endl; }
    // 虚析构函数
    virtual ~Base() { cout << "Base析构" << endl; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived构造" << endl; }
    ~Derived() { cout << "Derived析构" << endl; } // 会被调用
};

int main() {
    Base* ptr = new Derived();
    delete ptr; // 先调用Derived析构,再调用Base析构
    return 0;
}

运行结果:

Base构造 Derived构造 Derived析构 Base析构

构造函数和析构函数是否可以发生多态?

(1)构造函数中不可能发生多态

在构造函数执行时,虚函数表指针未被正确初始化

(2)析构函数中不可能发生多态行为

在析构函数执行时,虚函数表指针已经被销毁

多态的关键是:程序运行时根据对象的实际类型,调用对应类的虚函数

构造函数执行时,虚表指针还没完全就绪,对象的 "实际类型" 也没最终确定,所以无法通过虚函数表实现 "根据实际类型调用函数" 的多态效果。在析构函数执行时,虚函数表指针可能已经被摧毁。

  • 构造函数中调用虚函数:不会触发多态,只会调用当前构造函数所属类的虚函数版本;

  • 析构函数中调用虚函数:会触发多态(基类析构为虚函数时),但析构有固定顺序,最终调用的是对应阶段的虚函数版本。

    #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;
    }
    virtual void func() {
    cout << "Derived::func()" << endl;
    }
    ~Derived() {
    cout << "~Derived()" << endl;
    }
    };
    int main() {
    Base* p = new Derived();
    delete p;
    return 0;
    }

运行结果:

Base()

Base::func()

Derived()

~Derived()

Base::func()

~Base()

复制代码
#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;
}

运行结果:

Base()

Base::func()

Derived()

Derived::func()

Derived::func()

~Derived()

Base::func()

~Base()

3.继承中如何正确使用强制类型转化?

(1)dynamic_cast是与继承相关的类型转换关键字

(2)dynamic_cast要求相关的类中必须有虚函数

(3)用于有直接或间接继承关系的指针或引用之间

指针:若转换成功,则得到目标类型的指针,若转换失败,则得到一个空指针

引用:若转换成功,则得到目标类型的引用,若转换失败,则得到一个异常操作信息

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 {

};
int main() {
	Base* p = new Derived();
	// Derived* pd = p; 报错,所以要使用强制类型转换dynamic_cast,但是相关的类中要有虚函数
	Derived* pd = dynamic_cast<Derived*>(p);
	cout << "pd=" << pd << endl;
	return 0;
}

运行结果:

Base::Base()

pd=013F5F48

若指针转换失败,则得到一个空指针

复制代码
#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 {

};
int main() {
	Base* p = new Base;
	Derived* pd = dynamic_cast<Derived*>(p);
	if (pd != NULL) {
		cout << "pd=" << pd << endl;
	}
	else {
		cout << "error!" << endl;
	}

	return 0;
}

运行结果:

Base::Base()

error!

相关推荐
小小小米粒44 分钟前
Collection单列集合、Map(Key - Value)双列集合,多继承实现。
java·开发语言·windows
智者知已应修善业1 小时前
【51单片机中的打飞机设计】2023-8-25
c++·经验分享·笔记·算法·51单片机
czhc11400756631 小时前
C# 428 线程、异步
开发语言·c#
:1212 小时前
java基础
java·开发语言
SilentSamsara2 小时前
Python 环境搭建完整指南:从下载安装到运行第一个程序
开发语言·python
小短腿的代码世界3 小时前
Qt文件系统与IO深度解析:从QFile到异步文件操作
开发语言·qt
智者知已应修善业3 小时前
【51单片机按键调节占空比3位数码管显示】2023-8-24
c++·经验分享·笔记·算法·51单片机
harder3214 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
jinanwuhuaguo4 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社4 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust