C++ 多态深入学习总结笔记

多态和虚函数
1. 通过案例理解多态
  • 案例:父类Animal,2个子类Dog和Cat,实现speak方法
  1. 未使用虚函数 virtual 声明

main.h 文件

class Animal {
public:
	void speak(void);
};
class Dog :public Animal {
public:
	void speak(void);
};
class Cat :public Animal {
public:
	void speak(void);
};

main.c 文件

void Animal::speak(void) {
	cout << "Animal speak" << endl;
}
void Dog::speak(void) {
	cout << "Wang~ Wang~ Wang~" << endl;
}
void Cat::speak(void) {
	cout << "Miao~ Miao~" << endl;
}
int main(void)
{
	Animal a;	
	Dog b;	
	Cat c;
	Animal *p;
	p = &b;	//重点:父类指针可以指向子类对象,反之不可
	p->speak();
	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

  1. 使用虚函数 virtual 声明

main.h 文件

class Animal {
public:
	virtual void speak(void);
};
class Dog :public Animal {
public:
	void speak(void);
};
class Cat :public Animal {
public:
	void speak(void);
};

main.c 文件

void Animal::speak(void) {
	cout << "Animal speak" << endl;
}
void Dog::speak(void) {
	cout << "Wang~ Wang~ Wang~" << endl;
}
void Cat::speak(void) {
	cout << "Miao~ Miao~" << endl;
}
int main(void)
{
	Animal a;	
	Dog b;	
	Cat c;
	Animal *p;
	p = &b;	//重点:父类指针可以指向子类对象,反之不可
	p->speak();
	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

2. 多态和虚函数
  • 多态

    1. polymorphism,面向对象的三大特征之一
    2. 从宏观讲,多态就是要实现一套逻辑多种具体适配的执行结果,猫就应该是猫的叫声,狗就应该是狗的叫声
    3. 从微观讲,多态就是要一套代码在运行时根据实际对象的不同来动态绑定 / 跳转执行相匹配的具体函数
  • 虚函数

    1. 函数声明前加virtual的即是虚函数
    2. 虚函数是C++实现多态特性的基础,从语法上讲多态特性的基类方法必须是虚函数
3. 多态中的 override
  • 基类中方法声明为 virtual,派生类中重新实现同名方法以实现多态,这就叫 override(中文为覆盖,或重写)
  • 注意区分 override 和 redefining,微观上最大区别就是是否有 virtual,宏观上最大区别就是是否表现为多态
3. 多态一定要通过面向对象和override来实现吗
  • 宏观上的多态是一种编程效果,微观上的多态是一种 C++ 支持的编程技术,微观是为了去实现宏观
  • 不用 C++ 的 virtual 和 override,也可以实现宏观上的多态,比如 C 语言实现方法
  • C++ 源生支持多态,实现起来更容易,后续修改和维护更容易,架构复杂后优势更大
纯虚函数与抽象类
1. 纯虚函数
  • 纯虚函数就是基类中只有原型没有实体的一种虚函数
  • 纯虚函数形式:virtual 函数原型=0;
  • 代码实践:在基类Animal中使用纯虚函数

main.h 文件

class Animal
{
public:
	virtual void speak(void) = 0;
};
class Dog :public Animal
{
public:
	void speak(void);
};
class Cat :public Animal
{
public:
	void speak(void);
};

main.c 文件

void Dog::speak(void)
{
	cout << "Wang~ Wang~ Wang~" << endl;
}
void Cat::speak(void)
{
	cout << "Miao~ Miao~" << endl;
}
int main(void)
{
	Dog b;	
	Cat c;
	Animal *p;
	p = &b;
	p->speak();
	system("pause");
	return 0;
}
  • 纯虚函数为什么没有实体?因为语义上不需要
  • 纯虚函数是否占用内存?不会,因为纯虚函数所在的类根本无法实例化对象
2. 抽象类(abstract type)
  • 带有纯虚函数的类成为抽象类,也可包含其他函数,抽象类只能作为基类来派生新类,不可实例化对象
  • 派生类必须实现基类的纯虚函数后才能用于实例化对象
  • 抽象类的作用:将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对应暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。这种机制可以让语法和语义保持一致
  • 抽象类的子类必须实现基类中的纯虚函数,这样子类才能创建对象,否则子类就还是个抽象类
3. 接口(interface)
  • 接口是一种特殊的类,用来定义一套访问接口,也就是定义一套规约
  • 接口类中不应该定义任何成员变量
  • 接口类中所有成员函数都是公有且都是纯虚函数
  • 有些高级语言中直接提供关键字 interface 定义接口,接口其实就是个纯粹的抽象基类
虚析构函数
1. 什么是虚析构函数
  • 析构函数前加virtual,则析构函数变为虚析构函数
  • 规则:基类有1个或多个虚函数时(注意不要求是纯虚函数),则其析构函数应该声明为 virtual
2. 为什么需要虚析构函数
  • 代码演示:父子类各自添加析构函数,用两种分配和回收对象的方式分别实验,观察析构函数被调用的规律
int main(void)
{
	Dog b;
	Animal *p = &b;
	p->speak();	//实际执行时只执行了 Dog的析构函数,并未执行父类 Animal的析构函数

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

int main(void)
{
	Animal *p = new Dog();
	p->speak();
	delete p;	//实际执行时只执行了父类 Animal的析构函数,并未执行的 Dog析构函数

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

问题解决:引入虚析构函数

class Animal
{
public:
	virtual void speak(void) = 0;
	virtual ~Animal();
};
int main(void)
{
	Dog b;
	Animal *p = &b;
	p->speak();

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

  • 结论:虚析构函数在各种情况下总能调用正确(和对象真正匹配的)析构函数
3. 分析和总结
  • 虚函数 virtual 价值,就是让成员函数在运行时动态解析和绑定具体执行的函数,这是 RTTI 机制的一部分
  • 析构函数也是成员函数,加 virtual 的效果和普通成员函数加 virtual 无本质差异
  • 加 virtual 是有开销的,运行时动态绑定不如编译时静态绑定效率高资源消耗优,但是可以多态
using重新定义继承时访问权限
1. using关键字在非public继承时的权限重开作用
  • 父类 public 方法在 private / protected 继承时,到了子类就成了 private / protected 而不是 public,无法用子类对象来调用
  • 解决方法
    1. 改为public继承,有用但是有时候不得不 protected 或者 private 继承时就没办法
    2. 在子类中再实现一个 public 的方法,内部调用父类继承而来的那个方法,能用但是有点麻烦而且有额外开销

main.h 文件

class Animal
{
public:
	void speak(void);
};
class Dog :private Animal
{
public:
	void DogSpeak(void);
};

main.c 文件

void Animal::speak(void)
{
	cout << "Animal speak" << endl;
}
void Dog::DogSpeak(void)
{
	this->speak();
}
int main(void)
{
	Dog d;
	d.DogSpeak();
	system("pause");
	return 0;
}
  1. 在子类中使用 using 关键字将该方法声明为 public 访问权限,本质上类似于权限打洞
    用法:在子类 public 声明中使用 using Base::func; 即可,不带返回值类型不带参数列表
class Dog :private Animal
{
public:
	using Animal::speak;
};

注意: using 只用于 private / protected 继承中的权限损失找回,如果方法在父类中本来就是private的子类中无法 using 访问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值