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!

相关推荐
Mao_Hui2 小时前
Unity3d实时读取Modbus RTU数据
开发语言·嵌入式硬件·unity·c#
echome8882 小时前
Python 装饰器详解:从入门到精通的实用指南
开发语言·python
重生之后端学习2 小时前
62. 不同路径
开发语言·数据结构·算法·leetcode·职场和发展·深度优先
栗子~~2 小时前
hardhat 单元测试时如何观察gas消耗情况
开发语言·单元测试·区块链·智能合约
The hopes of the whole village2 小时前
Matlab FFT分析
开发语言·matlab
兰文彬2 小时前
n8n 2.x版本没有内嵌Python环境
开发语言·python
yiyaozjk2 小时前
Go基础之环境搭建
开发语言·后端·golang
谁动了我的代码?2 小时前
VNC中使用QT的GDB调试,触发断点时与界面窗口交互导致整个VNC冻结
开发语言·qt·svn
We་ct3 小时前
LeetCode 212. 单词搜索 II:Trie+DFS 高效解法
开发语言·算法·leetcode·typescript·深度优先·图搜索算法·图搜索