【C++第二阶段】继承多态电脑组装实例

你好你好!
以下内容仅为当前认识,可能有不足之处,欢迎讨论!


文章目录

    • 继承
      • 继承语法
      • 继承方式
      • 继承中的对象模型
      • 继承中构造和析构顺序
      • 同名成员处理
      • 同名静态成员处理
      • 多继承语法
      • 菱形继承问题
    • 多态
      • 多态基本概念
        • 重写&重载
      • 多态原理
      • 多态重写计算器
      • 纯虚函数和抽象类
      • 虚析构&纯虚析构函数
    • 电脑组装需求案例


继承

继承语法

目的:减少代码复用

语法:在类后面写一个class 子类 : 继承方式 父类,就算继承了。

案例:

#include<iostream>
#include<string>

using namespace std;

class BasePage {

	
public:
	void header() {
		cout << "==========网页头部==========" << endl;
	}
	void footer() {
		cout << "**********网页尾部**********" << endl;
	}
};

class PythonPage :public BasePage {
public:
	PythonPage() {
		python();
	}
	void python() {
		BasePage().header();
		cout << "~~~~~~~~~~python网页~~~~~~~~~~" << endl;
		BasePage().footer();
	}
};


class CPPPage :public BasePage {
public:
	CPPPage() {
		CPP();
	}
	void CPP() {
		BasePage().header();
		cout << "~~~~~~~~~~CPP网页~~~~~~~~~~" << endl;
		BasePage().footer();
	}
};


class JavaPage :public BasePage {

public:
	JavaPage() {
		Java();
	}
	void Java() {

		BasePage().header();
		cout << "~~~~~~~~~~Java网页~~~~~~~~~~" << endl;
		BasePage().footer();
	}
};

void test_0217_0() {
	PythonPage python_page;

	CPPPage cpp;

	JavaPage java;
}

int main() {
	cout << "hello ! world ! " << endl;
	test_0217_0();
	system("pause");
	return 0;
	}

运行结果:

image-20240217224729737

此处有疑惑,继承的父类不能有构造函数?

继承方式

公有继承:父类里面成员属性是什么权限,之后仍然是什么权限。

保护权限:父类里面的public&protected变为子类的protected权限。

私有继承:父类里面的public&protected变为子类的private权限。

父类的private权限内容,其他都无法访问。

代码说明:

①公有权限

class BaseClass {
public:
	int class_a;
protected:
	int class_b;
private:
	int class_c;
};

class PublicSon :public BaseClass {
	PublicSon() {
		class_a = 100;
		class_b = 200;
		//class_c = 300;
	}
};

image-20240218202109122

根据图可以看出,在继承方式为public时,无法访问父类中private权限下的内容,只能访问权限为publicprotected下的内容。此时,父类与子类的权限变化如下表格

继承方式为public权限/类父类子类
继承方式为public权限publicpublic
继承方式为public权限protectedprotected
继承方式为public权限private无法访问

②保护权限

继承方式为protected时,代码:

class BaseClass {
public:
	int class_a;
protected:
	int class_b;
private:
	int class_c;
};

class ProtectedSon :protected BaseClass {
	ProtectedSon() {
		class_a = 100;
		class_b = 200;
		//class_c = 300;
	}
};

此时,父类为private权限下的内容仍然无法访问。

image-20240218202758809

权限变化如下表格:

继承方式为protected权限/类父类子类
继承方式为protected权限publicprotected
继承方式为protected权限protectedprotected
继承方式为protected权限private无法访问

③私有权限

私有权限将父类中的所有成员属性在子类继承时转换为了private权限,在孙类继承时无法获得子类成员属性。

class BaseClass {
public:
	int class_a;
protected:
	int class_b;
private:
	int class_c;
};

class PrivateSon :private BaseClass {
	PrivateSon() {
		class_a = 100;
		class_b = 200;
	}
};

class GrandSon :public PrivateSon {
	void print() {
		cout << "class_a = " << class_a << endl;
		cout << "class_c = " << class_b << endl;
	}
};

如图所示:

image-20240218203216049

可以看到无法访问class_a的值。

继承中的对象模型

问题:

子类对于父类中的成员是都继承吗?

回答:

对于非静态成员变量,都继承。

验证:

解决办法:①在程序中打印出所占内存大小;②利用开发人员命令提示工具查看对象模型。

①代码

#include<iostream>
using namespace std;
#include<string>

class BaseClass {
public:
	int class_a;
protected:
	int class_b;
private:
	int class_c;
};

class SonClass : public BaseClass {
public:
	int class_d;
};

void test_0218_0() {
	cout << "the SonClass size = " << sizeof(SonClass) << "." << endl;
}


int main() {
	cout << "hello ! world ! " << endl;
	test_0218_0();
    system("pause");
    return 0;
}

运行结果:

image-20240218211808554

可以看到,只是编译器以及协议说明父类的私有权限下的内容无法访问,但是子类仍然有其成员属性。

②利用开发人员命令提示工具查看对象模型(开始菜单中)

进入当前文件所在文件夹后,用cl /d1 reportSingleClassLayout类名 "文件名"查看当前类的排列信息。

image-20240218212407495

可以看到SonClass占16字节,因为有4个整型构成。

同样,查看BaseClass。

image-20240218212526728

最后结论:父类中私有成员也被子类继承,只是由编译器隐藏后无法访问。

继承中构造和析构顺序

在父类和子类初始化时,它们的构造顺序和析构顺序是怎样的?

代码验证:

