【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 类对组件的所有权和生命周期的管理,以确保不会出现内存泄漏或其他资源管理问题。
    释放一次就够了


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

学吧,学无止境,太深了

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HelpFireCode

随缘惜缘不攀缘。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值