重载、覆盖和隐藏的区别以及基类、父类指针关系

函数重载:

在一个类中,函数名相同,函数参数类型,或者函数参数个数不同,或者都不同,成为函数重载。
函数的重载发生在程序编译阶段,由编译器进行优化和区分。
《高质量C++/C编程指南》已经清晰的列出了重载函数的特性:

(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。

如:

class A
{
public:
    A()
    {
        age = 3;
    }
    A(int num)
    {
        age = num;
    }
private:
    int age;
};

如A()与A(int num)同为A的构造函数,但由于参数不同,则两则互为重载,对于一个实例,编译器会根据参数的个数执行不同的构造函数


函数的隐藏和覆盖只发生在子类和父类之间,函数隐藏发生在编译时期,相对于对象指针为固定偏移,函数覆盖发生在运行时期,基类和子类的虚函数会根据虚函数列表到指向出运行虚函数。

对于父类和子类中同名函数,如果没有virtual关键字修饰,编译器都将子类中的基类同名函数做隐藏处理;如果有virtual关键字修饰,编译器会建立虚函数列表,运行时期,根据虚函数表运行相应的虚函数。


函数隐藏

函数隐藏,是指派生类函数将基类函数给藏起来了,即函数隐藏发生在子类和父类之间。特性为:

  1. 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。
  2. 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数别隐藏。
#include <iostream>
using namespace std;
class A
{
public:
    void foo()
    {
        printf("1\n");
    }
};
class B :public A
{
public:
    void foo()
    {
        printf("3\n");
    }
};
int main(void)
{
    A a;
    A *p_A = &a;
    p_A->foo();//执行成员函数

    B b;
    B *p_B = &b;
    p_B->foo();//执行成员函数

    p_A = &b;   //p_A指针执行子类,可以访问被隐藏的基类成员函数
    p_A->foo();

    system("pause");
    return 0;
}

输出为1 3 1
执行成员函数的输出为1,3,这里没有问题。B中成员函数foo()与基类A中成员函数foo,函数名相同,且没有virtual关键字,则子类会将基类中的同名函数隐藏,但是,通过用基类指针指向子类,可以访问被隐藏的基类同名函数foo().
同时也说明,除数据区以外,子类会继承基类所有的成员函数,但对于同名的成员函数,子类在实现自身的成员函数后,会将基类的同名函数隐藏(这里的隐藏指不能通过子类型的对象指针访问到基类的隐藏指针,但可以通过基类指针访问到被隐藏的子类指针中的基类指针)
子类父类中成员函数在内存中的排布
图为子类父类中成员函数在内存中的排布

函数覆盖

函数的覆盖是指派生类函数覆盖基类函数,只作用与派生类函数,其特性为:

  1. 不同的范围(分别位于派生类与基类);
  2. 函数名字相同;
  3. 参数相同;
  4. 基类函数必须有virtual关键字。
    实际上虚函数的作用,就是实现覆盖
    函数的覆盖发生在执行时期,对于有虚函数的类,都含有虚函数表,其虚函数表的指针偏移量固定,但表中函数所指向的函数地址不同,运行时期,会根据虚函数表去执行对应的虚函数。
#include<iostream>  
using namespace std;

class A
{
public:
    void foo()
    {
        printf("1\n");
    }
    virtual void fun()
    {
        printf("2\n");
    }
};
class B : public A
{
public:
    void foo()
    {
        printf("3\n");
    }
    void fun()
    {
        printf("4\n");
    }
};

int main(void)
{
    A a;
    B b;
    A *p = &a;
    p->foo();   //访问基类成员函数
    p->fun();   //访问基类虚函数

    p = &b;
    p->foo();   //访问子类成员函数
    p->fun();   //访问基类虚函数

    B *ptr = (B *)&a;  
    ptr->foo();  //访问子类成员函数
    ptr->fun(); //访问基类成员函数

    system("pause");
    return 0;
}

输出为1 2 1 4 3 2
基类和子类中的同名函数,根据是否有virtual关键字修饰分为成员函数和虚函数,对于成员函数基类做隐藏处理,对于虚函数,子类重新实现,并更新虚函数表,基类和子类都是通过访问虚函数表间接访问虚函数。
即,对象指针访问成员函数时,会根据指针的类型,偏移确定的间隔,子类可以通过跳转基类的偏移来访问子类中被隐藏的基类同名函数;对象指针访问虚函数时,会根据虚函数表的指向去执行虚函数。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/su1041168096/article/details/79086572