多态
多态的基本概念
- 多态 c++面向对象三大特性之一
- 多态分为两类:
- 静态多态 : 函数重载和 运算符重载 属于静态多态 ,复用函数名
- 动态多态 : 派生类 和 虚函数 实现运行时多态
- 两者区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 多态多态的函数地址晚绑定 - 运行阶段确定函数地址
class animal **//此时占一个字节**
{
public :
void speak()
{
cout << "动物说话" << endl;
}
} ;
//猫
class cat :public animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
} ;
//执行说话的函数
//地址早绑定 显示 动物在说话 而非猫在说话
void dospeak(animal &animal) //animal &animal = cat
{
animal.speak();
}
void test01()
{
cat cat;
dospeak(cat);
}
如果想执行 猫在说话 ,那么这个地址不能提前绑定,需要在运行阶段绑定 改为如下
class animal **//此时占4个字节 一个指针大小**
{
public :
//虚函数
virtual void speak()
{
cout << "动物说话" << endl;
}
} ;
//狗
class dog :public animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
} ;
void test01()
{
//cat cat;
//dospeak(cat);
dog dog;
dospeak(dog);
}
动态多态满足条件:
1.有继承关系
2.子类需要重写父类中的虚函数 virtual
重写:函数返回值类型 函数名 参数列表 需要完全相同
动态多态使用:
父类的指针或者引用指向子类对象
Animal &animal = cat;
多态案例1 计算机类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机类
//普通实现
class calculator
{
public:
int getresult(string oper)
{
if (oper == "+")
{
return num1 + num2;
}
else if (oper == "-")
{
return num1 - num2;
}
else if (oper == "*")
{
return num1 * num2;
}
//如果想扩展新的功能,需要修改源码
//在真实开发中,提倡 开闭原则 即,对修改进行关闭,对扩展进行开发
int num1;
int num2;
};
void test01()
{//创建计算器对象
calculator c;
c.num1 = 10;
c.num2 = 10;
cout << c.num1 << "+" << c.num2 << " = " << c.getresult("+") << endl;
cout << c.num1 << "-" << c.num2 << " = " << c.getresult("-") << endl;
cout << c.num1 << "*" << c.num2 << " = " << c.getresult("*") << endl;
}
多态实现
// 利用多态实现计算器
//实现计算器抽象类
class abstractcalculator
{
public :
virtual int getresult()
{
return 0;
}
int num1;
int num2;
} ;
//加法计算器类
class addcalculator :public adstractcalculator
{
public :
int getresult
{
return num1 + num2;
}
} ;
//减法计算器类
class subcalculator :public adstractcalculator
{
public :
int getresult
{
return num1 - num2;
}
} ;
//乘法计算器类
class mulcalculator :public adstractcalculator
{
public :
int getresult
{
return num1 * num2;
}
} ;
void test02
{
//多态使用条件
//父类指针或者引用指向子类对象
//加法
adstractcalculator * abc = new addcalculatoro;
abc->num1 = 100;
abc->num2 = 100;
cout << abc-> num1 << "+" << abc->num2 << "=" << abc->getresult << endl;
// 记得销毁
delete abc;
//减法
abc = new subcalculator;
cout << abc-> num1 << "-" << abc->num2 << "=" << abc->getresult << endl;
// 记得销毁
delete abc;
//乘法
abc = new mulcalculator;
cout << abc-> num1 << "*" << abc->num2 << "=" << abc->getresult << endl;
// 记得销毁
delete abc;
}
好处:
1.组织结构清晰 2.可读性强 3.对于前期和后期的扩展,可维护性高
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为 纯虚函数
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表) = 0;
- 当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类.
//纯虚函数和抽象类
class base
{
public:
//纯虚函数
virtual void func() = 0;
};
class son:public base
{
public:
virtual void func() {}; // 子类必须重写父类 中纯虚函数 ,哪怕是空
};
void test01()
{
//1.无法实例化对象
//base b; 错误
// new base; 错误
son s; //子类必须重写父类 中纯虚函数 ,否则无法实例化
base *base = new son;
base->func();
}
多态的目的就是 通过一个父类指针,由于创建对象不同,可以调用多种形态的函数
案例2 制作饮料
利用多态技术实现,提供抽象制作饮料基类.提供子类制作咖啡和茶叶
class adstractdrinking
{
public:
//煮水
virtual void boil() = 0;
//冲泡
virtual void brew() = 0;
//倒杯
virtual void pour() = 0;
//加料
virtual void putsomething() = 0;
//制作饮料
void makedrinking()
{
boil();
brew();
pour();
putsomething();
}
} ;
//制作咖啡
class coffee :public adstractdrinking
{
public :
//煮水
virtual void boil()
{
cout << "农夫山泉" << endl;
}
//冲泡
virtual void brew()
{
cout << "冲泡咖啡" << endl;
}
//倒杯
virtual void pour()
{
cout << "倒入杯中" << endl;
}
//加料
virtual void putsomething()
{
cout << "加入方糖" << endl;
}
};
//制作茶叶
class teacoffee :public adstractdrinking
{
public :
//煮水
virtual void boil()
{
cout << "矿泉水" << endl;
}
//冲泡
virtual void brew()
{
cout << "冲泡茶叶" << endl;
}
//倒杯
virtual void pour()
{
cout << "倒入杯中" << endl;
}
//加料
virtual void putsomething()
{
cout << "加入枸杞" << endl;
}
};
//制作
void dowork(adstractdrinking *abc) //adstractdrinking * abc = new coffee /tea
{
abc-> makedrink(); // 同一个接口
delete abc;
}
void test01 ()
{
//咖啡
dowork(new coffee) ;
//茶叶
dowork(new tea) ;
}
int main()
{
test01();
}
虚析构和纯虚函数
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码.
解决方式: 将父类中析构函数改为虚函数/纯虚函数
-
虚函数和纯虚函数共性 :
-
可以解决父类指针释放子类对象
-
都需要有具有的函数实现
-
虚析构和纯虚析构区别:
-
如果是纯虚函数,该类属于抽象类,无法实例化对象
-
虚析构语法:.
virtual ~类名(){}
*纯虚析构语法
virtual ~类名()=0;
类名::~类名(){}
class animal
{
public:
animal()
{
cout << "animal构造函数" << endl;
}
~animal()
{
cout << "animal析构函数" << endl;
}
//纯虚函数
virtual void speak() = 0;
} ;
class cat :public animal
{
public :
cat(string n)
{
cout << "cat构造函数" << endl;
name = new string (n);
}
virtual void speak()
{
cout << *name << "小猫在说话" << endl;
}
~cat()
{
if(name != null)
{
cout << "cat析构函数" << endl;
delete name;
name = null;
}
}
string *name;
} ;
void test01()
{
animal *animal = new cat("tom");
animal->speak();
//父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆属性,会出现内存泄漏
delete animal;
}
此时运行结果:
animal构造函数
cat构造函数
tom在说话
animal析构函数调用
cat析构函数呢???
父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆属性,会出现内存泄漏
解决方法: 父类析构改为虚析构
virtual ~animal()
{
cout << "animal析构函数" << endl;
}
//或者用纯虚析构 声明 + 实现,
class animal
{
public:
animal()
{
cout << "animal构造函数" << endl;
}
//纯虚析构
virtual ~animal = 0;
//纯虚函数
virtual void speak() = 0;
} ;
animal ::~animal()
{
cout << "animal纯虚析构函数" << endl;
}
此时运行结果:
animal构造函数
cat构造函数
tom在说话
cat析构函数
animal(纯)析构函数调用
利用虚析构可以解决 父类指针释放子类对象时不干净的问题
总结:
1.虚析构或者纯虚析构就是用来解决父类指针释放子类对象
2.如果子类中没堆区数据,可以不写为虚析构 纯虚析构
3.拥有纯虚析构的类也属于抽象类