基本概念
多态分为两类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址
基本语法
C++中允许父类和子类之间的类型转换,父类的引用可以直接指向子类的对象
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话。" << endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
cout << "猫在说话。" << endl;
}
};
//地址早绑定,在编译阶段确定函数地址
void doSpeak(Animal& animal)
{
animal.speak();
}
void test()
{
Cat cat;
doSpeak(cat);
}
int main()
{
test();
system("pause");
return 0;
}
如果想执行子类的函数,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定,在父类的同名函数前加virtual关键字。
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话。" << endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
cout << "猫在说话。" << endl;
}
};
void doSpeak(Animal& animal)
{
animal.speak();
}
动态多态满足条件
1、有继承关系
2、子类重写父类的虚函数
重写不是重载,重载是函数名相同,参数不一样,重写是函数返回值、函数名、形参列表中的所有内容相同
动态多态使用,父类的指针或者引用,执行子类对象
virtual的原理
Animal类中不写virtual时,大小为1个字节,因为函数不属于类,Animal相当于个空类,编译器为了确定该空类的信息,规定大小为1个字节。
使用virtual后,会在类中生成一个虚函数(表)指针(vfptr),指针占4个字节,该指针指向虚函数表(vftable),表内部记录虚函数的地址,就是Animal::speak()函数的地址,&Animal::speak()。
当子类Cat中没有出现函数重写,即
class Cat:public Animal
{
public:
};
继承后会继承父类的东西,子类中也会有一个虚函数指针,指向子类的虚函数表,记录的还是&Animal::speak(),检查类的内存情况:
1>class Animal size(8):
1> +---
1> 0 | {vfptr}
1> +---
1>Animal::$vftable@:
1> | &Animal_meta
1> | 0
1> 0 | &Animal::speak
1>Animal::speak this adjustor: 0
1>class std::is_error_code_enum<char const *> size(1):
1> +---
1> 0 | +--- (base class std::integral_constant<bool,0>)
1> | +---
1> +---
1>class Cat size(8):
1> +---
1> 0 | +--- (base class Animal)
1> 0 | | {vfptr}
1> | +---
1> +---
1>Cat::$vftable@:
1> | &Cat_meta
1> | 0
1> 0 | &Animal::speak
1>class std::ostreambuf_iterator<wchar_t,struct std::char_traits<wchar_t> > size(16):
1> +---
1> 0 | _Failed
1> | <alignment member> (size=7)
1> 8 | _Strbuf
1> +---
当子类出现函数重写后,子类中的虚函数表内部会被替换为子类的虚函数地址,&Cat::speak(),当父类的指针或者引用指向子类对象时,发生多态
Animal& animal = cat;
animal.speak();
会从cat的虚函数表中找这个函数,检查类的内存布局:
1>class Animal size(8):
1> +---
1> 0 | {vfptr}
1> +---
1>Animal::$vftable@:
1> | &Animal_meta
1> | 0
1> 0 | &Animal::speak
1>Animal::speak this adjustor: 0
1>class std::is_error_code_enum<char const *> size(1):
1> +---
1> 0 | +--- (base class std::integral_constant<bool,0>)
1> | +---
1> +---
1>class Cat size(8):
1> +---
1> 0 | +--- (base class Animal)
1> 0 | | {vfptr}
1> | +---
1> +---
1>Cat::$vftable@:
1> | &Cat_meta
1> | 0
1> 0 | &Cat::speak
1>Cat::speak this adjustor: 0
案例
多态的优点:代码组织结构清晰、可读性强、利于前期和后期的扩展和维护。
写一个计算器类
常规写法:
class Calculator
{
public:
int m_num1;
int m_num2;
public:
float GetSymbol(string symbol)
{
if(symbol == "+")
{
return m_num1 + m_num2;
}
else if(symbol == "-")
{
return m_num1 - m_num2;
}
else if(symbol == "*")
{
return m_num1 * m_num2;
}
}
};
void test()
{
Calculator c1;
c1.m_num1 = 10;
c1.m_num2 = 2;
cout<< c1.m_num1 <<"+" <<c1.m_num2 <<"="<<c1.GetSymbol("+") << endl;
cout << c1.m_num1 << "-" << c1.m_num2 << "=" << c1.GetSymbol("-") << endl;
cout << c1.m_num1 << "*" << c1.m_num2 << "=" << c1.GetSymbol("*") << endl;
}
如果对已经写的计算器进行扩展,比如增加运算,除法、乘方之类的,就得在类中的函数中进行修改。
多态写法
class AbstractCalculator
{
public:
int m_Num1;
int m_Num2;
virtual int getResult()
{
return 0;
}
};
class ADDCalculator : public AbstractCalculator
{
virtual int getResult()
{
return m_Num1+m_Num2;
}
};
class SUBCalculator : public AbstractCalculator
{
virtual int getResult()
{
return m_Num1 - m_Num2;
}
};
class MULCalculator : public AbstractCalculator
{
virtual int getResult()
{
return m_Num1 * m_Num2;
}
};
void test()
{
AbstractCalculator* abc = new ADDCalculator;//父类的指针指向子类
//new出一个子类,返回地址
abc->m_Num1 = 10;
abc->m_Num2 = 2;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//new之后要删除
delete abc;
abc = new SUBCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 2;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
}
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0
,当类中有了纯虚函数,这个类也称为抽象类。
抽象类的特点:
无法实例化对象(也不能在堆区new出来),子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
案例2 制作饮品
class AbstractDrink
{
public:
virtual void step1() = 0;
virtual void step2() = 0;
virtual void step3() = 0;
void makeDrink()
{
step1();
step2();
step3();
}
};
class Coffee : public AbstractDrink
{
public:
virtual void step1()
{
cout << "第一步,倒入咖啡" << endl;
}
virtual void step2()
{
cout << "第二步,煮咖啡" << endl;
}
virtual void step3()
{
cout << "第三步,添加糖和牛奶" << endl;
}
};
class Tea : public AbstractDrink
{
public:
virtual void step1()
{
cout << "第一步,倒入茶" << endl;
}
virtual void step2()
{
cout << "第二步,煮茶" << endl;
}
virtual void step3()
{
cout << "第三步,添加柠檬" << endl;
}
};
void doDrink(AbstractDrink * abc)
{
abc->makeDrink();
delete abc;
}
void test()
{
doDrink(new Coffee);
cout << "-------------" << endl;
doDrink(new Tea);
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,
解决方法:将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针无法释放子类对象,都需要有具体的函数实现
虚析构和纯虚析构的区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法:virtual ~类名() {}
纯虚析构语法:virtual ~类名()=0;
如果子类中没有堆区数据,可以不写虚析构或纯析构
案例
class Animal
{
public:
Animal()
{
cout << "Animal构造函数的调用" << endl;
}
~Animal()
{
cout << "Animal析构函数的调用" << endl;
}
virtual void doSpeak() = 0;
};
class Cat :public Animal
{
public:
string * m_name;
virtual void doSpeak()
{
cout << *m_name << "小猫在叫" << endl;
}
Cat(string name)
{
cout << "Cat构造函数的调用" << endl;
m_name = new string(name);
}
~Cat()
{
cout << "Cat析构函数的调用" << endl;
if(m_name != NULL)
{
delete m_name;
m_name = NULL;
}
}
};
void test()
{
Animal* abc = new Cat("Tom");
abc->doSpeak();
delete abc;
}
输出为:
构造时先构造父类后构造子类,按情况析构时先析构子类再析构父类,但在这子类没有调用析构函数,
在delete Animal,后析构父类指针时不会调用子类中的析构函数,导致子类如果有堆区的数据,就会出现内存泄漏。
解决方法:虚析构
class Animal
{
public:
Animal()
{
cout << "Animal构造函数的调用" << endl;
}
virtual ~Animal()
{
cout << "Animal析构函数的调用" << endl;
}
virtual void doSpeak() = 0;
};
解决方法:纯虚析构
和纯虚函数不一样,纯虚函数仅需要声明即可,而纯虚析构既需要声明,也需要实现。
class Animal
{
public:
Animal()
{
cout << "Animal构造函数的调用" << endl;
}
virtual ~Animal() = 0;//只写这个不对,假如父类中也有堆区的数据
//父类的析构就也需要具体实现,所有仅有声明是不够的
virtual void doSpeak() = 0;
};
Animal:: ~Animal()
{
cout << "Aniaml析构函数的调用" << endl;
}
案例,电脑组装类
//具体零件基类
class AbstractCPU
{
public:
virtual void Calculate() = 0;
};
class AbstractVideoCard
{
public:
virtual void Display() = 0;
};
class AbstractMemoryModule
{
public:
virtual void Storage() = 0;
};
//不同厂商生产的零件子类
class IntelCPU :public AbstractCPU
{
public:
virtual void Calculate()
{
cout << "Intel厂家的CPU工作" << endl;
}
};
class IntelVideoCard :public AbstractVideoCard
{
public:
virtual void Display()
{
cout << "Intel厂家的显卡工作" << endl;
}
};
class IntelMemoryModule :public AbstractMemoryModule
{
public:
virtual void Storage()
{
cout << "Intel厂家的内存条工作" << endl;
}
};
class LenovoCPU :public AbstractCPU
{
public:
virtual void Calculate()
{
cout << "Lenovo厂家的CPU工作" << endl;
}
};
class LenovoVideoCard :public AbstractVideoCard
{
public:
virtual void Display()
{
cout << "Lenovo厂家的显卡工作" << endl;
}
};
class LenovoMemoryModule :public AbstractMemoryModule
{
public:
virtual void Storage()
{
cout << "Lenovo厂家的内存条工作" << endl;
}
};
class Computer
{
public:
Computer(AbstractCPU* cpu, AbstractVideoCard* videoCard, AbstractMemoryModule* memoryModule)
{
m_cpu = cpu;
m_memoryModule = memoryModule;
m_videoCard = videoCard;
}
void doWork()
{
m_cpu->Calculate();
m_videoCard->Display();
m_memoryModule->Storage();
}
~Computer()
{
if (m_cpu != NULL)
{
cout << "delete m_cpu" << endl;
delete m_cpu;
m_cpu = NULL;
}
if (m_videoCard != NULL)
{
cout << "delete m_videoCard" << endl;
delete m_videoCard;
m_videoCard = NULL;
}
if (m_memoryModule != NULL)
{
cout << "delete m_memoryModule" << endl;
delete m_memoryModule;
m_memoryModule = NULL;
}
}
private:
//抽象类不能实例化,但是可以定义一个指向该类的指针
AbstractCPU* m_cpu;
AbstractVideoCard* m_videoCard;
AbstractMemoryModule* m_memoryModule;
};
void test()
{
Computer c1= Computer(new IntelCPU,new IntelVideoCard,new IntelMemoryModule);
c1.doWork();
cout << "------------------"<< endl ;
Computer c2 = Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemoryModule);
c2.doWork();
}
test函数中Computer也可以这样创建
void test()
{
Computer *c1= new Computer(new IntelCPU,new IntelVideoCard,new IntelMemoryModule);
c1->doWork();
delete c1;
cout << "------------------"<< endl ;
Computer * c2 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemoryModule);
c2->doWork();
delete c2;
}