一. 多态的分类
① 两种多态的实现方式
静态多态: 函数重载和运算符重载属于静态多态,复用函数名.动态多态: 派生类和虚函数实现运行时多态
② 静态多态和动态多态的区别
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
二. 多态的实现方式
使用基类的引用或者指针指向子类的对象,可以调用子类的函数,就是实现通过基类指针或者引用根据指针的指向来动态的确定具体的子类的行为.
动态多态的满足条件
- 有继承关系
- 子类要重写父类的虚函数(函数名,函数参数列表,返回值都要相同)
- 父类的指针或者引用指向子类对象.
普通的静态绑定的函数例子(非虚函数),不会实现多态,地址早绑定,在编译阶段都根据类型确定好了
#include <iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话!" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话!" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
int main()
{
Animal animal;
Dog dog;
doSpeak(animal);
doSpeak(dog);
system("pause");
return 0;
}
上面的例子说明,普通的函数在调用的时候,地址是早绑定的,也就是说,无论你传入的对象是基类还是子类,都会按照函数定义的时候那个类型去调用同一个函数,如果实现动态绑定呢,只需要在函数前面加上virtual,告诉编译器这个函数是要动态绑定的,在运行的时候根据引用的类型或者指向的类型去确定调用哪个函数
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
virtual void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
virtual void speak()
{
cout << "小狗在说话" << endl;
}
};
class Cat :public Animal
{
public:
virtual void speak()
{
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
int main()
{
Animal animal = Animal();
doSpeak(animal);
Dog dog = Dog();
doSpeak(dog);
Cat cat = Cat();
doSpeak(cat);
system("pause");
return 0;
}
结果:
三. 多态的原理剖析
① 普通的类占用的内存空间大小(没有成员变量,只有成员函数)
#include <iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话!" << endl;
}
};
int main()
{
Animal animal = Animal();
cout << "空类或者是没有成员变量的类占用的内存空间 = " << sizeof(animal) << endl;
system("pause");
return 0;
}
结果:
② 含有虚函数的类的占用内存空间大小
- 含有虚函数的时候,类占用的内存空间,除了成员变量还有一个虚函数指针,如果是x86是4个字节,如果是64位操作系统占用的是8个字节.
- 虚函数指针,指向了虚函数表,虚函数表里面存放的是虚函数的地址(也就是函数指针).
- 虚函数指针一般用
vfptr 表示,虚函数表一般用vftable 表示.
#include <iostream>
using namespace std;
class Animal
{
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
int main()
{
Animal animal = Animal();
int *p;
cout << "指针占用的内存大小: " << sizeof(p) << endl;
cout << "含有虚函数的类占用的内存空间大小: " << sizeof(animal) << endl;
system("pause");
return 0;
}
结果:
四. 多态带来的好处
- 代码组织结构清晰
- 可读性强
- 利于代码的扩展和维护
- 符合开闭原则(对修改关闭,对扩展开放)
举个例子,写一个计算数值的计算器类,可以实现加减的操作.
#include <iostream>
#include <string>
using namespace std;
class Calculator
{
public:
Calculator(int a, int b):mNumber1(a),mNumber2(b)
{
}
int getResult(string op)
{
if (op == "+")
{
return mNumber1 + mNumber2;
}
else if (op == "-")
{
return mNumber1 - mNumber2;
}
}
public:
int mNumber1;
int mNumber2;
};
int main()
{
Calculator cal = Calculator(10, 20);
string op = "+";
cout << cal.mNumber1 << op << cal.mNumber2 << "=" << cal.getResult(op) << endl;
op = "-";
cout << cal.mNumber1 << op << cal.mNumber2 << "=" << cal.getResult(op) << endl;
system("pause");
return 0;
}
如果现在我们想实现乘除操作,就不得不修改原来的代码,在后面加额外的额分支.如果使用多态的技术,就不用修改原来的代码, 可以在原来的基础上使用.并且在使用的时候也可以用同一个接口去调用.
#include <iostream>
#include <string>
using namespace std;
class AbstractCalculator
{
public:
virtual int get_result() = 0;
public:
int mNumber1;
int mNumber2;
};
class Add :public AbstractCalculator
{
public:
Add(int a, int b)
{
mNumber1 = a;
mNumber2 = b;
}
int get_result()
{
return mNumber1 + mNumber2;
}
};
class Sub :public AbstractCalculator
{
public:
Sub(int a, int b)
{
mNumber1 = a;
mNumber2 = b;
}
int get_result()
{
return mNumber1 - mNumber2;
}
};
class Mul :public AbstractCalculator
{
public:
Mul(int a, int b)
{
mNumber1 = a;
mNumber2 = b;
}
int get_result()
{
return mNumber1 * mNumber2;
}
};
class Div :public AbstractCalculator
{
public:
Div(int a, int b)
{
mNumber1 = a;
mNumber2 = b;
}
int get_result()
{
return mNumber1 / mNumber2;
}
};
int main()
{
AbstractCalculator *cal = new Add(10,20);
cout << cal->mNumber1 << "+" << cal->mNumber2 << "=" << cal->get_result() << endl;
delete cal;
cal = NULL;
cal = new Sub(10, 20);
cout << cal->mNumber1 << "-" << cal->mNumber2 << "=" << cal->get_result() << endl;
delete cal;
cal = NULL;
cal = new Mul(10, 20);
cout << cal->mNumber1 << "*" << cal->mNumber2 << "=" << cal->get_result() << endl;
delete cal;
cal = NULL;
cal = new Div(20, 10);
cout << cal->mNumber1 << "/" << cal->mNumber2 << "=" << cal->get_result() << endl;
delete cal;
cal = NULL;
system("pause");
return 0;
}
可以看出来,虽然多态的代码量变多了,但是逻辑和扩展上更容易扩展.结构上也更加的清晰,也很容易理解.
五. 纯虚函数和抽象类
① 纯虚函数的定义
- 虚函数在后面加上=0,这个函数就变成了纯虚函数.
语法: virtual void somefunction(void) = 0
② 纯虚函数的意义
- 在多态中,通常父类中的虚函数是毫无意义的,主要是调用子类重写的函数.
- 定义成纯虚函数,则其派生类,必须实现这个函数,就是派生类都必须实现自己行为,不然就会报错.
- 一旦一个类中有了纯虚函数,这个类就变成了抽象类,这个类就不能实例化,并且子类必须重写父类的纯虚函数,否在子类也是一个抽象类,也不能实例化.
- 如果定了纯虚函数,目的就是让子类去实现对这个函数的重写.
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Derived :public Base
{
virtual void func()
{
cout << "Derived:: func() 调用 !" << endl;
}
};
int main()
{
Base *b = new Derived;
b->func();
delete b;
b = NULL;
system("pause");
return 0;
}
六 . 多态的案例
#include <iostream>
using namespace std;
class AbstractDrinking
{
public:
virtual void boil() = 0;
virtual void brew() = 0;
virtual void pour_in_cup() = 0;
virtual void put_something() = 0;
void make_drink()
{
boil();
brew();
pour_in_cup();
put_something();
}
};
class Coffee :public AbstractDrinking
{
virtual void boil()
{
cout << "煮农夫山泉" << endl;
}
virtual void brew()
{
cout << "冲泡咖啡" << endl;
}
virtual void pour_in_cup()
{
cout << "倒入杯中" << endl;
}
virtual void put_something()
{
cout << "加入牛奶和糖" << endl;
}
};
class Tea :public AbstractDrinking
{
virtual void boil()
{
cout << "煮圣水" << endl;
}
virtual void brew()
{
cout << "冲泡茶叶" << endl;
}
virtual void pour_in_cup()
{
cout << "倒入杯中" << endl;
}
virtual void put_something()
{
cout << "加入枸杞和菊花" << endl;
}
};
void doWork(AbstractDrinking *abs)
{
abs->make_drink();
delete abs;
abs = NULL;
}
int main()
{
doWork(new Coffee);
cout << "==================================" << endl;
doWork(new Tea);
system("pause");
return 0;
}
七. 虚析构和纯虚析构
① 为什么需要虚析构和纯虚析构
- 在使用多态的时候,如果子类中有属性开辟到了堆区,那么父类指针释放的时候无法调用到子类的析构代码.
- 可以将父类的析构函数改为虚析构或者纯虚析构.
② 虚析构和纯虚析构的作用
- 可以解决父类指针释放子类对象
- 如果是纯虚析构,则父类无法实例化对象
③ 虚析构和纯虚析构的必要性
下面这个例子没有用到虚析构和纯虚析构,然后看看会有什么问题?
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal() 构造函数调用!" << endl;
}
virtual void speak() = 0;
~Animal()
{
cout << "Animal() 析构函数被调用!" << endl;
}
};
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat() 构造函数调用!" << endl;
mName = new string(name);
}
virtual void speak()
{
cout << *mName << "猫在说话!" << endl;
}
~Cat()
{
cout << "Cat() 析构函数调用!" << endl;
if (mName != NULL)
{
delete mName;
mName = NULL;
}
}
public:
string *mName;
};
int main()
{
Animal *animal = new Cat("汤姆");
animal->speak();
delete animal;
animal = NULL;
system("pause");
return 0;
}
结果: 解析
- 在释放父类指针的时候,只有父类的析构函数被调用.
- 子类有变量分配到了堆区,但是内存并没有被释放,引发内存泄漏.
解决方案
- 将父类的析构函数定义为虚析构或者是纯虚析构
- 子类重写析构函数
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal() 构造函数调用!" << endl;
}
virtual void speak() = 0;
virtual ~Animal()
{
cout << "Animal() 析构函数被调用!" << endl;
}
};
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat() 构造函数调用!" << endl;
mName = new string(name);
}
virtual void speak()
{
cout << *mName << "猫在说话!" << endl;
}
virtual ~Cat()
{
cout << "Cat() 析构函数调用!" << endl;
if (mName != NULL)
{
delete mName;
mName = NULL;
}
}
public:
string *mName;
};
int main()
{
Animal *animal = new Cat("汤姆");
animal->speak();
delete animal;
animal = NULL;
system("pause");
return 0;
}
结果: 纯虚析构
- 纯虚析构在类外也要实现它
- 如果一个类有了纯虚析构,则这个类就是抽象类.
|