多态分为两类:
• 静态多态,函数重载 和 运算符重载 ,复用函数名。
• 动态多态, 派生类和虚函数。
平常说的多态大都指的是“动态多态”。
静态多态和动态多态的区别:
• 静态多态---地址早绑定---编译阶段确定函数的地址。
• 动态多态---地址晚绑定---运行阶段确定地址。
注:往后的代码main中的情形基本为如下形式
int main()
{
text01();
system("pause");
return 0;
}
目录
1. 多态的基本语法
接下来,构建一个场景,方便理解:
//建立一个动物类(父类)
class Animal
{
public:
//动物会说话
void speak()
{
cout << "动物在说话" << endl;
}
};
//建一个猫类(子类)
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫喵喵叫" << endl;
}
};
//建立一个狗类(子类)
class Dog:public Animal
{
public:
void speak()
{
cout << "小狗汪汪叫" << endl;
}
};
//执行说话的函数
void doSpeak(Animal& a)
{
a.speak();
}
void text01()
{
Cat c;
doSpeak(c);//Animal& a = c
Dog d;
doSpeak(d);
}
如上代码运行之后,呈现的均为Animal的speak函数,而非,猫用猫的,狗用狗的。
原因:其中的doSpeak函数中的speak函数的地址已经默认为Animal类的了,为早绑定。
做如下修改:
class Animal
{
public:
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat: public Animal
{
public:
//此行为称为“重写” 函数的返回值类型、名称、参数列表 与父类虚函数 完全相同
//此处,打头的virtual可写可不写
virtual void speak()
{
cout << "小猫喵喵叫" << endl;
}
};
class Dog: public Animal
{
public:
void speak()
{
cout << "小狗汪汪叫" << endl;
}
};
在运行,会发现,猫用猫的speak,狗用狗的speak。
原因:此时,地址为晚绑定。
综上,动态多态的满足条件:
1. 有继承关系。
2. 子类要重写虚函数。
动态多态的使用:
利用父类的指针或引用指向子类的对象。(doSpeak(Animal & a);)
2. 多态的深入剖析
对于Animal.speak,对比没加virtual和加了virtual的大小(sizeof(Animal))。
会发现前者大小为1(即空类),后者大小为4,其中多了个指针,
该指针被称为vfptr,v--virtual,f---functon,ptr---pointer,即虚函数(表)指针,
该指针指向vftable(即虚函数表)(vftable是共享的),表内记录着虚函数的地址。
Animal内部结构:
vfptr -> vftable
vftable
{&Animal::speak
}
//当子类中出现重写,子类的虚函数表 内部 会将父类的地址替换成子类虚函数的地址
Cat内部结构:
vfptr -> vftable
vftable
{&Cat::speak
}
//此时父类的指针或引用指向子类时,就会发生多态。
Animal& animal = cat;
animal.speak();
可以通过查看对象模型来进行验证:
Animal中无virtual
Animal中有virtual
对于Cat (在Animal有virtual的情况下)
不发生重写
发生重写
仔细看会发现,其中,父类虚函数的地址被子类虚函数的地址给覆盖掉了。
3. 多态带来的好处
接下来,我们通过一个简单的例子来进行分析
//设置一个简易的计算器
class Calculator
{
public:
//计算器的简单运算原理
int getResult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
}
//输入计算的两个数
int m_Num1;
int m_Num2;
};
//实现计算
void text01()
{
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 20;
//加法运算
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
}
以上就是一个普通的简单计算器,可以看出,如果此时,要添加一个除法运算,就需要去源
代码中进行修改,再看一看用多态写的计算器
class Calculator
{
public:
//虚函数
virtual int getResult()
{
//return什么都无所谓
return 0;
}
int m_Num1;
int m_Num2;
};
class AddCalculator:public Calculator
{
public:
//此处的virtual可写可不写,以下均不写
virtual int getResult()
{
return m_Num1 + m_Num2;
}
};
class SubCalculator :public Calculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
class MulCalculator :public Calculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
//功能演示
void text02()
{
//多态使用条件:父类的指针或引用指向子类
Calculator* c = new AddCalculator();
c->m_Num1 = 10;
c->m_Num2 = 30;
cout << c->m_Num1 << " + " << c->m_Num2 << " = " << c->getResult() << endl;
//因为用new在堆区开辟新的空间,所以最后要delete
delete c;
//再看看其他的(减法)
Calculator* b = new SubCalculator();
b->m_Num1 = 10;
b->m_Num2 = 30;
cout << b->m_Num1 << " - " << b->m_Num2 << " = " << b->getResult() << endl;
delete b;
}
对比两种代码可以看到,多态有一个显著的优点,那就是代码量变多了,可以很好地锻炼我
们的手部肌肉,增强手指灵活性,让男程序员、女程序员更好更有力地自我安慰……咳咳。
言归正传,言归正传,虽然,代码量变多了,但是,他有一个显著的优点,那就是功能区域
化,每一个功能都有一个自己的类,组织结构非常的清晰,如果出现了错误,可以很快地找到他的
位置。
第二个优点就是代码的扩展和维护方便,只需在外部添加或者删除,不需要深入源代码。
另外,使用多态,还有一个优点,但由于以上示例过于简单的原因,所以看不出来——可读
性强,在实际的工作中,通常都是多人合作完成代码,所以可读性也是非常关键的一点,有句话
怎么说的来着,你写的代码别人一眼就能看明白,不是说明别人有多牛逼,而是体现出你写代码的
能力很强。
总结,多态的好处有三:结构清晰、扩展与维护方便、可读性强。