(一四二)继承和动态内存分配

简介:

当基类、派生类用,或者不用动态内存时,共有四种情况:

(注意,前提是基类的动态内存分配相关函数符合常规使用动态内存的要求)

 

情况一:基类 使用 动态内存分配、派生类新增数据成员 不使用 动态内存分配

假如基类使用动态内存分配(new),其必然设定①构造函数、②复制构造函数、③赋值运算符、④析构函数。

又知,派生类的构造函数(包括默认构造函数),需要调用基类的构造函数。

那么基类的数据成员若使new,则已经没问题。

派生类新增的数据成员,由于不使用new,因此可以按值传递。

 

①对于派生类构造函数:直接将参数赋值对应的数据成员——ok

 

②对于派生类复制构造函数,使用默认的复制构造函数(因此对于派生类新增数据对象使用按值传递,对于基类的数据对象使用基类的复制构造函数)——ok

 

③对于赋值运算符来说,使用默认的赋值运算符,是按值传递(因此对派生类新增的数据成员,按值传递,对基类数据对象,使用基类的赋值运算符函数),如果有特殊需求,则显式调用基类的赋值运算符A::operator=(b); //显式调用基类赋值运算符函数),然后对派生类的数据成员进行处理(③可以参考情况三的赋值运算符处理)——ok

 

④对于析构函数来说,会先调用派生类的析构函数,然后调用基类的析构函数——ok

 

因此:假如派生类新增数据成员不使用动态内存的话,如无特别需求,可以无需特别设置

使用默认的构造函数、复制构造函数、赋值运算符、析构函数即可。

 

如代码:

class A	//基类
{
	char*name;
public:
	A(const char*q)		//构造函数
	{	
		name = new char[strlen(q) + 1];strcpy_s(name, strlen(q) + 1, q); 
	}
	A(const A&a)	//复制构造函数
	{ 
		name = new char[strlen(a.name) + 1];strcpy_s(name, strlen(a.name) + 1, a.name);
	}
virtual ~A() { delete[]name; }	//析构函数
	A&operator=(const A&a)	//赋值运算符
	{
		if (this == &a)return *this;	//为防止自己赋值自己
		delete[]name;name = new char[strlen(a.name) + 1];
		strcpy_s(name, strlen(a.name) + 1, a.name);
		return *this;
	}
	friend std::ostream& operator<<(std::ostream&os, const A&b)
	{
		os << b.name;
		return os;
	}
};

class B :public A	//基类的派生类
{
	int id;
public:
	B(const char*a, int q) :A(a)	//构造函数
	{
		id = q;
	}
	B(const B&b) :A(b)	//复制构造函数,可省略,使用默认复制构造函数
	{
		id = b.id;
	}
	B&operator=(const B&b)	//赋值运算符,可省略,使用默认的赋值运算符
	{
		if (this == &b)return *this;	//为防止自己赋值自己
		A::operator=(b);	//显式调用基类赋值运算符函数
		id = b.id + 5;	//为了区分而修改
		return *this;
	}
	friend std::ostream& operator<<(std::ostream&os, const B&b)
	{
		os << A(b) << "," << b.id;
		return os;
	}
};

情况二:基类 不使用 动态内存分配、派生类新增数据成员 不使用 动态内存分配

和情况一并没有什么区别。如无特殊需求,使用默认的复制构造函数、赋值运算符即可。

 

 

 

情况三:基类 使用 动态内存分配、派生类新增数据成员 使用 动态内存分配

由于派生类新增数据成员使用动态内存分配,那么显然,不能使用默认构造函数、默认的复制构造函数、默认的赋值运算符、默认的析构函数了(否则无法形成newdelete的对应)。

 

①构造函数:

首先,构造函数需要调用基类的构造函数,是毫无疑问的。因此,基类的动态内存分配ok

其次,对于派生类新增的数据成员,对于需要使用动态内存的,使用new运算符来分配内存。不使用动态内存的,常规处理,因此ok

 

②复制构造函数:

首先,复制构造函数需要调用基类的复制构造函数。将派生类对象作为参数传递给基类初始化列表,由于基类引用可以指向派生类对象,因此可以成功初始化派生类的基类部分。

然后,对派生类新增的部分,对于需要使用动态内存的,使用new运算符来分配内存。不使用动态内存的,常规处理,因此ok

 

③赋值运算符:

