你好你好!
以下内容仅为当前认识,可能有不足之处,欢迎讨论!
文章目录
- 继承
- 继承语法
- 继承方式
- 继承中的对象模型
- 继承中构造和析构顺序
- 同名成员处理
- 同名静态成员处理
- 多继承语法
- 菱形继承问题
- 多态
- 多态基本概念
- 重写&重载
- 多态原理
- 多态重写计算器
- 纯虚函数和抽象类
- 虚析构&纯虚析构函数
- 电脑组装需求案例
继承
继承语法
目的:减少代码复用
语法:在类后面写一个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;
}
运行结果:
此处有疑惑,继承的父类不能有构造函数?
继承方式
公有继承:父类里面成员属性是什么权限,之后仍然是什么权限。
保护权限:父类里面的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;
}
};
根据图可以看出,在继承方式为public
时,无法访问父类中private
权限下的内容,只能访问权限为public
和protected
下的内容。此时,父类与子类的权限变化如下表格
继承方式为public 权限/类 | 父类 | 子类 |
---|---|---|
继承方式为public 权限 | public | public |
继承方式为public 权限 | protected | protected |
继承方式为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
权限下的内容仍然无法访问。
权限变化如下表格:
继承方式为protected 权限/类 | 父类 | 子类 |
---|---|---|
继承方式为protected 权限 | public | protected |
继承方式为protected 权限 | protected | protected |
继承方式为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;
}
};
如图所示:
可以看到无法访问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;
}
运行结果:
可以看到,只是编译器以及协议说明父类的私有权限下的内容无法访问,但是子类仍然有其成员属性。
②利用开发人员命令提示工具查看对象模型(开始菜单中)
进入当前文件所在文件夹后,用cl /d1 reportSingleClassLayout类名 "文件名"
查看当前类的排列信息。
可以看到SonClass占16字节,因为有4个整型构成。
同样,查看BaseClass。
最后结论:父类中私有成员也被子类继承,只是由编译器隐藏后无法访问。
继承中构造和析构顺序
在父类和子类初始化时,它们的构造顺序和析构顺序是怎样的?
代码验证:
#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;
}
运行结果
由运行结果可以看出,是父类先构造,子类再构造,而释放时,是由子类先释放,父类后释放。
同名成员处理
问题:父类和子类若有同名的成员属性和成员函数,如何分别访问?有何区别?
结论:子类继承父类后,新建子类对象,会访问到子类成员属性和成员函数。如需访问父类成员属性或成员函数,需加作用域。
延申提问:①重名但是不重类型的成员属性,是否会冲突?②重名成员函数若在父类中重载,重载为有参的成员函数,是否能访问到父类成员函数?
对应结论:①②不可以,发生重名成员函数,即编译器隐藏掉父类所有对应成员函数,除非加上作用域访问。
成员属性&成员函数举例:
#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;
}
运行结果:
同名静态成员处理
静态成员变量:所有对象共享同一块数据,编译之前分配内存,类内声明,类外初始化。
静态成员函数:只能访问静态成员变量,只有一份,所有对象共享同一份函数实例。
访问同名静态成员,访问方式与同名成员处理一致。但同名静态成员可以直接访问,静态成员变量访问时,单独的类可以直接访问,若想在子类中访问父类,::
的含义为在子类中,访问父领域中的成员变量,成员函数也一致。
代码:
#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;
}
运行结果:
多继承语法
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;
}
运行结果:
可以看到,子类继承父类中所有成员属性。
也可以用cl /d1 report singleClassLayoutSonA "Section2.cpp"
查看子类布局。
打开开发者命令提示工具进入到对应文件目录中。
菱形继承问题
父类被两个子类继承,孙类继承两个父类。马和驴继承四脚类,骡继承马和驴类,但是若四脚类有寿命,则马和驴都有寿命,在骡那里寿命应该继承马还是驴的?这个问题被称为菱形继承问题——孙类继承的重名的爷类成员属性,应该算作谁的?
原理暂且不谈,C++提供一种解决办法:虚继承,由编译器自己寻找冲突的变量,并自己制定一个最终的成员属性。
如果不使用虚继承,编译器会报错:
代码:
#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;
}
但是这样是不符合常理的,因为即使继承,骡的寿命也只有一个——孙类的成员属性也只需要一个,造成了资源浪费:
解决办法就是在继承父类的子类前加上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;
}
运行结果:
原理:
使用开发人员命令提示工具后,出现的是孙类继承子类的两个指针——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;
}
如果不使用多态, 则传入不同的子类,会调用到父类的成员函数:
这个原因是早绑定,在编译阶段就确定了函数的地址。所以,如果想通过传如不同的对象,需要晚绑定,在运行阶段再确定函数地址,从而调用对应不同对象的重名成员函数,就需要使用多态。
代码:
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;
}
};
父类改为虚函数,子类需重写虚函数。
结果:
具体做法就是在父类重名成员函数前加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;
}
运行结果:
纯虚函数和抽象类
场景:
在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容。因此可以将虚函数改为纯虚函数。
目的:方便代码修改和阅读。
语法: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;
}
②子类必须要重写纯虚函数。
#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;
}
③子类如果不写纯虚函数,那也是不能实例化对象。
#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;
}
虚析构&纯虚析构函数
场景:如果子类里面在堆区开辟了内存,没有由程序员手动释放,会造成内存泄露问题。
多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
目的:解决场景中出现的内存泄漏问题。
解决:
将父类中的析构函数改为虚析构或者纯虚析构。
#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类的析构函数并没有调用,说明堆区的数据没有释放干净,导致了内存泄漏。父类在删除时,不会调用子类的析构函数。
当改为虚析构函数时,代码如:
#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;
}
运行结果如下:
当改为纯虚析构函数时,需要在类外重写该类的纯虚析构函数。
class Base {
public:
//构造与析构
Base() {
cout << "Base Class 构造——函数调用." << endl;
}
virtual ~Base()=0;
public:
//纯虚函数
virtual void print() = 0;
};
Base::~Base() {
cout << "Base Class 析构——函数调用." << endl;
}
总结:如果子类中有在堆区开辟内存,而父类没有其他操作,会造成子类指针没有及时释放,从而内存泄漏。
所以,需要在父类中用虚析构或者纯虚析构来解决内存泄漏问题。虚析构不用再在子类中重写,需要在父类这里写好,对于虚析构,直接在类中写;对于纯虚析构,则需要在类外写,因为纯虚析构=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、显卡、内存、主板)的组装过程,并使用继承和多态的方式来实现。主要问题是如何正确地管理资源,尤其是在使用多重继承时。这段代码在资源管理方面存在几个问题,以下是主要的几点:
多重删除问题:
Intel
类同时继承了CPU
、Card
、Memory
和MainBoard
类。在test0223_0
函数中,您为每个接口创建了一个Intel
实例,实际上它们都指向相同类型的对象。但在Computer
的析构函数中,您尝试删除了这些指针,这将导致对同一个对象的多次删除尝试,从而触发未定义行为。解决方法:一个可能的解决方法是,在
Computer
类中只保留一个Intel
类型的指针,而不是为每种类型的组件保留独立的指针。这样,您就可以确保只删除一次对象。但这样做会让Computer
类与Intel
类紧密耦合,降低了代码的灵活性和可重用性。设计改进:考虑到您的设计意图,一个更好的方法可能是避免在
Computer
类的析构函数中删除这些组件。相反,您可以在创建这些组件的相同作用域(test0223_0
函数)中删除它们,或者使用智能指针来自动管理这些资源,从而避免手动删除所导致的问题。使用智能指针:您可以使用
std::unique_ptr
或std::shared_ptr
来自动管理资源。这样,当指针离开作用域或被重置时,它们指向的对象会自动被删除。使用智能指针还可以防止忘记删除对象,减少内存泄漏的风险。// 使用智能指针改进资源管理 #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
类对组件的所有权和生命周期的管理,以确保不会出现内存泄漏或其他资源管理问题。
释放一次就够了
以上是我的学习笔记,希望对你有所帮助!
如有不当之处欢迎指出!谢谢!