继承
本质:
代码复用
派生类继承了基类的除构造和析构以外的所有成员、类型和作用域
派生类的构造和析构方式和顺序
先调用基类成员对象的构造函数,再调用基类自己的构造函数,再调用派生类自己的成员对象构造函数,最后调用派生类自己的构造函数
先析构派生类自己,再析构派生类的成员对象,再析构基类自己,最后析构基类的成员对象
继承方式
public:任意位置访问
protected:本类,子类类中
private:本类类中
基类中不同访问限定符下的成员以不同的继承方式继承后在派生类中访问限定
public | protected | private | |
---|---|---|---|
(公有继承)public | public | protected | 不可访问 |
(保护继承)protected | protected | protected | 不可访问 |
(私有基础)private | private | private | 不可访问 |
类和类的关系
组合 has_a a part of
继承 is_a a kind of // private has_a
代理 被代理类的接口的功能子集
基类和派生类中同名函数的关系
- 重载 overload //重定义
三要素:同名、不同参、同作用域 - 隐藏 overhide
派生类中的同名函数会隐藏基类中所有的同名函数,若要访问基类中的同名函数,要在前面加上作用域
三要素:继承关系、不同作用域、同名 - 覆盖 override //重写
派生类中的同名同参虚函数会覆盖基类中同名同参的虚函数
三要素:继承关系、不同作用域、同名同参
基类和派生类指针或引用的相互指向或引用
允许基类的指针或引用指向或者引用派生类对象
不允许派生类的指针或引用指向或引用基类对象
基类指针指向派生类对象时,指针永远放的是派生类中基类部分的起始地址
class Base
{
public:
Base(int a) :ma(a)
{
memset(this, 0, sizeof(*this));
}
virtual void Show()//虚函数
{
cout << "Base()" << endl;
}
//虚析构函数
virtual ~Base()
{
cout << "~Base()" << endl;
}
protected:
int ma;
};
class Derive : public Base
{
public:
Derive(int b) :mb(b), Base(b){}
void Show()//派生类里面有一个从基类里面继承的虚函数,返回值相同,函数名相同,参数列表也相同,函数自动被编译器处理成虚函数
{
std::cout << "Derive::Show()" << std::endl;
}
virtual ~Derive()
{
cout << "~Derive()" << endl;
}
public:
int mb;
};
int main()
{
Base b(10);
Derive d(20);
//d = b;//err 基类不能赋值给派生类
b = d;//ok 派生类可以赋值给基类
//对象的赋值是从下到上
Base* pb = &d; //ok 基类的指针可以指向派生类对象
//但是基类不能通过指针访问派生类自己的成员,只能访问派生类从基类那里继承的成员
//Derive* pd = &b; //err 派生类的指针指向基类对象
Base& rb = d; //ok 基类的引用引用派生类对象
//Derive& rd = b; //err 派生类的引用引用基类对象
return 0;
}
虚函数机制
虚函数指针指向虚函数表,虚函数表里面存放虚函数的地址。虚函数表在编译阶段产生,程序运行时存放在只读数据段,生命周期是程序开始到结束。
虚函数指针是在对象里面给出的,相同类型的虚函数指针指向同一块虚函数表。
派生类里面有一个从基类里面继承的虚函数,返回值相同,函数名相同,参数列表也相同,函数自动被编译器处理成虚函数。
派生类从基类继承 vfptr 以后,如果派生类也有 vfptr,两个表就会合并成一个。派生类覆盖基类的虚函数表,使两张表可以合成一张。
虚析构
析构函数能满足同名覆盖的关系,最后析构的时候把派生类的析构函数的地址放在寄存器的地址
析构函数什么时候要被写成虚函数
基类的指针指向堆上的对象。delete 指针时,由于指针是个基类类型,只能调用基类的析构函数。如果基类的析构函数是普通函数,会导致派生类的析构函数无法调用
静态的多态: 直接调用对象,编译期绑定 例如:模板、函数的重载
动态的多态: 用指针或者引用调用虚函数,对象完整,运行时绑定 例如: call 虚函数、call eax
基类:内存分布
派生类:Derive::show() 在虚函数表里面把 Base::show() 覆盖
虚函数表
- 能取地址
- 依赖对象调用
那些能成为虚函数表
普通的全局变量 X
普通的类成员方法 V
静态的成员方法 X 没对象
inline函数 X 没地址
构造函数 X 没对象
析构函数 V
int main()
{
Base* pb = new Derive(10);
std::cout << sizeof(Base) << std::endl; //8 vfptr,ma
std::cout << sizeof(Derive) << std::endl; //12 vfptr,ma,mb
std::cout << typeid(pb).name() << std::endl; //class Base*
std::cout << typeid(*pb).name() << std::endl; //class Base // class Derive
//访问的是对象RTTI的类型信息,运行的是谁,访问的就是谁
pb->Show(); //动多态//call Base::Show
//call ecx===>运行时的绑定 运行时的多态
//如果show()没有加vfptr,就是静多态,在编译期确定
delete pb;
Derive der(10);
Base &b = der;
std::cout << typeid(b).name() << std::endl;//class Derive
//虚函数指针指向虚函数表,打印类型访问RTTI
return 0;
}
纯虚函数
拥有纯虚函数的类是抽象类,不能定义对象,可以定义指针引用
class Brid
{
public:
Brid(std::string name) :mname(name){}
virtual void Fly() = 0;//纯虚函数 //编译器可以不对该函数提供实现
protected:
string mname;
};
class Eagle:public Brid
{
public:
Eagle(string name) :Brid(name){}
virtual void Fly()//派生类从抽象类继承的纯虚函数要重写,否则派生类也变成抽象类
{
cout << mname << " can fly 100m!" << endl;
}
};
class Chicken: public Brid
{
public:
Chicken(std::string name) :Brid(name){}
virtual void Fly()
{
cout << mname << " can not fly!" << endl;
}
};
void ShowFly(Brid* pb)//通过基类的指针接收各种不同的实参
{
pb->Fly();
}
p1 和 p2 指的是 e 和 c 的虚函数表,两者被交换,e 和 c 的虚函数表被交换
int *p1 = (int*)&e;
int *p2 = (int*)&c;
int tmp = p1[0];
p1[0] = p2[0];
p2[0] = tmp;
如果基类是普通函数,而派生类是虚函数,内存布局如下。无法实现多态,代码会崩溃。代码会导致派生类起始部分存放的不是基类部分,而是虚指针
vfptr
Base::
ma
mb
虚继承
class A//虚基类
{
public:
A(int a) :ma(a){}
public:
int ma;
};
class B :virtual public A//虚继承
{
public:
B(int b) :mb(b),A(b){}
public:
int mb;
};
派生类的内存布局
菱形继承
#include <iostream>
//将父类继承爷爷类,改成虚继承,防止儿子在多继承我时,出现爷爷的变量拷贝多份
class A
{
public:
A(int a) :ma(a){}
public:
int ma;
};
class B :virtual public A
{
public:
B(int b) :mb(b),A(b){}
public:
int mb;
};
class C :virtual public A
{
public:
C(int c) :mc(c),A(c){}
public:
int mc;
};
class D : public B, public C
{
public:
D(int d) :md(d),B(d),C(d),A(d){}
public:
int md;
};
int main()
{
D d(10);
d.ma = 20;
return 0;
}
D的构造顺序ABACD,析构顺序DCABA
