1. 浅析多态的意义
多态现象:如果有几个类似而不完全相同的对象,有时人们要求在向他们发出同一个消息时,他们的反应各不相同,分别执行不同的操作。这种情况就是多态现象。
C++中所谓的多态:指的是继承而产生的相关的不同类,其对象对同一消息会做出不同的响应
多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性,可以减轻系统升级、调试和维护的工作量和复杂度。
2. 赋值兼容(多态实现的前提)
2.1. 规则
赋值兼容规则是指在需要基类的任何地方都可以使用公有派生类的对象来替代,赋值兼容是一种默认行为,不需要任何的显示转化
赋值兼容规则中所指的替代包括以下情况:
- 派生类的对象可以赋值给基类对象
- 派生类的对象可以初始化基类的引用
- 派生类对象的地址可以赋给指向基类的指针
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员
2.2. 代码
class Shape
{
public:
Shape(int x, int y) : _x(x), _y(y)
{
}
void draw()
{
cout << "draw shape" << endl;
cout << "start (" << _x << "," << _y << ")" << endl;
}
protected:
int _x;
int _y;
};
class Circle : public Shape
{
public:
Circle(int x, int y, int r) : Shape(x, y), _r(r)
{
}
void draw()
{
cout << "draw circle" << endl;
cout << "start (" << _x << "," << _y << ")" << endl;
cout << "radius = " << _r << endl;
}
private:
int _r;
};
int main()
{
Shape s(3, 5);
s.draw();
Circle c(6, 7, 8);
c.draw();
s = c;
s.draw();
Shape &rs = c;
rs.draw();
Shape *ps = &c;
ps->draw();
return 0;
}
2.3. 补充
父类也可以通过强转的方式转化为子类,父类对象强转为子类对象后,访问从父类继承下来的部分是可以的,但访问子类的部分,则会发生越界的风险,越界的结果是未知的。
// c = static_cast<Circle>(s); // 缺少转化函数
// c.draw();
Circle *pc = static_cast<Circle*>(&s);
pc->draw();
3. 多态形成的条件
3.1. 多态
3.1.1. 静多态
函数重载,也是一种多态现象,通过命名倾轧在编译阶段决定,故称为静多态
3.1.2. 动多态
动多态,不是在编译阶段决定,而是在运行阶段决定,故称为动多态,动多态形成的条件如下:
- 父类中有虚函数
- 子类override(覆写)父类中的虚函数
- 通过已被子类对象赋值的父类指针或引用,调用共用接口
3.2. 虚函数
- 格式
class 类名
{
virtual 函数声明;
};
- 例举
Shape类中
virtual void draw() {
cout << "draw Shap ";
cout << "start (" << _x << "," << _y << ")" << endl;
}
Circle类中
void draw() {
cout << "draw Circle " << endl;
cout << "start (" << _x << "," << _y << ")" ;
cout << "radio r = " << _r << endl;
}
Rect类中
void draw()
{
cout << "draw Rect" << endl;
cout << "start (" << _x << "," << _y << ")";
cout << "len = " << _len << " wid = " << _wid << endl;
}
- 测试
int main()
{
Circle c(1, 2, 4);
c.draw();
Rect r(2, 3, 4, 5);
r.draw();
Shape *ps;
int choice;
while (1) // 真正的实现了动多态,在运行阶段决定
{
scanf("%d", &choice);
switch (choice)
{
case 1:
ps = &c;
ps->draw();
break;
case 2:
ps = &r;
ps->draw();
break;
}
}
return 0;
}
3.3. 虚函数小结
- 在基类中用virtual声明成员函数为虚函数。类外实现虚函数时,不必再加virtual
- 在派生类中重新定义此函数称为覆写,要求函数名,返回值类型,函数参数个数及类型全部匹配,并根据派生类的需要重新定义函数体
- 当一个成员函数被声明为虚函数后,其派生类中完全相同的函数(显示的写出)也为虚函数,可以在前加virtual以示清晰
- 定义一个指基类对象的指针,并使其指向其子类的对象,通过该指针调用虚函数,此时调用的就是指针变量指向对象的同名函数
- 子类中的覆写的函数,可以为任意访问类型,依子类需求决定
class Base
{
public:
virtual void func()
{
cout << "Base func" << endl;
}
};
class Derived : public Base
{
private:
void func()
{
cout << "Derived func" << endl;
}
};
int main()
{
Derived d;
// d.func();
Base *p = &d;
p->func();
return 0;
}
3.4. 纯虚函数
- 格式
class 类名
{
virtual 函数声明 = 0;
};
- 例举
Shape类中
virtual void draw() = 0;
Circle类中
void draw() {
cout << "draw Circle" << endl;
cout << "start (" << _x << "," << _y << ")" ;
cout << "radio r = " << _r << endl;
}
- 测试
int main()
{
// Shape s(1,2); // 函数纯虚函数的类称为抽象基类
Circle c(1, 2, 3);
Rect r(1, 2, 3, 4);
Shape *pc = &c;
pc->draw();
pc = &r;
pc->draw();
return 0;
}
3.5. 纯虚函数小结
- 含有纯虚函数的类,成为抽象基类,不可实例化,即不能创建对象,存在的意义就是被继承,提供族类的公共接口,java中成为interface
- 纯虚函数只有声明,没有实现,被初始化为0
- 如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类
3.6. 含有虚函数的析构
含有虚函数的类,析构函数也应该声明为虚函数。在delete父类指针的时候,会调用子类的析构函数,实现完整析构。
当一个类中有虚函数时,请将析构函数一并virtual
animal.h
#ifndef ANIMAL_H
#define ANIMAL_H
class Animal
{
private:
public:
Animal();
// 虚析构:为了析构完全
virtual ~Animal();
virtual void voice() = 0;
};
#endif
animal.cpp
#include "animal.h"
#include <iostream>
using namespace std;
Animal::Animal()
{
cout << "Animal" << endl;
}
Animal::~Animal()
{
cout << "Animal de" << endl;
}
dog.h
#ifndef DOG_H
#define DOG_H
#include "Animal.h"
class Dog : public Animal
{
private:
public:
Dog();
~Dog();
virtual void voice();
};
#endif
dog.cpp
#include "dog.h"
#include <iostream>
using namespace std;
Dog::Dog()
{
cout << "Dog" << endl;
}
Dog::~Dog()
{
cout << "Dog de" << endl;
}
void Dog::voice()
{
cout << "voice dog" << endl;
}
cat.h
#ifndef CAT_H
#define CAT_H
#include "animal.h"
class Cat : public Animal
{
private:
public:
Cat(/* args */);
~Cat();
virtual void voice();
};
#endif
cat.cpp
#include "cat.h"
#include <iostream>
using namespace std;
Cat::Cat()
{
cout << "Cat" << endl;
}
Cat::~Cat()
{
cout << "Cat de" << endl;
}
void Cat::voice()
{
cout << "voice cat" << endl;
}
main.cpp
#include "dog.h"
#include "cat.h"
int main()
{
#if 0
Dog dog;
dog.voice();
Cat cat;
cat.voice();
Animal *ani = &dog;
ani->voice();
ani = &cat;
ani->voice();
#endif
Animal *ani = new Dog;
ani->voice();
delete ani;
return 0;
}
3.7. 若干限制
- 只有类的成员函数才能声明为虚函数
虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数 - 静态成员函数不能是虚函数
静态成员函数不受对象的捆绑,只有类的信息 - 内敛函数不能是虚函数
- 构造函数不能是虚函数
构造时,对象的创建尚未完成。构造完成后,才能算一个名副其实的对象 - 析构函数可以是虚函数且通常声明为虚函数
3.8. 依赖倒置
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
class IReader
{
public:
virtual string getContents() = 0;
};
class Book : public IReader
{
public:
virtual string getContents()
{
return "content";
}
};
class NewsPaper : public IReader
{
public:
virtual string getContents()
{
return "news";
}
};
class Mother
{
public:
void tellStory(IReader *i)
{
cout << i->getContents() << endl;
}
};
int main()
{
Book b;
NewsPaper p;
Mother m;
m.tellStory(&b);
m.tellStory(&p);
return 0;
}