依旧还是来看一段代码
#include
using namespace std;
class base
{
public:
void priMsg()
{
cout<<__func__<<"line :"<<__LINE__<priMsg();
}
int main()
{
base obj1;
subclass obj2;
test(&obj1);
test(&obj2);
return 0;
}
运行结果:
priMsgline :14
priMsgline :14
首先我们要明白为什么obj2这个对象明明是subclass为什么也调用的14行的函数,是因为void test中的形参是 base* p,然后因为subclass是base的公有继承,所以向上转型,都调用的基类中的函数。
那怎么样才能实现不向上转型,分别调用父类和子类的函数呢?
加个 virtual就行了
#include
using namespace std;
class base
{
public:
virtual void priMsg()
{
cout<<__func__<<"line :"<<__LINE__<priMsg();
}
int main()
{
base obj1;
subclass obj2;
test(&obj1);
test(&obj2);
return 0;
}
运行结果:
priMsgline :14
priMsgline :22
这其实就是多态,那么现在开始我们今天的知识点,多态。
一.什么是多态?
-多态:字面意思”多种状态“,一个接口,多种方法,子类和基类的方法不同。(程序在运行时决定调用哪个方法,是面向对象编程的核心概念)。
程序在运行时才决定调用哪个方法,是面向对象编程的核心概念。
-多态性:将接口与实现进行分离,也就是实现共同的方法,但因为个体差异不同,采用不同的策略。
oop(面向对象编程)特点:
-封装(Wrap):实现细节隐藏,使代码模块化。
-继承(inheritance):扩展已存在的代码,目的是代码重用。
-多态(polymorphism):实现接口重用,不论传递过来的是哪个类的对象,函数都能通过同一个接口调用到适应各自对象的实现方法。
二.多态的实现
-使用virtual修饰的成员函数(虚函数)
虚函数的设置条件:
-——非类的成员函数不能设置为虚函数(例如友元函数)
-——类的静态成员不能定义为虚函数
首先什么是static静态成员函数?静态成员函数不属于类中的任何一个对象和实例,属于类共有的一个函数。也就是说,它不能用this指针来访问,因为this指针指向的是每一个对象和实例。
对于virtual虚函数,它的调用恰恰使用this指针。在有虚函数的类实例中,this指针调用vptr指针,指向的是vtable(虚函数列表),通过虚函数列表找到需要调用的虚函数的地址。总体来说虚函数的调用关系是:this指针->vptr(4字节)->vtable ->virtual虚函数。
所以说,static静态函数没有this指针,也就无法找到虚函数了。所以静态成员函数不能是虚函数。他们的关键区别就是this指针。
构造函数不能为const函数,构造函数的目的就是为了给成员变量赋初值,不能为const函数
-——构造函数不能定义为虚函数,但是析构函数却可以设置为虚函数
-——成员函数声明时需要使用 virtual关键字修饰,定义时不需要
-——基类成员函数设置为虚函数,那么派生类中同名函数(函数名,形参类型,个数返回值完全一样)自动称为虚函数。
三.覆盖,重载及隐藏
成员函数覆盖(override,也称重写)
是指派生类重新定义基类的虚函数,特征如下:
A.不同的作用域(分别位于派生类与基类)
B.函数名字相同
C.参数相同
D.基类函数必须有virtual关键字,不能有static
E.返回值相同
F.重写函数的权限访问限定符可以不同
成员函数重载(overload)
是指函数名相同,参数不同(数量,类型,次序),特征如下:
A.相同的范围(在同一个作用域中)
B.函数名字相同
C.参数不同
D.virtual关键字可有可无
E.返回值可以不同
成员函数的隐藏(也称重定义)
A.不在同一个作用域
B.函数名字可以不同
C.返回值可以不同
D.参数不同,此时,不论有无virtual关键字,基类的函数将被隐藏(注意和重载的区别,重载在同一个定义域中)
E.参数相同,但是基类函数没有virtual关键字,此时,基类的函数被隐藏(注意和覆盖的区别,覆盖是有virtual关键字)
四.联编(链接)
就是将模块或者函数合并在一起生成可执行代码的处理过程。按照联编所进行的阶段不同 ,可分为两种不同的联编方法:静态联编和动态联编
1.静态联编(静态链接)
是指在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定
2.动态联编(动态链接)
是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定。
————c++中一般情况下是静态联编,但是一旦涉及多态和虚拟函数就要使用动态联编了。
tips:重载只是一种语言特性,编译器根据函数不同的参数表,把同名函数区分开来,属于静态联编,与多态无关。引用一句Bruce Eckel的话:”不要犯傻,如果它不是晚绑定它就不是多态。”
五.抽象类
含有纯虚函数的类就是抽象类。
抽象类没有完整的信息,只能是派生类的基类,抽象类不能有实例,不能有静态成员,派生类应该实现抽象类的所有方法。
例:
class Graphic
{
public:
virtual float Area()=0;
};
class Rectangle :public Graphic
{
public:
float Area()
{
return h*w;
}
private:
float h,w;
};
一般的,使用一个类,只关心public成员,故此需要隐藏类的其他成员方法。例如,动物作为一个基类可以派生出老虎,孔雀等子类,但是动物本身并不能作为任何实际明确的对象。
六.虚析构函数
用来避免子类中回收不完整的情况
例子:
#include
#define pri() cout<<__func__<<"line::"<<__LINE__<
运行:
baseline::13
subclassline::21
~subclassline::23
~baseline::15
baseline::13
subclassline::21
~baseline::15
回收不完整
解决:
#include
#define pri() cout<<__func__<<"line::"<<__LINE__<
运行:
baseline::13
subclassline::20
~subclassline::21
~baseline::14
baseline::13
subclassline::20
~subclassline::21
~baseline::14
七.限制构造函数
构造函数权限不是public,那么这就是限制构造函数
#include
#define pri() cout<<__func__<<"line::"<<__LINE__<
运行报错
基类限制构造函数不能创建实例,只能派生出子类,定义子类对象来访问接口函数
#include
#define pri() cout<<__func__<<"line::"<<__LINE__<
可以运行:
baseline::13
subclassline::21
~subclassline::23
~baseline::15
如果是private限制呢?
这时候派生类也无法访问基类的私有成员,当然我们也有办法,那就是使用友元函数,打破基类私有限制的封装。
实例:
#include
using namespace std;
#define pri() cout<<__func__<<" line: "<<__LINE__<get();
freeObj(p);
return 0;
}