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!

相关推荐
无限进步_3 分钟前
【C++】巧用静态变量与构造函数:一种非常规的求和实现
开发语言·c++·git·算法·leetcode·github·visual studio
Advancer-6 分钟前
RedisTemplate 两种序列化实践方案
java·开发语言·redis
郝学胜-神的一滴14 分钟前
Socket实战:从单端聊天到多用户连接的实现秘籍
服务器·开发语言·python·网络协议·pycharm
小超超爱学习993720 分钟前
大数乘法,超级简单模板
开发语言·c++·算法
java1234_小锋25 分钟前
Java高频面试题:MyBatis如何实现动态数据源切换?
java·开发语言·mybatis
knighthood200128 分钟前
Qt5.15+VTK9.3.0实现点云点选功能
开发语言·qt
墨神谕33 分钟前
Java中,为什么要将.java文件编译成,class文件,而不是直接将.java编译成机器码
java·开发语言
和小潘一起学AI1 小时前
CentOS 7安装Anaconda
开发语言·python
努力努力再努力dyx1 小时前
【无标题】
开发语言·python
傻小胖2 小时前
Object.defineProperty() 完整指南
开发语言·前端·javascript