C++学习笔记(5)---继承(静态同名,多继承,虚继承),多态(虚函数,虚析构与纯虚析构)

目录

1 继承---面向对象三大特性之一

 2 多态---面向对象三大特性之一


1 继承---面向对象三大特性之一

1.1 继承的基本语法

作用:减少重复的代码,(A类==子类==派生类;B类==父类==派生类)

语法:class 子类:继承方式 父类

class A:public B;

派生类中的成员包含两大部分:(1)一类是从基类继承过来的;(2)一类是自己增加的成员;

从基类继承过来的表现其共性,而新增的成员体现了其个性。

1.2 继承方式

三种:(1)公共继承;(2)保护继承;(3)私有继承;

 1.3 继承中的对象模型

//------------练习22:继承中的对象模型-------------------
class Base//父类,基类
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son:public Base//子类
{
public:
	int m_D;
};
int main()
{
	Son test;//父类中所有非静态成员属性都会被子类继承下去
	//父类中私有成员属性,是被编译器隐藏了,因此访问不到,但确实被继承下去了
	cout<<sizeof(test)<<endl;//输出为16
	return 0;
}

  

1.4 构造和析构顺序

 子类继承父类后,当创建子类对象,也会调用父类的构造函数

 总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

 1.5 同名成员处理

 当子类和父类出现同名的成员,通过子类对象访问->

(1)访问子类同名成员,直接访问;(2)访问父类同名成员,需要加作用域

//同名成员属性处理
Son S;
cout<<"子类对象访问同名子类成员"<<S.m_A<<endl;
cout<<"子类对象访问同名父类成员"<<S.Base::m_A<<endl;
//同名成员函数处理
S.func();//直接调用,调用是子类中的同名成员
S.Base::func();//加作用域,调用是父类中的同名成员函数

注意:子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数,加作用域才可访问

1.6 同名静态成员处理---与同名成员处理一致

静态成员特点:共用同一份数据;编译就分配内存;类内声明,类外初始化;

//-------------练习24:子类和父类静态同名成员------------
//静态变量:共用同一份;编译分配内存;类内声明,类外初始化
class Base
{
public:
	static int m_A;
};
int Base::m_A=100;//类外初始化
class Son:public Base
{
public:
	static int m_A;
};
int Son::m_A=200;//类外初始化
void test01()//同名静态变量访问
{
//1、子类对象访问静态成员变量
	Son S1;
	cout <<"通过子类对象访问"<<endl;
	cout<<"子类静态成员变量:"<<S1.m_A<<endl;
	cout<<"父类静态成员变量:"<<S1.Base::m_A<<endl;
//2、子类类名访问静态成员变量
	cout <<"通过子类类名访问"<<endl;
	cout<<"子类静态成员变量:"<<Son::m_A<<endl;
	cout<<"父类静态成员变量:"<<Son::Base::m_A<<endl;
	//第一个::代表通过类名方式访问静态,第二个::代表访问父类作用域下
}
int main()
{
	test01();
	return 0;
}

