本文大纲:
-
继承的概念
-
子类构造过程
-
重载和隐藏
-
静态绑定、动态绑定和覆盖
-
虚析构函数
-
多态的概念
-
抽象类
继承
-
概念:
-
基类/父类 派生类/子类
-
类和类之间的关系:组合(是一部分)和继承(是一种)
-
代码复用
-
-
继承访问限定
-
外部只能访问对象public的成员,protected和privated的成员无法直接访问
-
在继承结构中,子类可以从基类继承过来private的成员,但是子类无法直接访问
-
protected和private区别?在基类中定义的成员,如果要被子类访问,但是外部不能访问,在基类中把成员定义为protected。如果子类和外部都不访问,在基类中把成员定义为private
-
默认的继承方式:class定义子类默认继承方式是private,struct定义子类默认继承方式是public
-
继承方式 | 基类的访问限定 | 子类的访问限定 | 外部的访问限定 |
---|---|---|---|
public | public | public | 可以访问 |
public | protected | protected | 不可以访问 |
public | private | 不可以访问 | 不可以访问 |
protected | public | protected | 不可以访问 |
protected | protected | protected | 不可以访问 |
protected | private | 不可以访问 | 不可以访问 |
private | public | private | 不可以访问 |
private | protected | private | 不可以访问 |
private | private | 不可以访问 | 不可以访问 |
子类构造过程
-
子类初始化从基类继承过来的成员变量:调用基类相应的构造函数来初始化
-
子类的构造和析构函数负责初始化和清理子类的成员
-
子类从基类中继承来的成员初始化由基类的构造和析构负责
-
class Base{
public:
Base(int data) : m_a(data){cout << "Base()" << endl;}
~Base(){cout << "~Base()" << endl}
protected:
int m_a;
};
class Derive : public Base{
public:
//调用基类相应的构造函数来初始化
Derive(int data) : Base(data), m_b(data){
cout << "Derive()" << endl;
}
~Derive(){
cout << "~Derive()" << endl
}
private:
int m_b;
};
int main(){
Derive d(20);
return 0;
}
-
上面代码运行结果如下。
-
Base()
-
Derive()
-
~Derive()
-
~Base()
-
-
子类对象构造和析构过程
-
子类调用基类的构造函数初始化从基类继承来的成员
-
子类调用自己的构造函数初始化子类自己的成员
-
当子类对象的作用域到期后,调用子类的析构函数,释放子类成员可能占用的外部资源(堆内存、文件等)
-
调用基类的析构函数,释放子类内存中从基类继承来的成员可能占用的外部资源(堆内存、文件等)
-
重载和隐藏
-
重载关系:同一作用域、同样的函数名、参数列表不同
-
隐藏(作用域隐藏)的关系:在继承结构中,子类的同名成员把基类的同名成员隐藏调用了
class Base{
public:
Base(int data = 10) : m_a(data){}
void display(){cout << "Base::display()" << endl;}
void display(int){cout << "Base::display(int)" << endl;}
protected:
int m_a;
};
class Derive : public Base{
public:
//调用基类相应的构造函数来初始化
Derive(int data = 15) : Base(data), m_b(data){}
void display(){cout << "Derive::display()" << endl;}
private:
int m_b;
};
int main(){
Derive d;
d.display();//Derive::display()
d.Base::display;//Base::display()
d.display(10);//error: Derive::display函数不接受1个参数
d.Base::display(10);//ok
return 0;
}
-
向上造型和向下造型:在继承结构中进行上下类型转换,默认只支持向上(基类)造型
int main(){ Base b(10); Derive d(20); //d = b;//类型从上到下的转换,向下造型是不可以的 //Derive *pd = &b;向下造型是不可以的 Derive *pd = (Derive *)&b;//不安全,涉及了内存的非法访问 b = d;//类型从下到上的转换,向上造型是可以的 Base *pb = &d;//类型从下到上的转换,向上造型是可以 }
静态绑定、动态绑定、覆盖
- 静态绑定:在编译时期绑定(函数调用)call确定的函数
class Base{
public:
Base(int data = 10) : m_a(data){}
void display(){cout << "Base::display()" << endl;}
void display(int){cout << "Base::display(int)" << endl;}
protected:
int m_a;
};
class Derive : public Base{
public:
//调用基类相应的构造函数来初始化
Derive(int data = 15) : Base(data), m_b(data){}
void display(){cout << "Derive::display()" << endl;}
private:
int m_b;
};
int main(){
Derive d(30);
Base *pb = &d;
pb->display();//静态绑定Base::display()
pb->display(10);//静态绑定Base::display(int)
cout << sizeof(Base) << typeid(pb).name() << endl; //4, class Base*
cout << sizeof(Derive) << typeid(*pb).name() << endl;//8, class Base
return 0;
}
-
动态(运行时期)绑定(函数调用)
-
如果类里面定义了虚函数,在编译阶段编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容是RTTI(Run-Time Type Information)指针和虚函数的地址。
-
当程序运行时,每一张虚函数表都会加载到内存的.rodata区
-
一个类里面定义了虚函数,这个类定义的对象,其运行时内存中开始部分多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。一个类型定义的多个对象指向同一个虚函数表
-
一个类里面的虚函数个数,不影响对象内存大小,影响虚函数表大小
-
如果子类中的函数和基类继承来的函数,返回值、函数名、参数列表都一样,而且基类函数的virtual虚函数,那么子类定义的这个函数自动处理为虚函数,覆盖从基类中继承过来的虚函数地址
-
class Base{
public:
Base(int data = 10) : m_a(data){}
virtual void display(){cout << "Base::display()" << endl;}
virtual void display(int){cout << "Base::display(int)" << endl;}
protected:
int m_a;
};
class Derive : public Base{
public:
//调用基类相应的构造函数来初始化
Derive(int data = 15) : Base(data), m_b(data){}
void display(){cout << "Derive::display()" << endl;}
private:
int m_b;
};
int main(){
Derive d(30);
Base *pb = &d;
//如果发现display()是虚函数,就进行动态绑定
//mov eax, dword ptr[pb]//pb指向的子类对象前四个字节放到到eax寄存器
//mov ecx, dword ptr[eax]
//call ecx(虚函数地址)
pb->display();//动态绑定,Derive::display()
pb->display(10);//动态绑定,Base::display(int)
cout << sizeof(Base) << typeid(pb).name() << endl; //8, class Base*
//pb是Base类型,Base类中有没有虚函数
//若Base没有虚函数,*pb识别的是编译时期的类型,*pb就是Base类型
//若Base有虚函数,*pb识别的就是运行时期的RTTI类型
cout << sizeof(Derive) << typeid(*pb).name() << endl;//12, class Derive
return 0;
}
- 覆盖的概念:基类和子类的函数,其返回值、函数名和参数列表都相同,而且基类的函数是虚函数,那么子类的函数就自动处理为虚函数,他们之间成为覆盖关系
- 动态绑定:虚函数通过指针或引用变量调用,才发生动态绑定
- 构造函数调用虚函数,静态绑定
- 对象本身调用虚函数,静态绑定
虚析构函数
- 虚函数依赖:
- 虚函数能产生函数地址,存储在vftable中
- 对象必须存在(对象中的vfptr -> vftable -> 虚函数地址)
- 构造函数不能为虚函数,构造函数调用的任何函数,都是静态绑定
- static静态成员函数不能为虚函数
- 虚析构函数调用的时候对象是存在的
class Base{
public:
Base(int data) : m_a(data){cout << "Base()" << endl;}
virtual ~Base(){cout << "~Base()" << endl}
virtual void display(){cout << "call Base::display()" << endl}
protected:
int m_a;
};//&Base::~Base &Base::display
class Derive : public Base{
public:
//调用基类相应的构造函数来初始化
Derive(int data) : Base(data), m_b(data), m_ptr(new int(data)){
cout << "Derive()" << endl;
}
~Derive(){
delete m_ptr;
cout << "~Derive()" << endl
}
private:
int m_b;
int *m_ptr;
};//&Derive::~Derive &Base::display
int main(){
Base *pb = new Derive(20);
pb->display();
delete pb;//如果不定义虚析构函数,派生类的析构函数没有被调用到!!!
//pb看到析构函数是普通函数,静态绑定,只调用Base::~Base()
return 0;
}
多态的概念
-
静态(编译期间)的多态:函数重载、模板(函数模板和类模板)
-
动态(运行期间)的多态:
-
在继承结构中,基类指针(引用)指向子类对象,通过该指针(引用)调用同名覆盖函数(虚函数),基类指针指向哪个子类对象,就调用哪个子类对象的覆盖函数
-
多态底层是通过动态绑定来实现的:基类指针指向哪个子类对象,就会访问该子类对象的vfptr指针,继续访问该子类对象的vftable,从虚函数表中调用的函数是子类对应的虚函数
-
class People{
public:
People(string name) : _name(name){}
virtual void who() = 0;//纯虚函数
protected:
string _name;
};
class Student : public People{
public:
Student(string name) : People(name){}
void who(){cout << _name << "is a student" << endl;}
};
class Teacher : public People{
public:
Teacher(string name) : People(name){}
void who(){cout << _name << "is a teacher" <<endl;}
}
class Police : public People{
public:
Police (string name) : People(name){}
void who(){cout << _name << "is a police " <<endl;}
}
//全局的API,符合软件设计的“开-闭”原则:对修改关闭、对扩展开放
void who(People &people){
people.who();
}
int main(){
Student student("asc");
Teacher teacher("qwe");
Police police("asd");
who(student);
who(teacher);
who(police);
return 0;
}
-
继承的优点:
-
代码复用
-
在基类中给所有的子类提供统一的虚函数接口,让子类进行重写,使用多态
-
抽象类
-
问题抛出:把什么类设计成抽象类?
-
定义基类的初衷并不是让基类抽象一个实体类型
-
基类的好处:
-
定义属性,子类都可以继承复用属性
-
给所有的子类保留统一的覆盖/重写接口
-
-
拥有纯虚函数的类是抽象类,抽象类不能再实例化对象,但是可以定义指针和引用变量
class Aircraft{
public:
Aircraft(string name, double oil) : _name(name), _leftOil(oil){}
double getLeftMiles(){
return _leftOil*getMilesPerPounds() //动态绑定
}
protected:
string _name;
double _leftOil;
virtual double getMilesPerPounds() = 0;
};
class SmallAircraft{
public:
SmallAircraft(string name, double oil) : Aircraft(name, oil){}
double getMilesPerPounds(){return 20.0;}
};
class MiddleAircraft{
public:
MiddleAircraft(string name, double oil) : Aircraft(name, oil){}
double getMilesPerPounds(){return 15.0;}
};
class LargeAircraft{
public:
LargeAircraft(string name, double oil) : Aircraft(name, oil){}
double getMilesPerPounds(){return 10.0;}
};
//给外部提供一个统一的接口
void showLeftMiles(Aircraft &aircraft){
cout << aircraft.getLeftMiles() << endl;//静态绑定
}
int main(){
SmallAircraft sa("小飞机", 200);
MiddleAircraft ma("中飞机", 300);
LargeAircraft la("大飞机", 400);
showLeftMiles(sa);
showLeftMiles(ma);
showLeftMiles(la);
return 0;
}