B站视频学习C++ 多态 135-

(135) 多态

多态分为两类

  • 静态多态:函数重载 和 运算符重载 ,复用函数名
  • 动态多态:子类和 虚函数 实现运行时多态

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

  • 静态多态的地址早绑定-- 编译阶段确定函数地址
  • 动态多态的地址晚绑定-- 运行阶段确定函数地址
#include <iostream>
using namespace std;
class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

// 执行说话的函数
// 
void doSpeak(Animal &animal) // Animal &animal = cat; 
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat); // 走动物在说话还是猫在说话? 
}

int main()
{
	test01();
	return 0;
}

问题:
结果是动物在说话还是猫在说话?

结果是动物在说话。????

2、如果想执行猫在说话,怎么做?
答:animal 类中的 speak函数 前加 virtual

#include <iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
    void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

// 执行说话的函数
// 
void doSpeak(Animal &animal) // Animal &animal = cat; 
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat); // 走动物在说话还是猫在说话? 
}

int main()
{
	test01();
	return 0;
}

运行结果:
小猫在说话

3、加了狗类

class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

// 执行说话的函数
// 地址早绑定,在编译阶段确定函数地址
// 如果想执行猫在说话,那么这个函数地址不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal) // Animal &animal = cat; 
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat); // 走动物在说话还是猫在说话? 

	Dog dog;
	doSpeak(dog);
}

运行结果:
小猫在说话
小狗在说话

动态多态满足条件:

  1. 有继承关系
  2. 子类重写父类虚函数 (本题speak函数)
    重写:函数返回值类型 函数名 参数列表完全相同

动态多态使用

  1. 父类的 指针或引用 指向 子类对象,本题就是

多态的目的:
封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了“接口重用”。也即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

多态:不同对象接收相同消息时产生不同的动作
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

(136) 多态原理分析

#include <iostream>
using namespace std;

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

void doSpeak(Animal &animal) // Animal &animal = cat; 
{
	animal.speak();
}

void test02()
{
	cout << "sizeof Animal = " << sizeof(Animal) << endl;
}

int main()
{
	test02();
	return 0;
}

运行结果:
sizeof Animal = 1
为什么?

加了关键字 virtual后,即

class Animal
{
public:
	 virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

运行结果:
sizeof Animal = 4
为什么?

这4个字节是指针
当子类重写父类的虚函数后,子类中的虚函数表 内部 会替换成 子类的虚函数地址
替换前
在父类 Animal 的内部结构中,虚函数指针(vfptr)指向 虚函数表(vftable),在虚函数表中存储 父类虚函数的地址(&Animal::speak())

当发生继承时,子类(Cat)虚函数表中存储 父类虚函数的地址(&Animal::speak())

当发生多态时,子类的虚函数表中的 父类虚函数的地址 将被替换成 子类自己的虚函数地址(&Cat::speak())

注: 当父类的指针 或 引用 指向 子类的对象时,就发生了多态

替换后
在这里插入图片描述
子类替换自己的 虚函数表,即 &Animal::speak 替换成 &Cat:: speak ,不会替换父类的 虚函数表

多态运行时原理
`

当父类指针或引用 指向 子类对象时,发生多态`。即
Animal & animal = cat;
animal.speak();

当子类重写虚函数时,子类会将 自身虚函数表中 父类的虚函数地址 替换成 子类的虚函数地址

调用cat后,到子类的虚函数表,确定函数入口,

(137) 多态案例-- 计算器类

分别利用 普通写法和多态 技术,实现两个操作数进行运算的 计算器类
多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期扩展以及维护

普通写法:

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

// 普通写法
class Calculaator
{
public:
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return m_num1 + m_num2;
		}
		else if (oper=="-")
		{
			return m_num1 - m_num2;
		}
		else if (oper == "*")
		{
			return m_num1 * m_num2;
		}

	}
	int m_num1;
	int m_num2;

};

void test01()
{
	Calculaator c;
	c.m_num1 = 10;
	c.m_num2 = 10;

	cout << c.m_num1 << " + " << c.m_num2 << " = " << c.getResult("+") << endl;
	cout << c.m_num1 << " - " << c.m_num2 << " = " << c.getResult("-") << endl;

}

int main()
{
	test01();
	return 0;
}

运行结果:
10 + 10 = 20
10 - 10 = 0
10 * 10 = 100

存在的问题:
如果想扩展新的功能(比如增加除法),需要修改源码(本题改getResult函数)
在真实开发中,提倡 开闭原则,即 对扩展进行开放,对修改进行关闭

用多态改写:

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

// 多态实现
class AbstractCalculator  // 类名后没有括号
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int m_Num1;
	int m_Num2;
};

// 加法计算机类
class AddCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

// 减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

void test02()
{
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	// 用完后记得销毁
	delete abc;

	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

int main()
{
	test02();
	return 0;
}

运行结果:
10 + 10 = 20
10 - 10 = 0

多态实现的好处:
1、组织结构清晰;都是一个个 独立出的小块,便于改写;
2、对于前期和后期 扩展和维护性高

138 纯虚函数 和 抽象类

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

纯虚函数语法: virtual 返回值类型 函数名 (参数列表)=0;
当类中有纯虚函数,这个类称为 抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的虚函数,否则属于抽象类
// 138  纯虚函数 和 抽象类
#include <iostream>
using namespace std;

class Base
{
public:
	// 纯虚函数
	// 抽象类特点
	// 1、无法实例化对象
	virtual void func() = 0;
};
int main()
{
	Base b;       // 在栈上 报错
	new Base;  // 在堆上 报错 

	return 0;
}

2、子类不重写父类的纯虚函数,也是抽象类,无法实例化

class Base
{
public:
	// 纯虚函数
	// 抽象类特点
	// 1、无法实例化对象
	// 2、抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类
	virtual void func() = 0;
};

class Son :public Base
{
public:

};
int main()
{
	Son a;
	return 0;
}

3、 抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类

#include <iostream>
using namespace std;

class Base
{
public:
	// 纯虚函数
	// 抽象类特点
	// 1、无法实例化对象
	// 2、抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类
	virtual void func() = 0;
};

class Son :public Base
{
public:
	virtual void func() {
		cout << "func函数调用" << endl;
	}

};

void test01()
{
	Base* base = new Son;
	base->func();
}

int main()
{
	test01();
	return 0;
}

运行结果:
func函数调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值