CPP_继承_多态
C++面向对象的三大特性:封装,继承,多态。
封装:使用一整套方法去创建一个新的类型,这叫类的封装。
继承:从一个现有的类型基础上,稍作改动,得到一个新的类型的方法,叫类的继承。
多态:当有几个不同的子类对象时,对象调用的函数会依据对象类型来调用相应类型的成员函数。
1. 继承
继承的主要目的是为了代码复用。
class 子类: 继承方式 父类
子类也称派生类,父类也称基类。
1.1 访问权限:public, protected, private
protected表示这种成员可以被子类继承和访问,但是外界却像private一样不可访问。
父类的private成员并不是不继承,而是对子类扩展的成员函数不可见。
public派生规则:
父类成员访问权限 | 子类继承自父类成员的访问限定 |
---|---|
public | public |
protected | protected |
private | 不可见 |
protected派生规则:
父类成员访问权限 | 子类继承自父类成员的访问限定 |
---|---|
public | protected |
protected | protected |
private | 不可见 |
private派生规则:
父类成员访问权限 | 子类继承自父类成员的访问限定 |
---|---|
public | private |
protected | private |
private | 不可见 |
注:protected成员子类可访问,对象不可访问。
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal(){m_id = 0;}
Animal(int id){ m_id = id;}
virtual void speak(){cout << "animal speak" <<endl;}
protected:
int m_id;
};
//class Rabbit: protected Animal
class Rabbit: public Animal
{
public:
Rabbit(int id){ m_id = id;}
void speak(){cout << "dog speak " << m_id <<endl;}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
int main()
{
Rabbit rab(1);
doSpeak(rab); // Animal& animal = cat
//doSpeak(rab); //protected报错:'Animal' is an inaccessible base of 'rabbit'
return 0;
}
1.2 继承中构造和析构顺序
创建子类对象时,先创建父类,再创建子类。
析构时,先析构子类,再析构父类。
1.3 继承中同名成员处理方式
当子类与父类出现同成员时,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员,直接访问即可。
- 访问父类同名成员,需要加作用域。
s.m_A
s.Base::m_A
s.func();
s.Base::func()
注:如果子类出现和父类同名的成员函数,子类的同名成员函数会隐藏父类中所有的同名成员函数(包括父类中各种重载函数)。
1.4 继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)
1.5 菱形继承
两个派生类继承同一个基类,又有某个派生类同时继承这两个派生类,这种继承被称为菱形继承,或者钻石继承。
菱形继承问题
- 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
- 羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
菱形继承问题
虚继承:继承之前加上关键字virtual,变成虚继承。
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_Age;
};
//继承之前加上关键字virtual,变为虚继承
//Animal类称为 虚基类
//羊类
class Sheep :virtual public Animal {};
//驼类
class Camel :virtual public Animal {};
//羊驼类
class Alpaca :public Sheep, public Camel {};
int main()
{
Alpaca alpaca;
alpaca.Sheep::m_Age = 18;
alpaca.Camel::m_Age = 28;
//菱形继承时,两个父类拥有相同的数据,需要加以作用域区分
//这份数据我们只需要一份,菱形继承导致数据有两份,造成资源浪费
//利用虚继承解决菱形继承的问题
cout << "alpaca.Sheep::m_Age = " << alpaca.Sheep::m_Age << endl;
cout << "alpaca.Camel::m_Age = " << alpaca.Camel::m_Age << endl;
cout << "alpace.m_Age = " << alpaca.m_Age << endl;
return 0;
}
------
alpaca.Sheep::m_Age = 28
alpaca.Camel::m_Age = 28
alpace.m_Age = 28
1.6 类型转换
父类A 子类B
A *p = new B; 可以
B *p = new A; 不可以
p调用的成员方法为父类(A)的方法,非子类B的成员方法。属编译时绑定(因p的指针类型为A *),如要访问子类,需要使用多态(父类相应方法定义为virtual)。
A a;
B *p;
p = &a; 可以
p访问成员变量时可能越界(因p的成员变量多于a)。
2. 多态
2.1 动态多态
多态分为两类:静态多态和动态多态。
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名。
- 动态多态:派生类和虚函数实现运行时多态。
区别
- 静态多态的函数地址早绑定--编译阶段确定函数地址。
- 动态多态的函数地址晚绑定--运行阶段确定函数地址。
动态多态满足条件
- 有继承关系
- 子类重写父类的虚函数(重写:函数返回值 函数名 参数列表 完全相同)
动态多态使用
父类的指针或引用指向子类的对象
#include <iostream>
using namespace std;
class Animal
{
public:
void virtual speak(){ // 虚函数
cout << "Animal speak" << endl;
}
};
class Cat: public Animal
{
public:
// 重写:函数返回值、函数名、参数列表 完全相同
void speak(){
cout << "Cat speak" << endl;
}
};
class Dog: public Animal
{
public:
void speak(){
cout << "Dog speak" << endl;
}
};
// 地址早绑定,在编译阶段确定函数地址
// 如果想实现cat speak,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定--地址完绑定
// 父类中成员函数前加virtual
void doSpeak(Animal &animal)
{
animal.speak();
}
int main()
{
Cat cat;
Dog dog;
doSpeak(cat);
doSpeak(dog);
return 0;
}
-----------------
Cat speak
Dog speak
声明为vitual的成员函数称为虚函数,在运行时确定执行体,属动态绑定,dynamic binding,调用子类的具体实现。
一旦某个函数在父类中声明为virtual,在所有继承它的子类中这个函数也是virtual的,子类可以不必写virtual关键字。
多态原理
有虚函数的类都有一个隐含的指针成员vptr,指向一个虚函数表。(一个类一个)
每个父类或子类都各自有一张虚函数表。在构造对象时,对象的vptr成员指向该类的虚函数表vbtable(virtual base table)。
包含虚函数的类的sizeof应多加4个字节,用于存放vptr(虚函数表指针)。
2.2 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容。因此可以将虚函数改为纯虚函数。
如果一个成员函数声明为virtual并在末尾加上=0而没有提供函数的实现,称为纯虚函数。拥有纯虚函数的类称为抽象类,抽象类不能实例化(或者说不能创建该类的对象),而只能被其他类继承。
纯虚函数语法:
virtual 返回值类型 函数名(参数列表)= 0;
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
抽象类也称为接口,继承了某个抽象类也称实现了某个接口。
#include <iostream>
using namespace std;
class CPU
{
public:
virtual void calc() = 0;
};
class GPU
{
public:
virtual void display() = 0;
};
class RAM
{
public:
virtual void store() = 0;
};
class Computer
{
public:
Computer(CPU *cpu, GPU *gpu, RAM * ram);
void doWrok();
~Computer();
CPU* cpu_;
GPU* gpu_;
RAM* ram_;
};
Computer::Computer(CPU* cpu, GPU* gpu, RAM* ram)
{
cpu_ = cpu;
gpu_ = gpu;
ram_ = ram;
}
void Computer::doWrok()
{
cpu_->calc();
gpu_->display();
ram_->store();
}
Computer::~Computer()
{
if(cpu_ != NULL){
delete cpu_;
cpu_ = NULL;
}
if(gpu_ != NULL){
delete gpu_;
gpu_ = NULL;
}
if(ram_ != NULL){
delete ram_;
ram_ = NULL;
}
}
class IntelCpu : public CPU
{
virtual void calc(){
cout << "Intel CPU" << endl;
}
};
class AmdCpu : public CPU
{
virtual void calc(){
cout << "AMD CPU" << endl;
}
};
class NvidiaGpu: public GPU
{
void display(){
cout << "Nvidia GPU" << endl;
}
};
class AmdGpu: public GPU
{
void display(){
cout << "AMD GPU" << endl;
}
};
class CorsairRam: public RAM
{
void store(){
cout << "Corsair RAM" << endl;
}
};
class GskillRam: public RAM
{
void store(){
cout << "Gskill RAM" << endl;
}
};
int main(int argc, const char *argv[])
{
CPU *tIntelCpu = new IntelCpu;
GPU *tNvidiaGpu = new NvidiaGpu;
RAM *tCorsairRam = new CorsairRam;
cout << "first Computer" << endl;
Computer *tcomputer = new Computer(tIntelCpu, tNvidiaGpu, tCorsairRam);
tcomputer->doWrok();
cout << "------------------------------" << endl;
Computer *scomputer = new Computer(new AmdCpu, new AmdGpu, new GskillRam);
scomputer->doWrok();
return 0;
}
2.3 虚析构和纯虚析
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。解决方法:将父类中的析构函数改为虚析构或纯虚析构。
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名(){} = 0;
类名::~类名(){}
注:如果子类中没有堆区数据,可以不写虚析构或纯虚析构。
#include <iostream>
using namespace std;
class Animal
{
public:
Animal(){cout << "Animal constructor" << endl;}
//纯虚函数
void virtual speak() = 0;
//利用虚析构可以解决父类指针释放子类对象时不干净的问题
//virtual ~Animal(){cout << "Animal destructor"}
virtual ~Animal() = 0; //纯虚析构:需要声明,也需要具体实现
};
Animal::~Animal()
{
cout << "Animal pure virtual deconstructor" << endl;
}
class Cat: public Animal
{
public:
Cat(string name){
cout <<"Cat constructor" << endl;
m_Name = new string(name);
}
void speak(){
cout << *this->m_Name << " cat speak" << endl;
}
~Cat(){
if(m_Name != NULL){
cout << "free cat memory" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
int main()
{
Animal *animal = new Cat("Tom");
animal->speak();
//父类指针析构时不会调用子类析构函数,导致如果子类有堆区数据,造成内存泄漏
delete animal;
return 0;
}
------------------
Animal constructor
Cat constructor
Tom cat speak
free cat memory
Animal pure virtual deconstructor