总结:同名静态成员处理方式和非静态一样,只不过有两种访问方式(通过对象通过类名

1.7 多继承语法---C++允许一个类继承多个类

语法:class 子类:继承方式 父类1,继承方式 父类2

多继承可能会引发父类中有同名成员出现,需要加作用域区分---C++实际开发中不建议用多继承

class Son:public Base1,public Base2
{};
//当父类中出现同名成员,需要加作用域区分
cout<<"Base1::m_A="<<s.Base1::m_A<<endl;
cout<<"Base2::m_A="<<s.Base2::m_A<<endl;

1.8 菱形继承问题以及解决方法

1、菱形继承概念:

两个派生类继承同一个基类,又有某个类同时继承这两个派生类,称为菱形继承,或钻石继承

class Animal//虚基类
{
public:
	int m_Age;
};
class Sheep:virtual public Animal//虚继承
{
};
class Camel:virtual public Animal//虚继承
{
};
class Alpaca:public Sheep,public Camel
{
};
int main()
{
	Alpaca A;
	A.Sheep::m_Age=18;
	A.Camel::m_Age=20;
	cout<<"A.Sheep::m_Age"<<A.Sheep::m_Age<<endl;//20
	cout<<"A.Camel::m_Age"<<A.Camel::m_Age<<endl;//20
	cout<<"A.m_Age"<<A.m_Age<<endl;//20
	return 0;
}

总结:

(1)菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义;

(2)利用虚继承可以解决菱形继承问题

 2 多态---面向对象三大特性之一

 2.1 多态基本概念

1、多态分两类

(1)静态多态:函数重载和运算符重载属于静态多态,复用函数名;

(2)动态多态:派生类和虚函数实现运行多态

2、静态多态和动态多态区别:

(1)静态多态的函数地址早绑定-编译阶段确定函数地址;

(2)动态多态的函数地址晚绑定-运行阶段确定函数地址;

//--------------练习26:多态的基本语法------------------------
//多态的条件:(1)有继承关系;(2)子类重写父类的虚函数
//动态多态的使用:父类的指针或者引用,执行子类对象
class Animal
{
public:
	virtual void speak()//虚函数
	{
		cout<<"动物说话"<<endl;
	}
};
class Cat:public Animal//继承父类
{
public:
	//子类函数重写:函数返回值类型,函数名,参数列表,完全相同
	void speak()
	{
		cout<<"小猫说话"<<endl;
	}
};
class Dog:public Animal//继承父类
{
public:
	void speak()
	{
		cout<<"小狗说话"<<endl;
	}
};
//地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void DoSpeak(Animal& animal)//Animal& animal cat;
{
	animal.speak();
}
int main()
{
	Dog dog;
	DoSpeak(dog);
	Cat cat;
	DoSpeak(cat);
	return 0;
}

总结:1、多态满足条件:(1)有继承关系;(2)子类重写父类中的虚函数

2、多态使用条件---父类指针或引用指向子类对象

3、重写:函数返回值类型 函数名 参数列表 完全一致称为重写

2.2 多态的原理剖析

多态的优点:

(1)代码组织结构清晰;(2)可读性强;(3)利于前期和后期的扩展以及维护

总结:C++开发提倡利用多态设计程序架构,因为多态优点很多

2.3 纯虚函数和抽象类

1、纯虚函数

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

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:(1)无法实例化对象;(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类

2.4 虚析构和纯虚析构

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

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:(1)可以解决父类指针释放子类对选哪个;(2)都需要有具体的函数实现

虚析构和纯虚析构区别:(1)如果是纯虚析构,该类属于抽象类,无法实例化对象;(2)纯虚析构,需要声明也需要实现,实现式函数加作用域

虚析构语法:virtual ~类名(){}

纯虚构语法:virtual ~类名()=0;类名::~类名(){}

//-------------------练习29:虚析构和纯虚析构函数-------------------
//在利用多态时,父类指针或引用指向子类对象,如果子类对象中有堆区数据
//多态时执行不到子类的析构函数的,此时需要父类析构变成虚析构或者纯虚析构
class Animal//父类,纯虚,抽象类,不可以实例化对象
{
public:
	Animal()//父类构造函数
	{
		cout<<"Animal构造函数调用"<<endl;
	}
	virtual void speak()=0;//纯虚函数,多态入口
	//virtual ~Animal()//虚析构函数
	//{
	//	cout<<"Animal析构函数调用"<<endl;
	//}
	virtual ~Animal()=0;//纯虚构函数,类内声明,类外实现
};
 Animal::~Animal()//纯虚析构函数,类外声明
{
	cout<<"Animal析构函数调用"<<endl;
 }
class Cat:public Animal//继承父类
{
public:
	Cat(string name)//子类构造函数
	{
		m_Name=new string(name);//在堆区创建数据并返回指针
		cout<<"Cat构造函数调用"<<endl;
	}
	void speak()//子类必须重写父类虚函数
	{
		cout<<"小猫说话"<<endl;
	}
	string* m_Name;//为了有堆区数据,因此使用指针
	~Cat()//子类析构函数,必须释放堆区内存
	{
		if(m_Name!=NULL)
		{
			delete m_Name;
			m_Name=NULL;
			cout<<"Cat析构函数调用"<<endl;
		}
	}
};
void test()
{
	Animal* animal=new Cat("Tom");//堆区开辟数据,父类指针指向子类对象
	animal->speak();
	delete animal;
}
int main()
{
	test();
	return 0;
}

总结:(1)虚析构和纯虚析构就是用来解决通过父类指针释放子类对象;

(2)如果子类中没有堆区数据,可以不写为虚析构和纯虚析构;

(3)拥有纯虚析构函数的类也属于抽象类;

2.5 案例

//--------------------练习30:多态案例三:电脑组装-------------------
//电脑主要组合部件为CPU(计算),显卡(显示),内存条(存储)
//将每个零件封装出抽象基类,并且提供不同厂商生产不同的零件,例如Intel和Lenovo厂商
//创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,测试时组装三台不同的电脑进行工作

//1、每个零件创建一个父类
class CPU//基类,抽象类
{
public:
	virtual void Caculator()=0;//虚函数
};
class VidoCard
{
public:
	virtual void Display()=0;//虚函数
};
class Memory
{
public:
	virtual void Storage()=0;//虚函数
};

//2、创建一个电脑类
class Computer
{
public:
	Computer(CPU* cpu,VidoCard* vc,Memory* mem)//构造函数初始化,电脑组装零件
	{
		m_cpu=cpu;
		m_vc=vc;
		m_mem=mem;
	}
	void DoWork()
	{
		m_cpu->Caculator();//CPU计算
		m_vc->Display();//显卡显示
		m_mem->Storage();//内存条存储
	}
	CPU* m_cpu;
	VidoCard* m_vc;
	Memory* m_mem;
};
//3、创建零件
//Intel厂商零件(子类)
class IntelCPU:public CPU
{
public:
	void Caculator()
	{
		cout<<"IntelCPU计算"<<endl;
	}
};
class IntelVidoCard:public VidoCard
{
public:
	void Display()
	{
		cout<<"IntelVidoCard显示"<<endl;
	}
};
class IntelMemory:public Memory
{
public:
	void Storage()
	{
		cout<<"IntelMemory存储"<<endl;
	}
};
//Lenovo厂商零件子类
class LenovoCPU:public CPU
{
public:
	void Caculator()
	{
		cout<<"LenovoCPU计算"<<endl;
	}
};
class LenovoVidoCard:public VidoCard
{
public:
	void Display()
	{
		cout<<"LenovoVidoCard显示"<<endl;
	}
};
class LenovoMemory:public Memory
{
public:
	void Storage()
	{
		cout<<"LenovoMemory存储"<<endl;
	}
};
void test()
{
	//父类指针或者引用指向子类对象
	CPU* cpu=new IntelCPU;
	VidoCard* vc=new IntelVidoCard;
	Memory* mem=new IntelMemory;
	//组装电脑
	Computer computer(cpu,vc,mem);
	//电脑运行
	computer.DoWork();
}
int main()
{
	test();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值