#include<iostream>
using namespace std;
#include<string>

class BaseClass {
public:
	BaseClass() {
		cout << "==父类==构造==函数==" << endl;
	}
	~BaseClass() {
		cout << "==父类==析构==函数==" << endl;
	}
public:
	int class_a;
protected:
	int class_b;
private:
	int class_c;
};

class SonClass : public BaseClass {
public:
	SonClass() {
		cout << "==子类==构造==函数==" << endl;
	}
	~SonClass() {
		cout << "==子类==析构==函数==" << endl;
	}
public:
	int class_d;
};

void test_0218_0() {
	SonClass sc;
	cout << "the SonClass size = " << sizeof(SonClass) << "." << endl;
}


int main() {
	cout << "hello ! world ! " << endl;
	test_0218_0();
    system("pause");
    return 0;
}

运行结果

image-20240218213014038

由运行结果可以看出,是父类先构造,子类再构造,而释放时,是由子类先释放,父类后释放。

同名成员处理

问题:父类和子类若有同名的成员属性和成员函数,如何分别访问?有何区别?

结论:子类继承父类后,新建子类对象,会访问到子类成员属性和成员函数。如需访问父类成员属性或成员函数,需加作用域。

延申提问:①重名但是不重类型的成员属性,是否会冲突?②重名成员函数若在父类中重载,重载为有参的成员函数,是否能访问到父类成员函数?

对应结论:①②不可以,发生重名成员函数,即编译器隐藏掉父类所有对应成员函数,除非加上作用域访问。

成员属性&成员函数举例:

#include<iostream>
#include<string>
using namespace std;

class BaseClass {

public:
	BaseClass() {
		class_a = 100;
	}
	int class_a;

	void print() {
		cout << "Base Class print() function 调用." << endl;
	}

	//void print(int a):class_a(a) {//这个只有在构造函数成员函数初始化时才能用
	void print(int a ){
		cout << "Base Class print ( int a ) function 调用." << endl;
	}

};

class SonClass:public BaseClass {
public:
	SonClass() {
		class_a = 200;
	}
	int class_a;

	void print() {
		cout << "SonClass print function 调用." << endl;
	}
};

//同名成员属性的处理方式
void test_0219_0() {
	SonClass sc;
	cout << "sc类的class_a = " << sc.class_a << "." << endl;
	cout << "sc父类BaseClass同名类成员访问为:" << sc.BaseClass::class_a << "." << endl;

	sc.print();
	sc.BaseClass::print(100);

}

int main(){
    cout<<"hello world !"<<endl;
    test_0219_0();
    
    system("pause");
    return 0;
}

运行结果:image-20240219225226096

同名静态成员处理

静态成员变量:所有对象共享同一块数据,编译之前分配内存,类内声明,类外初始化。

静态成员函数:只能访问静态成员变量,只有一份,所有对象共享同一份函数实例。

访问同名静态成员,访问方式与同名成员处理一致。但同名静态成员可以直接访问,静态成员变量访问时,单独的类可以直接访问,若想在子类中访问父类,::的含义为在子类中,访问父领域中的成员变量,成员函数也一致。

代码:

#include<iostream>
#include<string>
using namespace std;

class BaseClass {
public:
	static int class_a;
	static void static_print() {
		cout << "=================================" << endl;
		cout << "Base Class static_print() 调用." << endl;
		cout << "Base Class class_a = " << class_a << "." << endl;
		cout << "=================================" << endl;
	}
	static void static_print(int a) {
		cout << "=================================" << endl;
		cout << "Base Class static_print( int a )调用." << endl;
		cout << "Base Class a = " << a << endl;
		cout << "=================================" << endl;
	}
};

class SonClass :public BaseClass {
public:
	static int class_a;
	static void static_print() {
		cout << "-------------------------------------" << endl;
		cout << "Son Class static_print() 调用." << endl;
		cout << "Son Class class_a = " << class_a << "." << endl;
		cout << "-------------------------------------" << endl;
	}
};

int BaseClass::class_a = 100;
int SonClass::class_a = 200;


//同名成员函数的处理方式
void test_0219_1() {
	//通过对象访问
	SonClass sc;
	cout << "通过对象访问." << endl;
	sc.static_print();
	sc.BaseClass::static_print();
	sc.BaseClass::static_print(114);

	//直接访问
	cout << "直接访问." << endl;
	SonClass::static_print();
	SonClass::BaseClass::static_print();
	SonClass::BaseClass::static_print(514);
}



int main() {
	cout << "hello world !" << endl;
	test_0219_1();
	system("pause");
	return 0;
	}

运行结果:

image-20240219235515639

多继承语法

C++允许一个类继承多个类,语法是:子类 : 权限1 父类1,权限2 父类2,...权限n 父类n{}

出现同名成员函数或成员变量照以上处理。

代码:

#include<iostream>
#include<string>
using namespace std;

class BaseA {

public:
	BaseA() {
		a_a = 11;
	}
	int a_a;
};

class BaseB {
public:
	BaseB() {
		b_a = 21;
		a_a = 221;
	}
	int b_a;
	int a_a;
};

class BaseC {
public:
	BaseC() {
		c_a = 31;
	}
	int c_a;
};

class SonA : public BaseA , public BaseB , public BaseC {
public:
	void print() {
		cout << "BaseA a_a = " << BaseA::a_a << "." << endl;
		cout << "BaseB a_a = " << BaseB::a_a << ",BaseB b_a = " << b_a << "." << endl;
		cout << "BaseC c_a = " << c_a << "." << endl;
		cout << "size of SonA = " << sizeof(SonA) << "." << endl;
	}
};

