多态分两类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别
静态多态的函数地址 早绑定,编译阶段确定函数地址
动态多态的函数地址 晚绑定,运行阶段确定函数地址
动态多态的满足条件
1、有继承关系
2、子类重写父类的虚函数
动态多态的使用
父类的指针或者引用,执行子类对象
多态的优点
1、代码组织结构清晰
2、可读性强
3、利于前期和后期的扩展和维护
#include
using namespace std;
class Animal
{
public:
//虚函数 动态多态
virtual void speak()
{
cout << "shuohua" << endl;
}
//Animal类内部结构:vfptr:虚函数(表)指针 指向一个虚函数表(vftable)
//虚函数表的内部记录一个虚函数的地址(&Animal::speak)
//而子类也会继承这个指针,指向子类的虚函数表,
//当子类重写了父类的虚函数,那么子类内部的虚函数表会替换(覆盖)成子类的函数地址(&Cat::speak)
//当父类的指针或引用指向子类对象时,发生多态
};
class Cat :public Animal
{
public:
//重写:函数返回值类型 函数名 参数列表 完全相同
virtual void speak()
{
cout << "cat 说话" << endl;
}
};
class Dog :public Animal
{
virtual void speak()
{
cout << "Dog 说话" << endl;
}
};
//地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal)//c++允许父子类的相互转换
{
animal.speak();
}
void test02()
{
cout << "size of Animal = " << sizeof(Animal1) << endl;//加virtual后 4字节 无virtual,相当于空类 1字节
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
//test02();
system("pause");
return 0;
}
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,那么动态联编
案例
#include
using namespace std;
//普通实现
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;
};
//利用多态实现
//基类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
class AddCllculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
class SubCllculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
class MulCllculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void work(AbstractCalculator& calculator)
{
cout << calculator.getResult() << endl;
}
void test01()
{
//用指针指向子类对象
AbstractCalculator* cal = new AddCllculator;
cal->m_Num1 = 10;
cal->m_Num2 = 10;
cout << cal->getResult() << endl;
delete cal; //记得销毁
//用引用指向子类对象
AddCllculator add;
add.m_Num1 = 100;
add.m_Num2 = 100;
work(add);
}
int main()
{
test01();
system("pause");
return 0;
}
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此,可将虚函数改为纯虚函数
纯虚函数的语法: virtual 返回值类型 函数名 (参数列表) = 0;
抽象类:当类中有了(只要有一个)纯虚函数,这个类也称为抽象类
抽象类的特点:
1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include
using namespace std;
//抽象类
class Base
{
public:
virtual void func() = 0;//子类必须要重写
};
class Son1 :public Base
{
public:
virtual void func()
{
cout << "Son1 func函数调用" << endl;
}
};
class Son2 :public Base
{
public:
virtual void func()
{
cout << "Son2 func函数调用" << endl;
}
};
void test01()
{
//Base b = new Base 抽象类无法实例化
Son1 s;//子类必须重写父类的纯虚函数,否则也无法实例化
s.func();
Base* base1 = new Son1;
base1->func();
Base* base2 = new Son2;
base2->func();//这就是多态的体现,通过创建对象的不同调用不同的接口
}
int main()
{
test01();
system("pause");
return 0;
}
在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要具体的函数实现
虚构函数和纯虚析构的区别:如果是纯虚析构,该类属于抽象类,无法实例化。
纯虚函数在类内声明,类外实现。
#include
using namespace std;
#include
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数调用" << endl;
}
virtual void speak() = 0;
//利用虚析构可以解决 父类指针释放子类对象不干净的问题
//virtual ~Animal()
//{
// cout << "Animal 析构函数调用" << endl;
//}
//纯虚析构
virtual ~Animal() = 0;//必须有代码的实现,否则会报错,因为如果父类中也有成员开辟在堆区中,在类外实现
};
//在类内声明,在类外实现
Animal::~Animal()
{
cout << "Animal的纯虚析构函数" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "cat 构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "喵喵喵" << endl;
}
string *m_Name;
~Cat()//若父类的析构函数没有虚化,则不执行此函数,也就是无法释放堆区数据,导致内存泄露
{
if (m_Name != NULL)
{
cout << "cat的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
};
void test01()
{
Animal* animal = new Cat("tom");
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
只有在子类中有堆区数据时,才需要用到虚析构和纯虚析构