继承
继承中静态同名成员处理:
在继承过后的子类和父类中会可能存在同名的静态成员,当我们想分别调用时,有不同的操作
子类的同名成员,直接调用就行,
父类的同名成员,需要加作用域.
总体上和上周学的非静态同名成员调用的方法一致。
但是根据所学内容可知,我们要调用这个成员时可以通过对象来访问,也可以通过类来访问。不同的点就出现在这里
cout<<"son下的m_A="<<son::m_A<<endl;
cout<<"base下的m_A="<<son::base::m_A<<endl;
在第二行代码中:第一个::表示通过类名访问,第二个::表示访问父类作用域下。个人理解为第二行的代码与第一行是有关系的,是复合关系,因为第一行的意思是通过类名来访问。所以第二行代码可以解读为通过类名访问的方式来访问base类作用域下的m_A。
静态同名成员函数处理:
与上面的成员处理一致,不过有一个要注意的点就是当子类中也出现了一个与父类同名的静态成员函数时,会将父类的所有同名的成员函数都隐藏掉,即使是父类中的同名函数发生了重载,也会覆盖掉。要想访问,也需要加作用域。
多继承语法
语法 class 子类 : 继承方式 :父类1, 继承方式 : 父类2
当两个父类中出现了同名的成员时,访问需要需要加作用域,才能分别访问到不同父类中的童名成员,由于很容易造成出错,所以实际开发中不常用多继承。
菱形继承:
当一个父类有两个不同的子类,而这两个子类又被一个类所继承,整体关系网络入像一个菱形,所以称为菱形继承。示例为,一个动物类作为第一个父类,然后有两个子类,分别为羊和驼;然后又有一个羊驼类继承了羊类和驼类。
在这个过程中会产生一些问题,例如羊和驼都有一个同名的age成员,同时继承到羊驼类时,羊驼自己的age究竟是多少?这个问题就成为二义性。
在如有羊驼类从羊类和驼类继承了两份相同的来自动物类的成员,造成了资源浪费
为解决这些问题,我们要学一个新的概念虚继承(virtual)
使用方式为在继承方式前加一个virtual,则成为了虚继承,那么羊驼类中的age就变成了只有一份了,我们如果想要不加作用域的的去访问羊驼类中的age也成立了。原理是;在改成虚继承之前,羊驼类中包含了两个age,要想不加作用域去访问age是不行的,因为编译器无法识别你要访问哪一个作用域下的age。是访问继承羊类后得到的age?还是访问继承驼类后得到的age?所以编译器报错。
而变成虚继承后羊驼类中只剩下一份age,那么就能避免了一开始提到的两个问题了。
多态
多态分为两类
1.静态多态,其实就是前面所学的运算符重载和函数重载。
2.动态多态,派生类和虚函数实现运行时多态
静态多态和动态多态的区别是:动态多态(动态绑定):即运行时的多态,在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。静态多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
继承中多态的使用要满足几个点
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
- 有继承关系
class Animal {
public:
void speak() {
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal &animal) {
animal.speak();
}
int main() {
Cat cat;
doSpeak(cat);
}
上面这段源码的结果是
动物在说话
这里就没有发生多态,没有发生我们想要的结果,我们想要的是小猫在说话,但是却打印的是动物在说话。我们再修改一下这段代码,加上virtual形成多态。
class Animal {
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal &animal) {
animal.speak();
}
int main() {
Cat cat;
doSpeak(cat);
}
这段代码与上面的代码不同点在于父类Animal中的void speak()前加了virtual;满足了多态的形成条件。其实质就是地址的晚绑定,也是动态多态。
多态的原理分析:
当父类的同名函数没有加上virtual时,该Animal类大小为一个字节,加上virtual后变成了四个字节,这是因为类中加入了一个指针(vfptr),虚函数表指针,虚函数表中记录了虚函数的地址,这个vfptr指针指向的便是这个虚函数表。当发生继承时,子类将这个虚函数进行了重写,那么子类中的的虚函数表内部的虚函数会被替换成子类重写的虚函数的地址。那么当我们调用这个cat的speak函数时,便是调用的cat类中的speak函数了。
上图是没有加virtual时Cat类中的继承情况。
我们来一个案例找找感觉!!!
#include <iostream>
#include <string>
using namespace std;
//普通实现
class Calculator {
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;
}
//如果要提供新的运算,需要修改源码
}
public:
int m_Num1;
int m_Num2;
};
void test01()
{
//普通实现测试
Calculator 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;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
-----------------------------------------------------------------------------------
下面的计算器是多态实现的
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;
}
};
//乘法计算器
class MulCalculator :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; //手动开辟的new要自己手动释放
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
这算是一个普通的计算器实现和多态的计算器的实现,我们可以看出来,普通的源码计算器代码量比较小,但是如果想要对源码进行修改或者增添其他功能,就需要找到最开始的代码然后进行修改。在实际的工作中要遵循开闭原则:对扩展进行开放,对修改进行关闭。所以我们在设计这个计算器的代码的时候,就应该为以后的修改做好准备。那么就需要多态来实现了。在这个案例中,我们如果想后续添加其他的运算,我们仅需要新加一个类来运算,实现多态。
纯虚函数:
我们可以看到,在上面的两个案例中,我们的父类的原函数实际上没有实现,都是使用子类的函数,那么我们就可以将这个函数设计成一个纯虚函数
语法:virtual 返回值类型 函数名 (参数列表) = 0
注意:只要类中的只要出现了这个纯虚函数,无论是成员函数也好,析构函数也好,那么这个类就被称为抽象类
抽象类的特点:
1.无法实例化对象
2.子类必须重写父类中的纯虚函数,否则也属于抽象类
这个概念有点抽象,我们可以在例子中理解
//抽象制作饮品
class AbstractDrinking {
public:
//烧水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//规定流程
void MakeDrink() {
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入牛奶!" << endl;
}
};
//制作茶水
class Tea : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮自来水!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将茶水倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入枸杞!" << endl;
}
};
void DoWork(AbstractDrinking* drink) {
drink->MakeDrink();
delete drink;
}
void test01() {
DoWork(new Coffee);
cout << "--------------" << endl;
DoWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
虚析构和纯虚析构
当我们在子类中创建了一个父类指针指向一个new的对象时,我们想要delete掉这个new出来的对象时,父类的指针析构时,不会调用子类中的析构函数,造成了内存泄漏。这个情况有点像之前的想要执行子类的同名函数,却执行了父类的函数,导致子类中的含函数没有被调用。所以我们的解决方案也和之前的有点类似,也是在父类中的析构函数前加上virtual。这个就成为虚析构,那么纯虚析构也有迹可循,也就是这个虚析构函数定义中,没有行为。那么称他为纯虚析构函数。
注意:当我们在类中按照纯虚函数的形式写好这个纯虚析构函数时,编译器会报错。这是因为这个析构函数需要有代码实现!
上示例!!
#include<iostream>
using namespace std;
class base {
public:
~base() {
cout << "父类析构函数被调用" << endl;
}
};
class derived :public base{
public:
int* p;
derived() {
p = new int(2);
}
~derived() {
cout << "子类的析构函数被调用" << endl;
delete p;
}
};
void fun(base* ptr) { //fun函数把传入的指针p指向的堆区数据给释放掉
delete ptr;
}
int main() {
base* b = new derived();
fun(b);
return 0;
}
该代码最终的结果是;只调用了父类的析构函数,子类的析构函数没有调用。我们可以加以修改。
virtual ~base() {
cout << "父类析构函数被调用" << endl;
}
那么我们就可以调用到这个子类的析构函数了。另外要想实现父类的纯虚析构函数也是可以的。但是要记得他和纯虚函数的写法有不一样的地方!
#include<iostream>
using namespace std;
class base {
public:
virtual~base() = 0;
};
base::~base() {}
class derived :public base {
public:
int* p;
derived() {
p = new int(2);
}
~derived() {
cout << "子类的析构函数被调用" << endl;
delete p;
}
};
void fun(base* ptr) { //fun函数把传入的指针p指向的堆区数据给释放掉
delete ptr;
}
int main() {
base* b = new derived();
fun(b);
return 0;
}
那么我们学了这么多的多态知识,我们可以来一个综合的案例来体会一下所学知识
#include<iostream>
using namespace std;
//抽象CPU类
class CPU
{
public:
//抽象的计算函数
virtual void calculate() = 0;
};
//抽象显卡类
class VideoCard
{
public:
//抽象的显示函数
virtual void display() = 0;
};
//抽象内存条类
class Memory
{
public:
//抽象的存储函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU * cpu, VideoCard * vc, Memory * mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
void work()
{
//让零件工作起来,调用接口
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供析构函数 释放3个电脑零件
~Computer()
{
//释放CPU零件
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
//释放显卡零件
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
//释放内存条零件
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU * m_cpu; //CPU的零件指针
VideoCard * m_vc; //显卡零件指针
Memory * m_mem; //内存条零件指针
};
//具体厂商
//Intel厂商
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了!" << endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Intel的显卡开始显示了!" << endl;
}
};
class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的内存条开始存储了!" << endl;
}
};
//Lenovo厂商
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的显卡开始显示了!" << endl;
}
};
class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了!" << endl;
}
};
void test01()
{
//第一台电脑零件
CPU * intelCpu = new IntelCPU;
VideoCard * intelCard = new IntelVideoCard;
Memory * intelMem = new IntelMemory;
cout << "第一台电脑开始工作:" << endl;
//创建第一台电脑
Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;
cout << "-----------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
//第二台电脑组装
Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
computer2->work();
delete computer2;
cout << "-----------------------" << endl;
cout << "第三台电脑开始工作:" << endl;
//第三台电脑组装
Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
computer3->work();
delete computer3;
}