类与对象——Polymorphism(多态)

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. 通过派生类对象访问同名函数,是静态联编
  2. 通过基类对象的指针访问同名函数,是静态联编
  3. 通过基类对象的指针或引用访问同名虚函数,是动态联编

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 (为何要使用运行时多态?)

类与对象——Polymorphism(多态)_第1张图片类与对象——Polymorphism(多态)_第2张图片

 实现运行时多态有两个要素:

  1. virtual function (虚函数)
  2. 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
};

运行时多态用途

——可以用父类指针访问子类对象成员

调用哪个同名虚函数?

  1.  不由指针、引用类型决定;
  2.  而由指针所指的【实际对象】的类型决定,不看引用看真对象
  3.  运行时,检查指针所指对象类型和引用真对象的类型。
//函数虚,不看指针看真对象
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()
}

虚函数的传递性

——基类定义了虚同名函数,那么派生类中的同名函数自动变为虚函数

虚函数缺点:

  1. 类中保存着一个Virtual function table (虚函数表)
  2. Run-time binding (运行时联编/动态联编)
  3. 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 (抽象类)

——类太抽象以至于无法实例化就叫做抽象类。

类与对象——Polymorphism(多态)_第3张图片

问题:Shape类层次中,getArea()函数放在哪个层次

选择1:放哪儿都行:Shape中或子类中定义getArea() 。

选择2:强制要求Shape子类必须实现getArea() 。

virtual double getArea() = 0; // 在Shape类中

Circle子类必须实现getArea() 纯虚函数才能实例化

Dynamic Cast(动态类型转换)

类与对象——Polymorphism(多态)_第4张图片

void printObject(Shape &shape)
// shape是派生类对象的引用
{
    cout << "The area is " << shape.getArea() << endl;
    // 如果shape是Circle对象,就输出半径
    // 如果shape是Rectangle对象,就输出宽高
}

dynamic_cast 运算符

  1.      沿继承层级向上、向下及侧向转换到类的指针和引用
  2.      转指针:失败返回nullptr
  3.      转引用:失败抛异常
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); //显式下转
  1. 可将派生类对象截断,只使用继承来的信息

  2. 但不能将基类对象加长,无中生有变出派生类对象

类与对象——Polymorphism(多态)_第5张图片

typeid(运行时查询类的信息)

  1.     typeid returns a reference to an object of class type_info. (typeid运算符返回一个type_info对象的引用)
  2.     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;
}

 

你可能感兴趣的:(类与对象——Polymorphism(多态))