目录
多态是C++面向对象三大特性之一
多态的分类
- 静态多态
-函数重载 和运算符重载属于静态多态,复用函数名
-函数地址早绑定-编译阶段确定函数地址
- 动态多态
-派生类和虚函数实现运行时多态
-函数地址晚绑定-运行阶段确定函数地址
动态多态满足条件:
1.继承关系
2.子类要重写(函数返回值类型、函数名、参数列表完全相同)父类的虚函数
动态多态的使用:
父类的指针或者引用 指向 子类的对象
cpp
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//地址早绑定-编译阶段确定函数地址
//如果想执行让猫说话,就要进行晚绑定,使用虚函数
void dospeak(Animal& animal)
{
animal.speak();
}
void test(void)
{
Cat cat;
dospeak(cat);
Dog dog;
dospeak(dog);
}
int main(void)
{
test();
return 0;
}
多态的原理剖析
父类内部结构 : vfptr-虚函数(表)指针 指向 vftable(表内记录虚函数的地址)
当子类重写父类的虚函数:子类中的虚函数表内部会替换成子类的虚函数地址
当父类的指针或者引用指向子类对象时,发生多态。
多态的优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展和维护
- (开闭原则)
cpp
#include <iostream>
#include <string>
using namespace std;
//举例实现一个简易的计算器
class AbstractCalulator
{
public:
//虚函数
virtual int getResult()
{
return 0;
}
int n_number;
int m_number;
};
class AddCalculator :public AbstractCalulator
{
public:
int getResult()
{
return n_number + m_number;
}
};
class SubCalculator :public AbstractCalulator
{
public:
int getResult()
{
return n_number - m_number;
}
};
class MulCalculator :public AbstractCalulator
{
public:
int getResult()
{
return n_number * m_number;
}
};
void test(void)
{
AbstractCalulator* abc = new AddCalculator;
abc->n_number = 10;
abc->m_number = 10;
cout << abc->n_number << "+" << abc->m_number << "=" << abc->getResult() << endl;
delete abc;
abc = new SubCalculator;
abc->n_number = 10;
abc->m_number = 10;
cout << abc->n_number << "-" << abc->m_number << "=" << abc->getResult() << endl;
delete abc;
abc = new MulCalculator;
abc->n_number = 10;
abc->m_number = 10;
cout << abc->n_number << "*" << abc->m_number << "=" << abc->getResult() << endl;
delete abc;
}
int main(void)
{
test();
return 0;
}
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类被称为 抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
cpp
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Son:public Base
{
public:
void func()
{
cout << "Son 's func()" << endl;
}
};
void test(void)
{
//Base base; 抽象类无法实例化对象
//new Base; 抽象类无法实例化对象
Base * base = new Son;
base->func();
}
int main(void)
{
test();
return 0;
}
虚析构和纯虚析构
如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码导致内存泄漏 ,解决办法是将父类的析构函数改为虚析构 或者纯虚析构;
虚析构和纯虚析构的共性:
- 可以解决父类指针释放子类对象
- 都需要具体的函数实现
虚析构和纯虚析构的区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0; 类名::~类名(){}
cpp
#include <iostream>
#include <string>
using namespace std;
class AbstractAnimal
{
public:
AbstractAnimal()
{
cout << "AbstractAnimal 构造函数调用" << endl;
}
//virtual ~AbstractAnimal()
//{
// cout << "AbstractAnimal 析构函数调用" << endl;
//}
virtual ~AbstractAnimal() = 0;
virtual void speak() = 0;
};
AbstractAnimal::~AbstractAnimal()
{
cout << "AbstractAnimal 析构函数调用" << endl;
}
class Dog :public AbstractAnimal
{
public:
Dog(string name)
{
cout << "Dog 构造函数调用" << endl;
m_name = new string(name);
}
~Dog()
{
if (m_name != NULL)
{
cout << "Dog 析构函数调用" << endl;
delete m_name;
m_name = NULL;
}
}
void speak()
{
cout << *m_name << " Dog speaking" << endl;
}
string* m_name;
};
void test(void)
{
AbstractAnimal* abs = new Dog("Tom");
abs->speak();
delete abs;
}
int main(void)
{
test();
return 0;
}