首先,需要显式的调用基类的赋值运算符(A::operator=(b); //显式调用基类赋值运算符函数),以使得基类的部分被成功赋值;

其次,对于派生类部分,按照正常方式处理使用动态内存的数据成员(delete后再new),和不使用动态内存的数据成员。

最后,注意在函数的开始部分,添加防止自己赋值给自己的代码。

 

④析构函数:

由于派生类的析构函数,会自动调用基类的析构函数,因此,只对派生类新增的数据成员进行delete释放内存处理。

 

如代码(只修改了派生类B):

class B :public A	//基类的派生类
{
	char* id;
public:
	B(const char*a, char*q) :A(a)	//构造函数
	{
		id=new char[strlen(q)+1];
		strcpy_s(id, strlen(q) + 1, q);
	}
	B(const B&b) :A(b)	//复制构造函数
	{
		id = new char[strlen(b.id) + 1];
		strcpy_s(id, strlen(b.id) + 1, b.id);
	}
	~B()	//析构函数,处理派生类新增数据成员
	{
		delete[]id;
	}
	B&operator=(const B&b)	//赋值运算符
	{
		if (this == &b)return *this;	//为防止自己赋值自己
		A::operator=(b);	//显式调用基类赋值运算符函数
		delete[]id;	//需要先delete
		id = new char[strlen(b.id) + 1];
		strcpy_s(id, strlen(b.id) + 1, b.id);
		return *this;
	}
	friend std::ostream& operator<<(std::ostream&os, const B&b)
	{
		os << A(b) << "," << b.id;
		return os;
	}
};

情况四:基类 不使用 动态内存分配、派生类新增数据成员 使用 动态内存分配

和情况三的办法一样(因为情况三使用基类的各种方法来处理基类部分的数据)。

 

 

总结:

①我忘了给基类加析构函数;

②我加了析构函数,忘了给析构函数加关键字virtual变成虚函数。

 

 

对于友元函数:

如果想在派生类的友元函数中调用基类的友元函数,那么应该使用强制类型转换,即 基类名(派生类对象) ,即可使用,如A(b)

也可以这样:(const 基类名&)派生类对象 表示强制转换派生类对象为const基类类型。

 


目录
相关文章
|
5月前
|
Java
JavaSE 面向对象程序设计进阶 继承和方法重写 2024理论与内存详解
JavaSE 面向对象程序设计进阶 继承和方法重写 2024理论与内存详解
32 3
|
12月前
|
编译器 C++
《C++避坑神器·六》多继承下问题处理(同名变量,信号槽,多态内存释放)
《C++避坑神器·六》多继承下问题处理(同名变量,信号槽,多态内存释放)
51 0
|
编译器 C语言 Swift
05-📝C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
复习`C++核心语法`,且适当进行汇编探索底层实现原理,进一步夯实基础,为以后的`底层开发`、`音视频开发`、`跨平台开发`、`算法`等方向的进一步学习埋下伏笔。
05-📝C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
|
Java
第13篇:Java继承的内存布局
🍀 ① 加载类信息: 会先加载当前类的父类的类信息(包括 Object 基类) 🍀 ② Son 对象中会拥有其所有父类的属性: 父类属性和子类属性名字一样是可以的 🍀 ③ 访问属性的时候:先看当前类中是否有该属性?若有,直接访问;⚡ 若没有,查看直接父类是否有该属性,并且该属性是否可访问?若有并且属性没有被 private 修饰,可访问直接父类的属性;⚡ 若有但属性被 private 修饰,直接报错;⚡ 若没有,继续查找父类的父类是否有该属性,并且是否可访问 ...【① 看自己有没有,有就直接用;若自己没有,就看父亲有没有,若父亲没有,就看爷爷有没有,... 一直向上找,直达找到 Obj
206 0
第13篇:Java继承的内存布局
多态继承中的内存图解 && 多态中的对象变化的内存图解
多态继承中的内存图解如下: 多态中的对象变化的内存图解如下: 我的GitHub地址:https://github.com/heizemingjun 我的博客园地址:http://www.cnblogs.
1049 0
|
C++
C++ 多继承和虚继承的内存布局
来源:http://www.oschina.net/translate/cpp-virtual-inheritance 来源:http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html C++中的虚拟继承的一些总结 1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决
2973 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
325 0
|
10天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
19 1
|
14天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。