多态
导航:
1.多态基本概念
2.多态小案例
3.纯虚函数
4.虚析构与纯虚析构
———————————————————————————————————
多态的基本概念
多态分为两类:
1.静态多态:函数重载以及运算符重载都属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行多态
如何区分:
静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址
动态多态的满足条件:
1.具有继承关系。
2.子类重写父类的虚函数。(虚函数就是在成员函数前加上关键字virtual)
其中重写条件:类中函数的返回值类型,函数名,参数列表必须完全相同
动态多态的使用方法:父类的指针或者引用指向子类地址。
例子:
#include <iostream>
#include <string>
using namespace std;
class cost //定义一个父类
{
public:
void shop() //仅仅只是一个成员函数,函数地址早绑定,也就是早确定函数地址
{
cout<<"最高消费0元"<<endl;
}
};
class shoper1:public cost //定义一个子类
{
public:
void shop()
{
cout<<"最高消费1000元"<<endl;
}
};
class shoper2:public cost
{
public:
void shop()
{
cout<<"最高消费1500元"<<endl;
}
};
void doshop(cost &cs) //父类的指针或者引用可以指向子类对象
{
cs .shop(); //进行成员函数调用
}
void test01()
{
shoper1 s1;
doshop(s1); //进行引用传递
shoper2 s2;
doshop(s2);
}
int main()
{
test01();
system("pause");
return 0;
}
运行程序:
最高消费0元
最高消费0元
如何将其重写,然后输出该有结果
这时候就要在父类中的函数前加上virtual,此时便为虚函数,是动态多类,函数地址晚绑定,就可以就行重写。
例如:
public:
virtual void shop() //加上virtual则叫做虚函数,这样函数地址是晚绑定,不加则就是早绑定,确定了函数地址
{
cout<<"最高消费0元"<<endl;
}
};
这个时候再运行程序:
最高消费1000元
最高消费1500元
———————————————————————————————————
类中仅有一个函数,那么就是一个空类,占1个字节
若类中有一个虚函数,那么占4个字节
虚函数-vfptr (虚函数表指针)指向vftable-(虚函数表)
当子类重写父类的虚函数
子类的虚函数表内部会替换成子类虚函数地址
利用普通和多态写的案例:
#include <iostream>
#include <string>
using namespace std;
//普通写法
class Calculator
{
public:
int getresult(string oper)
{
if(oper == "+")
{
return num1+num2;
}
if(oper == "-")
{
return num1-num2;
}
if(oper == "*")
{
return num1*num2;
}
return 0;
}
int num1;
int num2;
};
//普通的进行测试
void test()
{
Calculator c1;
c1.num1 = 100;
c1.num2 = 100;
cout<<c1.num1<<"+"<<c1.num2<<"="<<c1.getresult("+")<<endl;
cout<<c1.num1<<"-"<<c1.num2<<"="<<c1.getresult("-")<<endl;
cout<<c1.num1<<"*"<<c1.num2<<"="<<c1.getresult("*")<<endl;
}
//利用多态进行写
class AbstractCalculator
{
public:
virtual int getresult()
{
return 0;
}
int num1;
int num2;
};
//加法
class AddCalculator:public AbstractCalculator
{
public:
int getresult()
{
return num1 + num2;
}
};
//减法
class SubCalculator:public AbstractCalculator
{
public:
int getresult()
{
return num1 - num2;
}
};
//乘法
class MulCalculator:public AbstractCalculator
{
public:
int getresult()
{
return num1 * num2;
}
};
void test02()
{
//调用加法
AbstractCalculator *abs = new AddCalculator;//父类指针或者引用指向子类对象
abs->num1 = 100;
abs->num2 = 100;
cout<<abs->num1<<"+"<<abs->num2<<"="<<abs->getresult()<<endl;
delete abs; //手动进行释放
//调用减法
abs = new SubCalculator;
abs->num1 = 100;
abs->num2 = 100;
cout<<abs->num1<<"-"<<abs->num2<<"="<<abs->getresult()<<endl;
delete abs;
//调用乘法
abs = new MulCalculator;
abs->num1 = 100;
abs->num2 = 100;
cout<<abs->num1<<"-"<<abs->num2<<"="<<abs->getresult()<<endl;
delete abs;
}
int main()
{
test02();
system("pause");
return 0;
}
用多态写的好处是:有利于扩展,结构清晰,可读性强
———————————————————————————————————
纯虚函数
语法:virtual 返回值类型 函数名 (参数列表) = 0
若父类中含有一个纯虚函数,那么称为抽象类,无法进行实例化
同时子类中也无法实例化对象,也被称为抽象类
例子:
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
//纯虚函数
virtual void func() = 0;
};
class son:public Base
{
public:
virtual void func()
{
cout<<"子类中func的调用"<<endl;
}
};
void test()
{
//Base b1; //其中有纯虚函数,无法实例化
//new Base; //也无法实例化
Base *b = new son;
b->func();
delete b; //手动释放
}
int main()
{
test();
system("pause");
return 0;
}
———————————————————————————————————虚析构与纯虚析构
共同点:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构:virtual ~类名() {}
纯虚析构: virtual ~类名() = 0 (需要声明,也要有代码的实现)
能用到纯析构,是因为要在堆区开辟新数据,父类指针释放子类对象
例子:
#include <iostream>
#include <string>
using namespace std;
//纯析构和纯虚函数
class Animal
{
public:
Animal()
{
cout<<"Aniaml构造函数调用"<<endl;
}
//纯虚函数
virtual void speak() = 0;
//利用虚析构可以解决 父类指针释放子类对象时不干净问题
//纯虚析构 必须要有代码实现 需要声明也需要实现
//有纯虚析构 也无法进行实例化对象
virtual ~Animal() = 0;
//虚析构
/*virtual ~Animal()
{
cout<<"Aniaml析构函数调用"<<endl;
}*/
};
//类外进行代码实现
Animal::~Animal()
{
cout<<"Aniaml纯析构函数调用"<<endl;
}
class Cat:public Animal
{
public:
Cat(string name)
{
cout<<"Cat构造函数调用"<<endl;
m_Name = new string(name);
}
virtual void speak()
{
cout<<*m_Name<<"小猫在说话"<<endl;
}
~Cat()
{
if(m_Name != NULL)
{
cout<<"Cat虚构函数调用"<<endl;
delete m_Name; //释放内存
m_Name = NULL;
}
}
string *m_Name; //指针
};
void test01()
{
Animal *animal = new Cat("Tom"); //父类指针在析构时不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏存在
animal->speak();
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放类对象
2.如果子类中没有堆区数据,可以不写为虚析构式或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类,无法进行实例化