void test_0219_2() {
	SonA sa; 
	sa.print();
}


int main() {
	cout << "hello world !" << endl;
	test_0219_2();
    system("pause");
    return 0;
}

运行结果:

image-20240220000243759

可以看到,子类继承父类中所有成员属性。

也可以用cl /d1 report singleClassLayoutSonA "Section2.cpp"查看子类布局。

打开开发者命令提示工具进入到对应文件目录中。

image-20240220010146179

菱形继承问题

父类被两个子类继承,孙类继承两个父类。马和驴继承四脚类,骡继承马和驴类,但是若四脚类有寿命,则马和驴都有寿命,在骡那里寿命应该继承马还是驴的?这个问题被称为菱形继承问题——孙类继承的重名的爷类成员属性,应该算作谁的?

原理暂且不谈,C++提供一种解决办法:虚继承,由编译器自己寻找冲突的变量,并自己制定一个最终的成员属性。

如果不使用虚继承,编译器会报错:

image-20240220013736235

代码:

#include<iostream>
#include<string>
using namespace std;

class Animal {
public:
	int animal_age;
};

class Horse : public Animal{

};

class Donkey : public Animal {

};

class Mule : public Horse ,public Donkey {

};

void test_0219_3() {
	Mule mule;
	mule.Horse::animal_age = 10;
	mule.Donkey::animal_age = 15;
	cout << "mule.Horse::animal_age = " << mule.Horse::animal_age << "." << endl;
	cout << "mule.Donkey::animal_age = " << mule.Donkey::animal_age << "." << endl;
}

int main() {
	cout << "hello world !" << endl;
	test_0219_3();
    system("pause");
    return 0;
}

但是这样是不符合常理的,因为即使继承,骡的寿命也只有一个——孙类的成员属性也只需要一个,造成了资源浪费:

image-20240220014007433

解决办法就是在继承父类的子类前加上virtual关键字。

代码:

#include<iostream>
#include<string>
using namespace std;

class Animal {
public:
	int animal_age;
};

class Horse : virtual public Animal{

};

class Donkey : virtual public Animal {

};

class Mule : public Horse ,public Donkey {

};

void test_0219_3() {
	Mule mule;
	mule.Horse::animal_age = 10;
	mule.Donkey::animal_age = 15;
	mule.animal_age = 20;
	cout << "mule.Horse::animal_age = " << mule.Horse::animal_age << "." << endl;
	cout << "mule.Donkey::animal_age = " << mule.Donkey::animal_age << "." << endl;
	cout << "mule.animal_age = " << mule.animal_age << "." << endl;
}

int main() {
	cout << "hello world !" << endl;
	test_0219_3();
	system("pause");
	return 0;
	}

运行结果:

image-20240220014408518

原理:

使用开发人员命令提示工具后,出现的是孙类继承子类的两个指针——vbptr,意为virtual base pointer,vbptr会指向一个vbtable。对应每个子类回指向对应的table,表中数据是偏移量,有了偏移量,会找到唯一的数据。

多态

多态基本概念

什么是多态?为什么使用多态?多态的语法是什么?怎么使用多态?在何时何地使用多态?

①什么是多态?多态分为静态多态以及动态多态。静态多态就是函数的重载——参数类型,个数,名称不同或者运算符重载,这些都属于静态多态。动态多态意为派生类和虚函数实现运行时多态,说人话就是同一个名称,不同的用途。

②在说明多态语法以及如何使用多态前,先说明为什么需要使用多态以及何时何地使用多态。多态常见于继承中,想要让子类实现特定的成员函数功能,但这个成员函数名又与父类成员函数名相同。(详见下列代码)那么,若想让子类中的特定成员函数名相同,就需要使用多态——在父类成员函数名前加virtual关键词。这样,就能够访问到子类中特定的成员函数。

#include<iostream>
#include<string>
using namespace std;

class Animal {
public:
	void owo() {
		cout << "Animals are owoing!" << endl;
	}
};
class Cat :public Animal {
public:
	void owo() {
		cout << "Cat is meowoing~" << endl;
	}
};

class Dog :public Animal {
public:
	void owo() {
		cout << "Dog is owolfing!!" << endl;
	}
};

//此做法就是早绑定,编译阶段就已确定函数地址。
//如果需要晚绑定,需要在父类成员函数前加virtual关键字,运行时再确定函数地址。
//这样看传入什么对象,传什么对象就走哪个对象对应的成员函数。
void owoing(Animal& animal) {//Animal &animal = cat/dog;父类引用指向子类对象,父子类可以随意转换在C++中。
	animal.owo();
}

void test_0220_0() {
	Cat cat;
	owoing(cat);
	Dog dog;
	owoing(dog);
}


int main() {
	cout << "hello world !" << endl;
	test_0220_0();
    system("pause");
    return 0;
}

如果不使用多态, 则传入不同的子类,会调用到父类的成员函数:

image-20240220194301793

这个原因是早绑定,在编译阶段就确定了函数的地址。所以,如果想通过传如不同的对象,需要晚绑定,在运行阶段再确定函数地址,从而调用对应不同对象的重名成员函数,就需要使用多态。

代码:

class Animal {
public:
	virtual void owo() {
		cout << "Animals are owoing!" << endl;
	}
};
class Cat :public Animal {
public:
	virtual void owo() {
		cout << "Cat is meowoing~" << endl;
	}
};

