Polymorphism(多态)
广义的多态:不同类型的实体/对象对于同一消息有不同的响应,就是OOP中的多态性。
截止目前:多态性有两种表现的方式
1. 重载多态:
class C { public: int f(int x); int f(); };
2. 子类型多态:不同的对象调用同名重定义函数,表现出不同的行为
class A { virtual int f() { return 1; } }; class B : public A { virtual int f() { return 8; } }; A a; B b; A *p = &b; a.f(); // call A::f() b.f(); // call B::f() p->f(); // call B::f()
联编(Binding)
——确定具有多态性的语句调用哪个函数的过程。
static binding v.s. dynamic binding(动静态联编区别)
- 通过派生类对象访问同名函数,是静态联编
- 通过基类对象的指针访问同名函数,是静态联编
- 通过基类对象的指针或引用访问同名虚函数,是动态联编
1.Static Binding (静态联编)
在程序编译时(Compile-time)确定调用哪个函数 。例:函数重载
对象是什么类型,就调什么类型
class P { public: f() {… } }; //父类 class C : public P { public: f() {… } }; //子类 int main() { P p; C c; p.f(); //调用P::f() c.f(); //调用C::f() }
指针是什么类型,就调什么类型
class P { public: f() {… } }; //父类 class C : public P { public: f() {… } }; //子类 int main() { P *ptr; P p; C c; ptr = &p; ptr->f(); // 调用P::f() ptr = &c; ptr->f(); // 调用P::f() }
2.Dynamic Binding (动态联编)
在程序运行时(Run-time),才能够确定调用哪个函数
用动态联编实现的多态,也称为运行时多态(Run-time Polymorphism)。(一般我们研究动态多态)
Why Run-time Polymorphism (为何要使用运行时多态?)
实现运行时多态有两个要素:
- virtual function (虚函数)
- Override (覆写) : redefining a virtual function in a derived class. (在派生类中重定义一个虚函数)
struct A { virtual std::string toString() { return "A"; } };
override(显式声明覆写)
——C++11引入 override 标识符,指定一个虚函数覆写另一个虚函数。避免程序员在覆写时错命名或无虚函数导致隐藏bug
class A { public: virtual void foo() {} void bar() {} }; class B : public A { public: void foo() const override { // 错误: B::foo 不覆写 A::foo } // (签名不匹配) void foo() override; // OK : B::foo 覆写 A::foo void bar() override {} // 错误: A::bar 非虚 }; void B::foo() override { // 错误: override只能放到类内使用 }
final 显式声明禁止覆写
C++11引入final特殊标识符,指定派生类不能覆写虚函数
//struct可与class互换;差别在于struct的默认访问属性是public struct Base { virtual void foo(); }; struct A : Base { void foo() final; // A::foo 被覆写且是最终覆写 void bar() final; // 错误:非虚函数不能被覆写或是 final }; struct B final : A // struct B 为 final,不能被继承 { void foo() override; // 错误: foo 不能被覆写,因为它在 A 中是 final };
运行时多态用途
——可以用父类指针访问子类对象成员
调用哪个同名虚函数?
- 不由指针、引用类型决定;
- 而由指针所指的【实际对象】的类型决定,不看引用看真对象
- 运行时,检查指针所指对象类型和引用真对象的类型。
//函数虚,不看指针看真对象 class P { public: virtual f() {… } }; //父类 class C : public P { public: f() {… } }; //子类,f自动virtual int main() { P *ptr; P p; C c; ptr = &p; ptr->f(); //调用P::f() ptr = &c; ptr->f(); //调用C::f() } //函数虚,不看引用看真对象 class P { public: virtual f() {… } }; //父类 class C : public P { public: f() {… } }; //子类,f自动virtual int main() { P p; C c; P &pr1 = p; pr1.f(); //调用P::f() P &pr2 = c; pr2.f(); //调用C::f() }
虚函数的传递性
——基类定义了虚同名函数,那么派生类中的同名函数自动变为虚函数
虚函数缺点:
- 类中保存着一个Virtual function table (虚函数表)
- Run-time binding (运行时联编/动态联编)
- More overhead in run-time than non-virtual function (比非虚函数开销大)
#include#include <string> using std ::cout; using std ::endl; class A { public: virtual std ::string toString() { return "A"; } }; class B : public A { public: std ::string toString() override{ return "B"; } }; class C : public B { public: std ::string toString() override{ return "C"; } }; /*void print(A o) { cout << o.toString() << endl; } void print(B o) { cout << o.toString() << endl; } void print(C o) { cout << o.toString() << endl; }*/ void print(A *o) { cout << o->toString() << endl; } void print(A& o) { cout << o.toString() << endl; } int main() { /*A a; B b; C c; print(a); print(b); print(c);*/ A a; B b; C c; A *p1 = &a; A *p2 = &b; A *p3 = &c; print(p1); print(p2); print(p3); print(a); print(b); print(c); std::cin.get(); return 0; }
Abstract Classes (抽象类)
——类太抽象以至于无法实例化就叫做抽象类。
问题:Shape类层次中,getArea()函数放在哪个层次
选择1:放哪儿都行:Shape中或子类中定义getArea() 。
选择2:强制要求Shape子类必须实现getArea() 。
virtual double getArea() = 0; // 在Shape类中 Circle子类必须实现getArea() 纯虚函数才能实例化
Dynamic Cast(动态类型转换)
void printObject(Shape &shape) // shape是派生类对象的引用 { cout << "The area is " << shape.getArea() << endl; // 如果shape是Circle对象,就输出半径 // 如果shape是Rectangle对象,就输出宽高 }
dynamic_cast 运算符
- 沿继承层级向上、向下及侧向转换到类的指针和引用
- 转指针:失败返回nullptr
- 转引用:失败抛异常
void printObject(Shape &shape) { cout << "The area is " << shape.getArea() << endl; Shape *p = &shape; Circle *c = dynamic_cast(p); // Circle& c = dynamic_cast (shape); // 引用转换失败则抛出一个异常 std::bad_cast if (c != nullptr) // 转换失败则指针为空 { cout << "The radius is " << p1->getRadius() << endl; cout << "The diameter is " << p1->getDiameter() << endl; } }
upcasting (向上转换)
——Assigning a pointer of a derived class type to a pointer of its base class type (将派生类类型指针赋值给基类类型指针)
上转可不使用dynamic_cast而隐式转换
Shape *s = nullptr; Circle *c = new Circle(2); s = c; //OK,隐式上转
downcasting(向下转换)
——Assigning a pointer of a base class type to a pointer of its derived class type. (将基类类型指针赋值给派生类类型指针)
下转必须显式执行
Shape *s = new Circle(1); Circle *c = nullptr; c = dynamic_cast(s); //显式下转
-
可将派生类对象截断,只使用继承来的信息
-
但不能将基类对象加长,无中生有变出派生类对象
typeid(运行时查询类的信息)
- typeid returns a reference to an object of class type_info. (typeid运算符返回一个type_info对象的引用)
- typeid(AType).name() 返回实现定义的,含有类型名称的C风格字符串(char *)
#include//使用typeid,需要包含此头文件 #include using namespace std; class A { }; int main() { A a{}; // …… auto &t1 = typeid(a); if (typeid(A) == t1) { std::cout << "a has type " << t1.name() << std::endl; } system("pause"); return 0; }