目录
一、菱形(钻石)继承
1、如果有两个派生类继承了同一个基类,然后又有一个类同时继承这两个派生类,这种继承我们称之为菱形继承,或者钻石型继承。
2、菱形继承带来的问题:
1)歌手继承了人的成员,服务员同样继承了人类的成员
当唱歌的服务员调用访问成员的时候,会产生二义性。(加上作用域)2)唱歌的服务员继承自人的数据有两份,浪费了内存。
3、菱形继承带来的问题的解决方案:虚继承
#include <iostream>
using namespace std;
class Person{
public:
int m_A;
};
class Singer : public Person{
public:
};
class Waiter : public Person{
public:
};
class SingerWaiter : public Singer,public Waiter{
public:
};
int main()
{
SingerWaiter S;
S.m_A;//ambiguous,模糊的,因为不清楚到底是继承谁的
return 0;
}
通过观察我们发现,没有加virtual,会出现模棱两可的现象,所以我们必须加上virtual这个关键词
#include <iostream>
using namespace std;
class Person{
public:
int m_A;
};
class Singer : virtual public Person{
public:
};
class Waiter : virtual public Person{
public:
};
class SingerWaiter : public Singer,public Waiter{
public:
};
int main()
{
SingerWaiter S;
S.m_A;//正常运行,不会报错
return 0;
}
这次加上virtual后就正常运行,不会报错。
可是,这是为什么呢?为啥加上virtual就不会报错了呢?接下来我们一起来看第二部分,就会解决这个问题。
二、虚继承实现原理
vbptr:virtual base pointer(虚基类指针)
原理:只有一个唯一的成员,通过保存虚基类指针,这个指针指向的是一张表(虚基类表)
这个表中保存了当前获得到唯一的数据的偏移量。
#include <iostream>
using namespace std;
class Person{
public:
int m_A;
};
class Singer : virtual public Person{
public:
};
class Waiter : virtual public Person{
public:
};
class SingerWaiter : public Singer,public Waiter{
public:
};
int main()
{
SingerWaiter S;
S.m_A=20;
}
用linux打开后
Singer偏移量是0 ,Waiter偏移量是4
三、静态联编
比如我接下来要写的案例中,调用了Animal中的speak()函数,属于地址早绑定,静态联编,因为在编译期间就知道了speak函数的地址,是动物的speak();
#include <iostream>
using namespace std;
class Animal{
public:
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.speak();
}
int main()
{
Cat c;
dospeak(c);
Dog d;
dospeak(d);
cout<<sizeof(Animal)<<endl;//占1个字节,就是表示这个对象存在
return 0;
}
结果为:
四、动态联编
通过上述结果我们会发现,那个并不是我们想要的那个结果,如果传进来的是猫对象,就执行猫的speak();如果传进来的是狗对象,就执行狗的speak(); 那么这个地址就是晚绑定,动态联编,这样才是我们想要的结果。
#include <iostream>
using namespace std;
class Animal{
public:
virtual void speak()//虚函数,虚函数表指针,32位系统占4个字节,64位系统占8个字节
{
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.speak();
}
int main()
{
Cat c;
dospeak(c);
Dog d;
dospeak(d);
cout<<sizeof(Animal)<<endl;//占4个字节
return 0;
}
通过观察发现,我只是在基类中的void speak()的前面加上了virtual,就实现了不同的动物实现不同的说话方式。这就是动态多态
拓展
1、虚函数
被virtual修饰过的函数就是虚函数
2、虚函数的重写
派生类中有一个跟基类中完全相同的虚函数(派生类的虚函数与基类中的虚函数的返回值,函数名,参数列表完全相同),称子类的虚函数重写了基类的虚函数
(图片来源于牛客网)
五、多态、纯虚函数
1、多态:通俗的讲就是多种形态,就是去完成某个行为,不同的对象去完成某个行为会呈现出不同的状态。
多态构成的条件:多态是在不同的继承关系的类对象,去调用同一函数,产生了不同的行为,那么在继承中够成多态要具备两个条件:
1)必须通过基类的指针或者引用调用虚函数;
2)被调用的函数必须是虚函数,且派生类必须是对基类虚函数的重写。
虚函数覆盖
企业开发中,提倡开闭原则,对扩展进行开放,对修改进行关闭;
多态的好处:代码组织结构清晰,提高代码的可读性,提高可扩展性.
2、纯虚函数
语法:virtual 函数返回值 函数名(参数列表)=0; 当类中有了纯虚函数,这个类也被称为抽象类,抽象类的特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
#include <iostream>
using namespace std;
class Calculator{
public:
int m_A;
int m_B;
virtual int getResult()=0;//纯虚函数,本身就是抽象函数
};
//加法计算器
class Add : public Calculator{
public:
virtual int getResult()//必须重写父类,如果不重写的化,继承过来还是抽象函数
{
return m_A + m_B;
}
};
//减法计算器
class Sub : public Calculator{
public:
virtual int getResult()
{
return m_A - m_B;
}
};
//乘法计算器
class Mul : public Calculator{
public:
virtual int getResult()
{
return m_A * m_B;
}
};
int main()
{
Calculator *cal = new Add;
cal->m_A = 20;
cal->m_B = 10;
cout<<cal->getResult()<<endl;//30
return 0;
}
六、虚析构与纯虚析构
1、虚析构函数:子类继承了父类,但是普通析构函数是不会调用子类的析构函数的,所以会导致内存释放的不干净,所以我们需要使用虚析构函数来解决。
写法:
Virtual ~Animal(){}
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
2、纯析构函数
写法:
声明:Virtual ~Animal()=0;
实现:Animal::~Animal(){...}
纯虚析构,需要声明,也需要实现。类内声明,类外实现。
若类中有纯虚函数,则类也是抽象类,不可实例化。
#include <bits/stdc++.h>
using namespace std;
class People{
public:
int m_A;
People(){
this->m_A = 20;
cout<<"People构造函数"<<endl;
}
virtual void func() = 0;
virtual ~People()=0;//纯虚析构,类内声明
};
People :: ~People(){//类外实现
cout<<"People 的析构函数"<<endl;
}
class Worker : public People{
public:
char *pName;
Worker(const char* name){
cout<<"Worker构造函数"<<endl;
this->pName = new char[strlen(name)+1];
strcpy(this->pName,name);
}
void doWork(){
cout<<this->pName<<"在工作"<<endl;
}
~Worker()
{
cout<<"Worker析构"<<endl;
if(this->pName != NULL)
{
delet[] pName;
this->pName=NULL;
}
}
};
int main()
{
People *p=new Worker("Tom");
p->doWork();
delete p;
return 0;
}