class Dog :public Animal {
public:
	virtual void owo() {
		cout << "Dog is owolfing!!" << endl;
	}
};

父类改为虚函数,子类需重写虚函数。

结果:

image-20240220200419739

具体做法就是在父类重名成员函数前加virtual关键字,这样就实现晚绑定。

所以,多态使用就需要满足两个条件。①有继承关系;②子类重写父类的虚函数。

说到重写与重载的关系。

④如何使用多态?常见为父类的指针或引用指向子类对象,

③多态的语法是什么?在重名的成员函数或者成员变量前加关键字virtual

静态多态&动态多态区别

静态多态函数地址早绑定——编译阶段确定函数地址。

动态多态函数地址晚绑定——运行阶段确定函数地址。

重写&重载

重写:见发生在多态,是返回值,函数名,参数都相同

重载:对于同一个函数名,只是函数参数类型,个数,顺序不同。

多态原理

一句话就是说我的理解:重写函数时,子类将父类的函数覆盖了,这样在实际调用时,即使Animal &animal = cat;cat.owoing();,调用的也是子类里面的成员函数,也不是父类里面的。

视频中讲解,一个类里面没有成员属性时,内存占用大小为1,用上多态virtual时,内存占用大小为4。因为此时有一个指针存在于类中——vfptr(virtual function pointer)虚函数(表)指针,指向一个vftable(virtual function table),表的内部记录虚函数的地址。

如果该类有子类,并且重写了该类的成员函数,那么该类的虚函数指针,就指向子类自己的虚函数表——但是同名成员函数。

所以,如果父类引用或对象指向子类对象——实例Animal &animal = cat ; cat.owoing();时,猫类虽然是动物类,但是它的虚函数指针指向的虚函数表中的虚函数是自己的虚函数。

多态重写计算器

有很多好处,①不用按照逻辑再去修改代码,直接去对应的类里面就可以,因为满足了多态条件:父类引用或指针指向子类对象。②易于阅读;无论是自己阅读,还是阅读别人的代码。能把代码写的既高效又易于阅读,算是合格的程序员。③组织结构清晰。

#include<iostream>
#include<string>
using namespace std;

//原来不用多态时的计算器,修改麻烦需要看流程。
class Calculator {
public:
	//string Calculator_tor;

	int calc(string tor) {
		if (tor == "+") {
			return num + ber;
		}
		else if (tor == "-") {
			return num - ber; 
		}
		else if (tor == "*") {
			return num * ber;
		}
		else {
			return 0;
		}
	}
	int num;
	int ber;
};

class CalculatorPlus {
public:
	int num;
	int ber;
	virtual int get_result() {
		//这里忘记加return值了。
		return 0;
	}
};

class AddCalcPlus : public CalculatorPlus {
public:
	virtual int get_result() {
		return num + ber;
	}
};

class SubtractPlus : public CalculatorPlus {
public:
	virtual int get_result() {
		return num - ber;
	}
};

class TimesPlus : public CalculatorPlus {
public:
	virtual int get_result() {
		return num * ber;
	}
};



void test_0221_0() {
	Calculator calc;
	calc.num = 10;
	calc.ber = 20;
	cout << "num(" << calc.num << ")+ ber(" << calc.ber << ")= " << calc.calc("+") << "." << endl;
	cout << "num(" << calc.num << ")- ber(" << calc.ber << ")= " << calc.calc("-") << "." << endl;
	cout << "num(" << calc.num << ")* ber(" << calc.ber << ")= " << calc.calc("*") << "." << endl;
}

void test_0221_1() {
	//多态条件:①父类虚函数子类重写;②父类指针或引用指向子类对象
	CalculatorPlus* calcPlus = new AddCalcPlus();
	calcPlus->num = 10;
	calcPlus->ber = 20;
	cout << "num = " << calcPlus->num << " , ber = " << calcPlus->ber << " . " << endl;
	cout << "num+ber = " << calcPlus->get_result() << " . " << endl;
	cout << "===============" << endl;
	delete calcPlus;
	//需要及时释放父类指向子类对象的指针

	calcPlus = new SubtractPlus();
	calcPlus->num = 10;
	calcPlus->ber = 20;
	cout << "num = " << calcPlus->num << " , ber = " << calcPlus->ber << " . " << endl;
	cout << "num - ber = " << calcPlus->get_result() << " . " << endl;
	cout << "===============" << endl;
	delete calcPlus;

	calcPlus = new TimesPlus();
	calcPlus->num = 10;
	calcPlus->ber = 20;
	cout << "num = " << calcPlus->num << " , ber = " << calcPlus->ber << " . " << endl;
	cout << "num * ber = " << calcPlus->get_result() << " . " << endl;
	cout << "===============" << endl;
	delete calcPlus;
}

int main() {
	cout << "hello world !" << endl;
	test_0221_1();
    system("pause");
    return 0;
}

运行结果:

image-20240221182827994

纯虚函数和抽象类

场景:

在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容。因此可以将虚函数改为纯虚函数。

目的:方便代码修改和阅读。

语法:virtual return_type function_name (paramters) = 0;

纯虚函数定义之后,存在有纯虚函数的类不能实例化对象,且子类必须要重写纯虚函数,否则该子类也不能实例化对象。

实验验证:①存有纯虚函数的类不能实例化对象;②子类必须要重写纯虚函数;③否则该子类也不能实例化对象。

由图片可知,①存在有纯虚函数的类不能实例化对象。

#include<iostream>
#include<string>
using namespace std;

