一. 多态
1. 多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类:
静态多态:函数重载和运算符重载属于静态多态,复用函数名。
动态多态:派生类和虚函数实现运行时多态。
静态多态和动态多态区别:
1)静态多态的函数地址早绑定——编译阶段确定函数地址
2)动态多态的函数地址晚绑定——运行阶段确定函数地址
class Animal
{
public:
void speak()
{
cout<<"动物在说话"<<endl;
}
};
class Cat::public Animal
{
public:
void speak()
{
cout<<"小猫在说话"<<endl;
}
};
/*3. 原因:地址早绑定,在编译阶段就确定了函数地址,不管传入什么动物,
都会走animal里面的speak函数*/
void Dospeak (Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat; //1.想执行的是“小猫在说话”
Dospeak(cat);
}
int main()
{
test01(); //2. 但实际执行“动物在说话”
}
/*4. 如果想执行让猫说话,那么这个函数地址就不能提前绑定,
需要在运行阶段进行绑定,地址晚绑定*/
修改如下:
class Animal
{
public:
virtual void speak() /*函数前面加上virtual关键字,
变成虚函数,那么编译器在编译的时候就不能确定函数调用了*/
{
cout<<"动物在说话"<<endl;
}
};
class Cat::public Animal
{
public:
void speak() /*重写,函数返回值类型、函数名、参数、
列表完全相同,virtual void speak();
其中,virtual关键字在子类重写函数中可写可不写*/
{
cout<<"小猫在说话"<<endl;
}
};
void Dospeak (Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
Dospeak(cat);
}
int main()
{
test01(); //输出的是“小猫在说话”
}
动态多态满足条件: 1)有继承关系; 2)子类重写父类虚函数;
动态多态使用条件:父类指针或引用指向子类对象 (Animal & animal =cat)
2. 多态的原理剖析
当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址;
到底什么是C++的多态?
当父类的指针或者引用指向子类对象时候,发生多态
Animal & animal =cat; /*由于指向的是cat对象,
会从Cat类中的虚函数表中找这个函数*/
animal.speak();
3. 多态案例-计算机类
案例描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机类。
#include<iostream>
#include<string>
using namespace std;
class Calculator
{
public:
virtual int getresult()
{
return 0;
}
public:
int num1;
int num2;
};
class Add :public Calculator
{
public:virtual int getresult()
{
return num1 + num2;
}
};
class Mul :public Calculator
{
public:
virtual int getresult()
{
return num1 * num2;
}
};
void test01()
{
/*Add add;
add.num1 = 10;
add.num2 = 20;
cout << add.num1 << "+" << add.num2 << "="
<< add.getresult() << endl;*/
Calculator* abc = new Add;/*new Add相当于创建了一个子类的对象;
此处是父类的指针指向了子类的对象*/
abc->num1 = 10;
abc->num2 = 20;
cout << abc->num1 << "+" << abc->num2 << "="
<< abc->getresult() << endl;
delete abc;
}
int main()
{
test01();
system("pause");
return 0;
}
多态的优点:
1)代码组织结构清晰
2)可读性强
3)利于前期和后期的扩展以及维护
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
4. 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改写为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
1)无法实例化对象;
2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
virtual void func()=0;
};
class Son: public Base
{
public:
virtual void func()
{
cout<<"func调用"<<endl;
}
};
void test01()
{
//Base b ; 抽象类是无法实例化对象
//new Base; 抽象类是无法实例化对象
Son s; //子类必须重写父类中的纯虚函数,否则无法实例化对象
Base *base=new Son;
base->func();
/*其实多态的目的就是让函数的接口更加通用化,通过一个父类指针,
由于你创建的对象不同,指向对象不同,可以调用多种确切的函数*/
}
int main()
{
... ...
}
5.虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1)可以解决父类指针释放子类对象;
2)都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法: virtual ~类名(){ }
纯虚析构语法:(需要声明也需要实现)virtual ~ 类名() = 0;类名::~ 类名() { }
class Animal
{
public:
virtual void speak()=0; //纯虚函数
Animal()
{
cout<<"Animal构造函数调用"<<endl;
}
~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;
}
~Cat ()
{
if(m_Name!=NULL)
{
cout<<"Cat析构函数调用"<<endl;
delete m_Name;
m_Name=NULL;
}
}
string *m_Name;
};
void test()
{
Animal *animal=new Cat("Tom");
animal->speak();
delete animal;
/*父类指针在析构时,不会调用子类中析构函数,导致子类如果有堆区属性,
出现内存泄漏*/
}
int main() {... ...}
打印结果为:
Animal构造函数调用
Cat构造函数调用
Tom小猫在说话
Animal析构函数调用
没有“Cat析构函数调用”,说明堆区的数据没有释放干净,造成内存泄漏
解决办法,把父类的析构函数~Animal(){... ...}
改为virtual ~Animal() {... ...}即可
利用虚析构可以解决父类指针释放子类对象时不干净的问题
纯虚析构和虚析构只需要有一个
如果子类中没有堆区数据,可以不写为虚析构或者纯虚析构