多态与虚函数

多态

虚函数

定义

成员函数前面加上virtual关键字的函数,称为虚函数。

重定义(重写)的要求

  1. 基类与派生类中函数名要相同
  2. 函数的参数列表也要相同(参数的个数、类型、顺序)
  3. 函数的返回类型一致

小结:基类与派生类中的同名虚函数,除了函数体可以不一样之外,其他的全部都要保持一致。(函数名,函数返回类型,函数的参数列表)

虚函数的实现原理(重要)

当基类定义了虚函数的时候,就会在基类对象的存储布局的前面多一个虚函数指针,该虚函数指针会指向虚函数表(虚表),虚表中存放的是虚函数的入口地址。当派生类继承基类的时候,会吸收基类的虚函数,同样会在派生类对象的存储布局的前面多一个虚函数指针,该虚函数指针指向派生类自己的虚表,该虚表里面存放的是从基类吸收的虚函数的入口地址,当派生类对该虚函数进行重写时,会用派生类自己的虚函数的入口地址进行覆盖,存放的就是派生类自己的虚函数的入口地址

虚函数被激活的五个条件(重要)

  1. 基类要定义虚函数
  2. 派生类要重写(重定义、覆盖)该虚函数
  3. 创建派生类的对象
  4. 要用基类的指针(引用绑定)指向派生类的对象
  5. 要用基类的指针(引用)调用虚函数

那些函数不能设置为虚函数?

  1. 普通函数(全局函数,非成员函数,自由函数):因为虚函数定义中必须是成员函数
  2. 静态成员函数:静态成员函数发生的时机在编译的时候,而虚函数要体现多态,必须在运行的时候;静态成员函数没有this指针,被该类的所有对象共享,对该类而言是唯一的
  3. 内联函数:同样发生的时机在编译的时候;内联函数如果写成虚函数,那么就失去了内联的含义(内联函数目的就是在编译时发生,而虚函数是在运行时发生的)
  4. 友元函数:如果友元函数本身是一个普通函数,那么友元函数不能被设置为虚函数;但是如果友元函数本身是成员函数,那么该友元函数是可以被设置为虚函数的
  5. 构造函数:同样发生的时机在编译的时候;构造函数是不可以被继承的,而虚函数是可以被继承的;如果将构造函数设置为虚函数,那么就需要通过虚表找到虚函数的入口地址,则需要虚函数指针指向虚表,而虚函数指针存储在对象存储布局的最前面,而如果构造函数不调用,那么对象就不一定是完整的,那么对象的存储布局的前面就不一定有虚函数指针,但是如果没有虚函数指针就不能指向虚表来找到函数入口,前后矛盾

虚函数的访问

  1. 使用指针进行访问:可以体现动态多态
  2. 使用引用进行访问:可以体现动态多态
  3. 使用对象进行访问:虚函数体现的就是普通成员函数的特性,在编译的时候就已经确定了函数调用
  4. 使用其他普通成员函数进行访问
  5. 使用特殊成员函数(构造函数、析构函数):在调用Father的构造函数的时候,调用func1()的时候,派生类Son的对象还没有构建完成,所以就看不到虚表这套逻辑,所以只能看到Father自己的func1()。而当派生类Son销毁时,虚表已经被销毁了,所以在调用Father的func2()的时候,已经看不到Son这套逻辑,所以就只会调用到Father自己的func2函数。都没有体现动态多态
#include <iostream>

using std::cout;
using std::endl;

class Grandpa
{
public:
    Grandpa()
    {
        cout << "Grandpa()" << endl;
    }

    ~Grandpa()
    {
        cout << "~Grandpa()" << endl;
    }

    virtual
    void func1()
    {
        cout << "void Grandpa::func1()" << endl;
    }

    virtual
    void func2()
    {
        cout << "void Grandpa::func2()" << endl;
    }
};

class Father
: public Grandpa
{
public:
    Father()
    : Grandpa()
    {
        cout << "Father()" << endl;
        func1();
    }

    ~Father()
    {
        cout << "~Father()" << endl;
        func2();
    }

    virtual
    void func1()
    {
        cout << "void Father::func1()" << endl;
    }

    virtual
    void func2()
    {
        cout << "void Father::func2()" << endl;
    }
};

class Son
: public Father
{
public:
    Son()
    : Father()
    {
        cout << "Son()" << endl;
    }

    ~Son()
    {
        cout << "~Son()" << endl;
    }

    virtual
    void func1()
    {
        cout << "void Son::func1()" << endl;
    }

    virtual
    void func2()
    {
        cout << "void Son::func2()" << endl;
    }
};

int main()
{
    Son son;//栈对象
    return 0;
}

运行结果:

两个加深理解的代码

1

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

class A
{
public:
    A() 
    { 
        cout << "A's cons." << endl; 
    }


    virtual 
        ~A() 
        { 
            cout << "A's des." << endl; 
        }

    virtual 
        void f() 
        { 
            cout<<"A's f()."<<endl; 
        }

    void g() 
    { 
        f();
    }

};

class B 
: public A
{
public:
    B() 
    { 
        f(); 
        cout << "B's cons." << endl; 
    }

    ~B() 
    { 
        cout << "B's des." << endl; 
    }
};

class C 
: public B
{
public:
    C() 
    { 
        cout<<"C's cons."<<endl; 
    }

    ~C()
    { 
        cout<<"C's des."<<endl;
    }

    void f() 
    { 
        cout<<"C's f()."<<endl; 
    }

};
int  main(void)
{  
    A *pa=new C();
    pa->g();

    delete pa;

    return 0;

}

运行结果:

1:在调用B类的构造函数时会调用f函数,此时C对象还没有构建完整,且B类里面并没有重写f函数,并没有满足激活虚函数的五个条件,只能看到A类里面的f函数,最后也只能调用A类里的f函数。

2:对于pa->g(),这里调用g函数,再通过this指针调用g里面的f函数,且这个指针是指向派生类(C)的基类(A)类型的指针,满足了激活虚函数的五个条件,所以会调用C类里面的f函数。

2

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

class A
{ 
public:
    virtual
        void func(int val = 1)
        {
            cout << "A->" << val << endl;
        }
    virtual void test()
    {
        func();
    }
private:
    long _a;
};

class B
: public A
{ 
public:
    virtual
        void func(int val = 10)
        {
            cout << "B->" << val << endl;
        }
private:
    long _b;
};

class C
: public B
{
    virtual
        void func(int val = 10)
        {
            cout << "C->" << val << endl;
        }   

};

int main(void)
{
    B b;
    A *p1 = (A*)&b;
    B *p2 = &b;
    p1->func();
    p2->func();

    return 0;
}

运行结果:

注意:为什么用 p1调用func函数时会打印 B->1?

显然,这里是满足虚函数激活的五个条件的,但是虚函数是在运行时起作用的,故在编译阶段已经确定了func里面的参数val的值,而p1是A类型的指针,编译阶段会走A类里面的func函数,故在编译阶段将val的值确定为1了,然后在运行阶段调用B里面的func函数。

posted @ 2023-04-05 09:25  MyXjl  阅读(16)  评论(0编辑  收藏  举报