class Base {
public:
	//纯虚函数的定义方式:在最后=0
	//拥有纯虚函数的类不能实例化对象,栈区堆区都不能。子类必须重写该纯虚函数,否则也是抽象类。
	virtual void print() = 0;
};
void test0222_0() {
	Base base;
	Base* base = new Base();
}

int main() {
	cout << "hello world !" << endl;
	test0222_0();
    system("pause");
    return 0;
}

image-20240222224150153

image-20240222224233573

②子类必须要重写纯虚函数。

#include<iostream>
#include<string>
using namespace std;

class Base {
public:
	//纯虚函数的定义方式:在最后=0
	//拥有纯虚函数的类不能实例化对象,栈区堆区都不能。子类必须重写该纯虚函数,否则也是抽象类。
	virtual void print() = 0;
};

class FirstLevel : public Base {
public:
	virtual void print() {
		cout << "print函数调用" << "." << endl;
	}
};

void test0222_0() {
	//Base base;
	//Base* base = new Base();
	Base* base = new FirstLevel;
	//FirstLevel base;
	base->print();
}

int main() {
	cout << "hello world !" << endl;
	test0222_0();
    system("pause");
    return 0;
}

image-20240222224732359

③子类如果不写纯虚函数,那也是不能实例化对象。

#include<iostream>
#include<string>
using namespace std;

class Base {
public:
	//纯虚函数的定义方式:在最后=0
	//拥有纯虚函数的类不能实例化对象,栈区堆区都不能。子类必须重写该纯虚函数,否则也是抽象类。
	virtual void print() = 0;
};

class FirstLevel : public Base {
public:
	//virtual void print() {
		//cout << "print函数调用" << "." << endl;
	//}
};

void test0222_0() {
	//Base base;
	//Base* base = new Base();
	//Base* base = new FirstLevel;
	FirstLevel base;
	base->print();
}

int main() {
	cout << "hello world !" << endl;
	test0222_0();
    system("pause");
    return 0;
}

image-20240222224844413

虚析构&纯虚析构函数

场景:如果子类里面在堆区开辟了内存,没有由程序员手动释放,会造成内存泄露问题。

多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。

目的:解决场景中出现的内存泄漏问题。

解决:

将父类中的析构函数改为虚析构或者纯虚析构。

#include<iostream>
#include<string>
using namespace std;

class Base {
public:
	//构造与析构
	Base() {
		cout << "Base Class 构造——函数调用." << endl;
	}
	~Base() {
		cout << "Base Class 析构——函数调用." << endl;
	}

public:
	//纯虚函数
	virtual void print() = 0;
};

class First:public Base{
public:
	//成员属性声明
	string *first_name;

public:
	//构造函数与析构函数
	First(string class_name):first_name(new string(class_name)) {
		cout << "First Class name = "<<*first_name<<" 构造——函数调用." << endl;
	}
	~First() {
		cout << "First Class 析构——函数调用." << endl;
		//释放first_name指针指向的内存
		if (first_name != NULL) {
			cout << "释放first_name指针" << "." << endl;
			delete first_name;
			first_name = NULL;
		}
	}

public:
	//纯虚函数实现
	virtual void print() {
		cout << "print函数调用" << "." << endl;
	}
};

void test0222_1() {
	string class_name = "first class";
	Base* base = new First(class_name);
	base->print();
	delete base;
}

int main() {
	cout << "hello world !" << endl;
	test0222_1();
    system("pause");
    return 0;
}

此时First类的析构函数并没有调用,说明堆区的数据没有释放干净,导致了内存泄漏。父类在删除时,不会调用子类的析构函数。

image-20240222232750307

当改为虚析构函数时,代码如:

#include<iostream>
#include<string>
using namespace std;

class Base {
public:
	//构造与析构
	Base() {
		cout << "Base Class 构造——函数调用." << endl;
	}
	virtual ~Base()
	{
		cout << "Base Class 析构——函数调用." << endl;
	}

public:
	//纯虚函数
	virtual void print() = 0;
};

class First:public Base{
public:
	//成员属性声明
	string *first_name;

public:
	//构造函数与析构函数
	First(string class_name):first_name(new string(class_name)) {
		cout << "First Class name = "<<*first_name<<" 构造——函数调用." << endl;
	}
	virtual ~First() {
		cout << "First Class 析构——函数调用." << endl;
		//释放first_name指针指向的内存
		if (first_name != NULL) {
			cout << "释放first_name指针" << "." << endl;
			delete first_name;
			first_name = NULL;
		}
	}

public:
	//纯虚函数实现
	virtual void print() {
		cout << "print函数调用" << "." << endl;
	}
};

void test0222_1() {
	string class_name = "first class";
	Base* base = new First(class_name);
	base->print();
	delete base;
}

int main() {
	cout << "hello world !" << endl;
	test0222_1();
    system("pause");
    return 0;
}

运行结果如下:

image-20240222233043458

当改为纯虚析构函数时,需要在类外重写该类的纯虚析构函数。

class Base {
public:
	//构造与析构
	Base() {
		cout << "Base Class 构造——函数调用." << endl;
	}
	virtual ~Base()=0;
public:
	//纯虚函数
	virtual void print() = 0;
};

Base::~Base() {
	cout << "Base Class 析构——函数调用." << endl;
}

image-20240222233303850

总结:如果子类中有在堆区开辟内存,而父类没有其他操作,会造成子类指针没有及时释放,从而内存泄漏。

