个人在学习C++的基本STL用法后,虽然对于C++各项功能都有了一些了解,但是却无法形成具体系统影响,导致容易遗忘。这里进行西嘎嘎进阶内容整理,主要是内存管理、继承组成、多态虚函数。本章主要说明内存管理。
封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。
多态是C++面向对象编程中的关键技术,有继承关系的两个类,这两个类里面都有一个函数且名字、参数、返回值均相同,然后我们通过调用函数来实现不同类对象完成不同的事件,简单讲就是不同场景下同一件事物表现形式不同。对于多态的理解,最简单的例子就是运算符,我们通过运算符重载的方式,实现了针对不同输入,整型、浮点、双精度、乃至数组等输入数据类型的不同数据运算方法。
一个源程序经过编译、链接,才能成为可执行文件。根据编联完成时刻不同,编译编联称为静态联编;程序运行时联编叫动态联编。
静态联编支持的多态性称为静态多态性,大多通过函数重载和模板实现,也叫早绑定
动态联编所支持的多态性称为运行时多态(动态多态),大多通过虚函数实现,也叫后绑定。
利用函数重载机制,在调用同名函数时,编译系统会根据实参的具体情况确定调用函数,如果找不到合适输入类型则报错,详细可以查阅函数重载和模板类设计,这里不过多展开。
class hero{
......
void attack(warrior& hero){hero.HP-=this->attack>hero.phydefence?this->attack-hero.phydefence*0.6:0;}
void attack(master& hero){hero.HP-=this->attack;}
void attack(worker& hero){hero.HP-=this->attack*1.5;}
}
多态是在有继承关系的类对象中,去调用同一函数,根据指向对象类型不同,产生了不同的行为。
...
//如下所示为一简单多态例子
//此时,如果通过hero指针调用派生类的虚函数重写,实现多态。
class hero{
......
virtual void move()=0; //纯虚函数
}
class warrior:public hero{
......
void move() override {cout << "warrior is moving.";} //虚函数重写
}
class master:public hero{
......
void move() override {cout << "master is moving.";} //虚函数重写
}
int main(){
hero* sunzi = new warrior();
hero* yeye = new master();
sunzi->move();
yeye->move();
}
要实现多态操作,必要条件有二:
1.被调用函数必须是虚函数,且完成虚函数重写,如下例所示,基类虚函数在派生类重写函数中的名称、返回值、形参列表都完全相同;
2.通过基类指针或引用实现函数调用,这里会发生切片操作,只赋值基类成员,基类与派生类的虚表不同,函数调用时寻找派生类虚函数实现多态,如果不满足条件则调用基类函数。
以上说明了多态的表现形式,本身这种技术并不算特别复杂,主要是通过virtual和override引入了虚表的概念,为各种重写操作提供了“模板类”,通过完善派生类函数,实现多态,提高了代码的扩展性,可以为同一接口添加无限功能;提高了代码的可维护性,通过继承可以直接获取上一版本的代码,但需要修改或扩充功能时,也无需修改前期代码,直接通过多态覆盖即可。
以上内容说明多态的基本表现形式,主要涉及知识有虚函数、虚表以及抽象类,下面进行说明。
虚函数,即类成员函数前加有virtual关键字的函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,无需再显式声明为virtual。
**虚函数本质是通过构建虚表实现同名函数管理,实现多态功能。**虚函数重写,也称为覆盖,在派生类中通过构造一个与基类虚函数完全相同的函数(函数名、参数、返回值都相同),覆盖掉对应基类虚函数。当通过基类指针调用派生类时,根据虚表提取出派生类对应函数。
经检验以下函数不能定义为虚函数:
1)友元函数,它不是类的成员函数(只有成员函数可以成为虚函数)
2)全局函数(同上)
3)静态成员函数,它没有this指针(静态成员函数是属于声明类的,为所有对象实例公有,作用全局,只对类内部静态成员变量操作,当实例化类为对象时,静态成员被剔除,无法实例化。this作为实例指针,属于单个对象,只能操作对象内成员,也就无法调用静态成员)
3)构造函数(虚函数通过基类指针或引用调用子类的成员函数,而构造函数就是要确定一个具体对象,否则如何创建出派生类,没有基类就没有派生类,也就无法通过基类调用派生类实现功能。)
4)以及赋值运算符重载(可以但是一般不建议作为虚函数)
所有包含虚函数的类都有虚表,只用于存储虚函数地址,且表中虚地址的排列顺序和虚函数声明顺序相同。
编译器在编译的时候,发现基类中有虚函数,那么编译器会为每个包含虚函数的类创建一个虚表vtable,用于存放虚函数地址。为了能够实现虚函数的管理,光有虚表还不够,为了实现定位,编译器为每个对象提供了虚表指针vptr。在程序运行时,根据对象类型在构造函数中完成虚表的创建和指针的初始化,使指向所属类虚表,从而保证在调用执行多态时,能够找到正确函数。
派生类的虚表生成过程:
1.调用基类构造函数,构造基类虚表和初始化指针;
2.执行子类构造函数,先将基类中的虚表内容拷贝一份到派生类虚表中;
3.如果派生类重写了基类某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数地址;
4.派生类自己新增的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
如果想要在基类中定义虚函数,以便在派生类中重新定义更好地实现功能,但是又不能给出具体意义的实现,就可以使用纯虚函数,这是一种特殊的虚函数,在虚函数声明后面写上 = 0。包含纯虚函数的类就叫做抽象类,抽象类不能实例化出对象。纯虚函数是一定要被继承的,否则它存在没有任何意义,必须派生类重写,才能实例化,体现了更好的接口继承。这个纯虚函数的作用就是强迫我们重写虚函数,构成多态。
class hero{
......
virtual void move()=0; //纯虚函数
}
class warrior:public hero{
......
void move() override {cout << "warrior is moving.";} //虚函数重写
}
class master:public hero{
......
void move() override {cout << "master is moving.";} //虚函数重写
}
注意:override是用来检查函数是否重写,若基类声明纯虚函数,即使不写override也可以正常运行,实际中建议写明。
派生类虚表
C++ 多态的实现及原理
C++多态,简单直白的概括说明,建议先看