C++之多态

一、什么是多态

1.1如何使用

        多态,简单来说,就是不同类型的对象调用同一个接口,表现出不同的行为。主要通过继承和虚函数的机制来实现。

class A {
public:
	virtual void f1() {
		cout << "A::f1()" << endl;
	}
};

class B : public A {
public:
	virtual void f1() {
		cout << "B::f1()" << endl;
	}
};

void Test(A& tmp) {
	tmp.f1();
}

int main(void) {
	A a;
	B b;
	Test(a);
	Test(b);
	return 0;
}

1.2构成条件 

        要构成多态需要几个条件:

  1. 必须存在一个父类和至少一个子类。
  2. 在父类中使用virtual将成员函数声明为虚函数,同时,在子类中重写父类中的虚函数。重写,就是子类中的函数与父类中的虚函数具有相同的函数名、参数列表和返回类型(除了协变返回类型的情况)。
  3.  为了实现多态,必须通过父类的指针或引用来调用虚函数。

1.3协变 

        这里需要注意什么是协变, 就是除了返回值的类型不同,函数名、参数列表要相同。同时,对协变的返回类型还有要求,父类虚函数的返回值必须是一个父类指针或引用,子类的虚函数的返回值必须是父类虚函数返回值父类对应的子类的指针或引用。

        作者在这里实验过,如下:

class OtherBase {};  // 与Base无关的类
class OtherDerived : public OtherBase {};

class Base {
public:
    virtual OtherBase* clone() const { 
        cout << "Base" << endl;
        return new OtherBase;
    }
};

class Derived : public Base {
public:
    OtherDerived* clone() const override {
        cout << "Derived" << endl;
        return new OtherDerived();
    }
};

void func(Base& tmp) {
    auto a = tmp.clone();
    delete a;
}

int main(void) {
    Base a;
    Derived b;
    func(a);
    func(b);
    return 0;
}

        可以正确的跑出多态,因此返回的类不一定非要和当前类有关联。 

二、多态的底层原理

2.1虚函数表指针和虚函数表

        首先,最重要的就是虚函数表这个概念,我们可以通过监视窗口查看:

        在上面的案例中,对象a包含了一个叫_vfptr的东西,这个就是指向虚表的指针,而这个指针,也可以通过监视窗口的 void(A::'vftable'[2])() 看出来这其实是一个函数指针数组,而数组里的函数就是A::f1。

        总结一下,虚函数表实际上是一个函数指针数组,里面存放了虚函数的地址,虚函数表指针则是指向虚函数表的指针。

C++之多态_第1张图片         虚表指针所在的位置是就是对象所在的位置,因此&a得到的就是虚表地址,然后其他东西才会继续存储。虚表里面存储的是函数的地址,因此可以通过虚表再找到对应的虚函数。

2.2具体实现

        在构成条件中,第三条说必须通过父类的指针或引用来调用虚函数,在了解了虚表和虚表指针后就很好理解了。

        父类指针接受父类对象,然后调用虚函数,显然会直接去调用父类的虚函数。

        但是在父类指针接受子类对象时,会进行切片(划重点),这个时候子类的虚表会被直接接收。

        我们可以看到,父类引用的tmp的虚表指针存放的地址和b中虚表存放的地址是一样的,这样程序去调用f1()函数时,调用的就是b的虚函数,从而达成多态。 

        为什么图中tmp还是会看到B,监视窗口并不一定是真实的,如果我们给B加一个int d,在tmp中并不能对d进行调用。

2.3不构成多态时

        当不满足构成多态的任何一个条件时,都不能形成多态

  1. 当类之间没有继承关系时,尽管类内的成员函数加上了virtual,也不会构成虚函数,调用时就像普通的成员函数一样;
  2. 当父类子类不构成重写时,有两种可能,第一种是构成了重定义,父类的同名函数就会被隐藏,调用时需要标明类域,另一种则是子类没有重写或重定义,此时该函数会被当做父类的成员函数直接被子类继承;
  3. 当调用时,不使用父类指针或引用时,就会形成隐式类型转换,子类的数据会被复制给父类,而子类的虚表指针并不会被复制给父类,因此不会构成多态。

三、其他需要注意的点

3.1 final与override

        final关键字用来修饰成员函数,表示无法被继承;

        override关键字用来修饰子类成员函数,可以判断子类与父类是否构成虚函数,若不构成则报错。

3.2 抽象类与纯虚函数

        纯虚函数就是在虚函数后面加上“=0”,包含纯虚函数的类叫做抽象类。

        抽象类不能实例化出对象,抽象类的派生类如果没有重写纯虚函数,那么该派生类也是抽象类,同样不能实例化出对象。

        通过抽象类,可以强制规定派生类重写纯虚函数,从而形成多态。如果不需要多态就不用把成员函数设置为纯虚函数 。另外,只要有一个成员函数是纯虚函数,就构成抽象类,从而不能被实例化。

你可能感兴趣的:(c++,java)