所以,需要在父类中用虚析构或者纯虚析构来解决内存泄漏问题。虚析构不用再在子类中重写,需要在父类这里写好,对于虚析构,直接在类中写;对于纯虚析构,则需要在类外写,因为纯虚析构=0。

另外,有了纯虚析构后,该类也是抽象类,无法实例化对象。

虚析构&纯虚析构共性:①可以解决父类指针释放子类对象;②都需要具体的函数实现。

如果只写了虚析构,没有虚函数,那这个类还是可以实例化对象。

电脑组装需求案例

//
// Created by HelpFire on 2024/2/23.
//
#include<string>
#include<iostream>
using namespace std;
void test0223_0();

class CPU{
public:
    //string cpu;
    virtual void calc() = 0;
//    virtual ~CPU()=0;
};

class Card{
public:
    virtual void display()=0;
//    virtual ~Card()=0;
};

class Memory{
public:
    virtual void load()=0;
//    virtual ~Memory()=0;
};

class MainBoard {
public:
    //主板上要有显卡,内存,cpu
    virtual void assemble() = 0;
//    virtual ~MainBoard()=0;
};

class Intel : public CPU ,public Card ,public Memory ,public MainBoard{
//    黑马这里把Intel分别分开了,需要分开,因为这样相当于一个Intel电脑了
public:
    void calc() override{
        cout<<"Intel CPU 在执行调度程序."<<endl;
    }
    void display() override{
        cout<<"Intel GPU 在执行图像处理."<<endl;
    }
    void load() override{
        cout<<"Intel Memory 内存已载入."<<endl;
    }
    void assemble() override{
        cout<<"Intel 主板已装好."<<endl;
    }
};

class IntelCPU : public CPU{
    void calc() override{
        cout<<"Intel CPU 在执行调度程序."<<endl;
    }
};

class IntelCard:public Card{
    void display() override{
        cout<<"Intel GPU 在执行图像处理."<<endl;
    }
};
class IntelMemory:public Memory{
    void load() override{
        cout<<"Intel Memory 内存已载入."<<endl;
    }
};
class IntelMainBoard:public MainBoard{
    void assemble() override{
        cout<<"Intel 主板已装好."<<endl;
    }
};

class Mi : public CPU ,public Card ,public Memory ,public MainBoard{
//    黑马这里把Intel分别分开了,需要分开,因为这样相当于一个Intel电脑了
public:
    void calc() override{
        cout<<"Mi CPU 在执行调度程序."<<endl;
    }
    void display() override{
        cout<<"Mi GPU 在执行图像处理."<<endl;
    }
    void load() override{
        cout<<"Mi Memory 内存已载入."<<endl;
    }
    void assemble() override{
        cout<<"Mi 主板已装好."<<endl;
    }
};

class MiCPU : public CPU{
    void calc() override{
        cout<<"Mi CPU 在执行调度程序."<<endl;
    }
};

class MiCard:public Card{
    void display() override{
        cout<<"Mi GPU 在执行图像处理."<<endl;
    }
};
class MiMemory:public Memory{
    void load() override{
        cout<<"Mi Memory 内存已载入."<<endl;
    }
};
class MiMainBoard:public MainBoard{
    void assemble() override{
        cout<<"Mi 主板已装好."<<endl;
    }
};

class Computer{
//public://这里应该写private
private:
    CPU *computer_cpu;
    Card *computer_card;
    Memory *computer_memory;
    MainBoard *computer_board;

public:
    Computer(CPU *cpu , Card *card , Memory *memory , MainBoard *board){
        computer_cpu = cpu;
        computer_card = card;
        computer_memory = memory;
        computer_board = board;
    }
    ~Computer(){
        cout<<"释放堆区内存."<<endl;
        if (computer_cpu != NULL){
            delete computer_cpu;
            com
        }
        if (computer_memory != NULL){
            delete computer_memory;
        }
        if (computer_board != NULL){
            delete computer_board;
        }
        if (computer_card != NULL){
            delete computer_card;
        }
    }

public:
    void work(){
        computer_board->assemble();
        computer_memory->load();
        computer_cpu->calc();
        computer_card->display();
    }
};

class Base{
public:
    ~Base(){
        cout<<"释放堆区内存."<<endl;
    }
};

void test0223_1(){
    Base *ba = new Base;
    delete ba;
    Base *se = new Base;
    delete se;
}

int main(){
//    test0223_1();
    test0223_0();
    return 0;
}

void test0223_0() {
//    如果用这种方式,删除一个intel就够了,我写的这个有问题,无法释放干净。

    CPU *Intel_cpu = new Intel;
    Card *Intel_card = new Intel;
    Memory *Intel_memory = new Intel;
    MainBoard *Intel_board = new Intel;

    cout<<"--------------------------"<<endl;
    cout<<"Intel厂家出的全家桶👇"<<endl;
    Computer *computer = new Computer(Intel_cpu , Intel_card , Intel_memory,Intel_board);
    //Computer Intel (Intel_cpu , Intel_card , Intel_memory,Intel_board);
    //Intel.work();
    delete computer;

    cout<<"--------------------------"<<endl;
    cout<<"小米厂家出的全家桶👇"<<endl;
    Computer *Mi = new Computer(new MiCPU , new MiCard , new MiMemory,new MiMainBoard);
    Mi->work();
    delete Mi;

    cout<<"--------------------------"<<endl;
    cout<<"小米&Intel厂家出的混沌桶👇"<<endl;
    Computer Mitel(new MiCPU  , Intel_card , new MiMemory , Intel_board);
    Mitel.work();

//    需求:电脑主要组成部件为
//    CPU (用于计算) ,显卡 (用于显示),内存条(用于存储),主板(用于加载)
//    将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如intel厂商和Lenovo厂商
//    创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口测试时组装三台不同的电脑进行工作

    //问题:这些抽象基类,我要怎么用?因为之前的是父类引用或指针指向子类对象。
    //所以它应该要指向什么?指向电脑吗?
//    不指向电脑,而是指向特定厂商的子类。对于指定电脑来说,作为参数传入接受就好,最好是指针。

//    new 出来的是在堆区开辟的内存空间,需要指针接收。链接:https://blog.csdn.net/bzhxuexi/article/details/46623063

    //以上的指针都是在堆区创建,需要释放。所以,最好就是在最后的类那里用完就释放。析构函数需要重写。
//    析构函数的重写,要么就在原先的类里面写,要么就在最外面类写。

//为什么只运行了一次,释放堆区内存?做个小实验。

}

问题:对于A类继承多个类,还传入N个类的指针,如何析构函数使其能够顺利释放内存?

根据GPT4的回答。

这段代码尝试展示一个计算机组件(CPU、显卡、内存、主板)的组装过程,并使用继承和多态的方式来实现。主要问题是如何正确地管理资源,尤其是在使用多重继承时。这段代码在资源管理方面存在几个问题,以下是主要的几点:

  1. 多重删除问题Intel 类同时继承了 CPUCardMemoryMainBoard 类。在 test0223_0 函数中,您为每个接口创建了一个 Intel 实例,实际上它们都指向相同类型的对象。但在 Computer 的析构函数中,您尝试删除了这些指针,这将导致对同一个对象的多次删除尝试,从而触发未定义行为。

  2. 解决方法:一个可能的解决方法是,在 Computer 类中只保留一个 Intel 类型的指针,而不是为每种类型的组件保留独立的指针。这样,您就可以确保只删除一次对象。但这样做会让 Computer 类与 Intel 类紧密耦合,降低了代码的灵活性和可重用性。

  3. 设计改进:考虑到您的设计意图,一个更好的方法可能是避免在 Computer 类的析构函数中删除这些组件。相反,您可以在创建这些组件的相同作用域(test0223_0 函数)中删除它们,或者使用智能指针来自动管理这些资源,从而避免手动删除所导致的问题。

  4. 使用智能指针:您可以使用 std::unique_ptrstd::shared_ptr 来自动管理资源。这样,当指针离开作用域或被重置时,它们指向的对象会自动被删除。使用智能指针还可以防止忘记删除对象,减少内存泄漏的风险。

  5. // 使用智能指针改进资源管理
    #include <memory> // 引入智能指针头文件
    // ...
    // 在 Computer 类中使用智能指针
    std::unique_ptr<CPU> computer_cpu;
    std::unique_ptr<Card> computer_card;
    std::unique_ptr<Memory> computer_memory;
    std::unique_ptr<MainBoard> computer_board;
    // ...
    // 在 test0223_0 函数中使用智能指针创建 Intel 对象
    auto Intel_cpu = std::make_unique<Intel>();
    // 只需创建一个 Intel 对象,并将其作为各种组件的引用传递给 Computer
    Computer *computer = new Computer(Intel_cpu.get(), Intel_cpu.get(), Intel_cpu.get(), Intel_cpu.get());
    
    

    请注意,改进设计时还需要考虑如何处理 Computer 类对组件的所有权和生命周期的管理,以确保不会出现内存泄漏或其他资源管理问题。
    释放一次就够了


以上是我的学习笔记,希望对你有所帮助!
如有不当之处欢迎指出!谢谢!

学吧,学无止境,太深了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/510467.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中仕公考:不同省事业编考试内容一样吗?

在各省份的事业单位公开招聘过程中&#xff0c;考试内容是不一样的。虽然每一年都有很多省份参加事业编联考&#xff0c;且这些联考的考试日期相同&#xff0c;但是考试内容并不一样。各省将依据当地的特定情况来设计考试题目&#xff0c;其中会涉及到与本省的地理、经济、文化…

文章分享:《呼吸道传染病标本采集及检测专家共识》

【摘要】呼吸道传染病临床特点多表现为发热和&#xff08;或&#xff09;呼吸道症状&#xff0c;病原学组成复杂&#xff0c;标本类型选择多样&#xff0c;如何从发热伴呼吸道症候群患者中早期正确识别出潜在呼吸道传染病患者是防控的关键环节。增强医务人员对呼吸道传染病临床…

【现代C++】Lambda表达式

在现代C中&#xff0c;Lambda表达式提供了定义匿名函数对象的能力&#xff0c;这在很多场合&#xff0c;如STL算法、线程构造函数、事件处理等&#xff0c;都非常有用。Lambda表达式使得函数定义更加灵活和简洁。 1. 基本语法 Lambda表达式的基本形式包括一个捕获列表、参数列…

【微信小程序】流量主-激励视频(激励广告)下发策略,每天三次免费体验,然后再次点击触发激励视频,当日不再触发。

如题&#xff1a; 允许用户有三次体验效果&#xff0c;然后弹出激励视频弹窗&#xff0c;之后当日不再弹出。 体验小程序&#xff1a; /*** 判断当前项目当天是否点击超过3次&#xff0c;触发广告效果。* 若&#xff0c;当天低于三次&#xff0c;则新增&#xff0c;若高于…

未来电商怎么走,是腾讯视频号,还是抖音小店?你更看好谁?

大家好&#xff0c;我是电商花花。 现在想做一些项目&#xff0c;做些生意基本上就离不开电商&#xff0c;网络这么发达&#xff0c;直播短视频这么火&#xff0c;未来的发展依然是直播电商。 现在线下很多实体店都被电商所冲击&#xff0c;而且不少实体店也都在网上开店卖货…

Spring 整合 Log4j2日志框架

1. Log4j2日志概述 在项目开发中&#xff0c;日志十分的重要&#xff0c;不管是记录运行情况还是定位线上问题&#xff0c;都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息&#xff0c;能够帮助我们了解并监控系统状态&#xff0c;在发生错误或者接近某…

【面试HOT200】数组篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试coding部分的&#xff0c;整理期间苛求每个算法题目&#xff0c;平衡可读性与代码性能&#xff08;leetcode运行复杂度均打败80%以上&#xff09;。 &#x1f970;来源&#xff1a;材料主要源于…

linux删除 buff/cache缓存

1.查看当前内存占用 free -h如图&#xff0c;缓存占用了将近9G&#xff0c;接下来进行清理 释放页缓存 echo 1 > /proc/sys/vm/drop_caches释放dentries和inodes echo 2 > /proc/sys/vm/drop_caches释放所有缓存 echo 3 > /proc/sys/vm/drop_caches再次查看&#…

MQ

目录 MQ优点 异步 解耦 削峰填谷 mq的缺点 MQ常见的几种模式 Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别 MQ优点 mq是一种常见的中间件&#xff0c;在项目中经常用到&#xff0c;它具有异步、解耦、削峰填谷的作用。 异步 比如下单流程&#xff0c;A服务—>B服务&a…

为什么称FreeRTOS为轻量级OS,和Linux相比,有哪些具体的区别?

要理解它们&#xff0c;就要看这些最终的概念是怎么来的&#xff0c;其实这些都是在不同资源&#xff08;硬件&#xff09;上处理不同场景问题所得的结果。 FreeRTOS一般跑在几十Mhz&#xff0c;几十KB的硬件上&#xff0c;比如Cortex-M系列MCU上&#xff0c;资源很有限&#…

论文精读--GPT4

现有的所有模型都无法做到在线学习&#xff0c;能力有限&#xff0c;而让大模型拥有一个tools工具库&#xff0c;则可以使大模型变成一个交互式的工具去协调调用API完成任务&#xff0c;同时GPT4还联网了&#xff0c;可以不断地更新自己的知识库 多模态模型&#xff0c;接受文…

面经分享(Flask,轻量级Web框架)

1. Flask的核心特点 a. 轻量级&#xff1a;核心简洁&#xff0c;只提供了基本的功能&#xff0c;其他高级功能可以通过插件或扩展来添加。 b. 灵活性&#xff1a;允许开发者选择适合自己项目的组件和工具&#xff0c;没有强制的项目结构和设计模式。 c. 易于扩展&#xff1a;提…

JVM 内存溢出排查

说明&#xff1a;记录一次JVM内存溢出的排查过程&#xff1b; 场景 项目开发完成后&#xff0c;首次提交到测试环境。测试、产品同事反馈页面先是操作响应慢&#xff0c;抛出超时异常&#xff0c;最后直接无法使用。查看日志后得知是内存溢出。 重启服务后&#xff0c;我对前…

接口测试实战教程(加密解密攻防)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、对称加密 对称加密算法是共享密钥加密算法&#xff0c;在加…

RuntimeError: Error compiling objects for extension虚拟环境和系统环境——添加、删除、修改环境变量

前言&#xff1a;因为一个报错RuntimeError: Error compiling objects for extension 没有配置cl.exe环境变量&#xff0c;我的应用场景是需要搞定虚拟环境变量配置 RuntimeError: Error compiling objects for extension手把手带你解决&#xff08;超详细&#xff09;-CSDN博…

Mybatis 之 useGeneratedKeys

数据库中主键基本都设置为自增&#xff0c;当我们要插入一条数据想要获取这条数据的 Id 时&#xff0c;就可使用 Mybatis 中的 useGeneratedKeys 属性。 背景 这里以 苍穹外卖 中的 新增菜品 功能为例&#xff0c;有 菜品表(dish table)和 口味表(dish_flavor table)&#xf…

HTML标签之有序列表,无序列表,自定义链表

无序列表 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, i…

从零开始学数据分析之数据分析概述

当今世界对信息技术的依赖程度在不断加深&#xff0c;每天都会有大量的数据产生&#xff0c;我们经常会感到数据越来越多&#xff0c;但是要从中发现有价值的信息却越来越难。 这里所说的信息&#xff0c;可以理解为对数据集处理之后的结果&#xff0c;是从数据集中提炼出的可用…

redis 高可用与 持久化

目录 一 redis 高可用 &#xff08;一&#xff09;redis高可用技术 1&#xff0c;持久化 2&#xff0c;主从复制 3&#xff0c;哨兵 4&#xff0c;Cluster集群 二 redis 持久化 &#xff08;一&#xff09;持久化相关概念介绍 1&#xff0c;持久化的功能 2&…

Pygame基础9-射击

简介 玩家用鼠标控制飞机&#xff08;白色方块&#xff09;移动&#xff0c;按下鼠标后&#xff0c;玩家所在位置出现子弹&#xff0c;子弹匀速向右飞行。 代码 没有什么新的东西&#xff0c;使用两个精灵类表示玩家和子弹。 有一个细节需要注意&#xff0c;当子弹飞出屏幕…