关于c++面向对象重债覆盖隐藏和虚函数表

本文探讨了C++中的虚函数、覆盖与隐藏的区别,以及它们如何与多态特性相结合。通过具体代码示例解释了虚函数表的工作原理及多态的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自http://blog.youkuaiyun.com/yukin_xue/article/details/7437742

部分是我自己写的耶…毕竟是给自己看的
我自己写的地方有可能有大量错误

先说基本概念吧
重载是相对类自己而言的 一个类中两个稍微有些不一样的实现 实现了重载(只有返回值类型不同不可以哦)
隐藏和覆盖就是相对类和类两个不同类而言了,当然一般就是子类和父类啦…

这是覆盖的定义:(摘自原博客)

(1)不同的范围(分别位于派生类与基类)
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有 virtual 关键字。

原文中对隐藏的注意点的解释
令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难, 但是 C++的隐藏规则使问题复杂性陡然增加。这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 。

大概简化就是覆盖就是两个方法的名字意义参数也一样只有实现不一样
而隐藏就是两个有点不一样的方法出现在子类和父类中…

但是接下来我就不是很理解了

include   <iostream> 

class   Base 
...{ 
public: 
        virtual   void   f(float   x)...{   cout   < <   "Base::f(float)   "   < <   x   < <   endl;   }
        void   g(float   x)...{   cout   < <   "Base::g(float)   "   < <   x   < <   endl;   }
        void   h(float   x)...{   cout   < <   "Base::h(float)   "   < <   x   < <   endl;   }
}; 

class   Derived   :   public   Base 
...{ 
public: 
        virtual   void   f(float   x)...{   cout   < <   "Derived::f(float)   "   < <   x   < <   endl;   }
        void   g(int   x)...{   cout   < <   "Derived::g(int)   "   < <   x   < <   endl;   }
        void   h(float   x)...{   cout   < <   "Derived::h(float)   "   < <   x   < <   endl;   }
}; 

这是类的定义和几个方法的实现

void   main(void) 
...{ 
        Derived   d; 
        Base   *pb   =   &d; 
        Derived   *pd   =   &d; 
        //   Good   :   behavior   depends   solely   on   type   of   the   object
        pb-> f(3.14f);   //   Derived::f(float)   3.14 
        pd-> f(3.14f);   //   Derived::f(float)   3.14 
        //   Bad   :   behavior   depends   on   type   of   the   pointer 
        pb-> g(3.14f);   //   Base::g(float)   3.14 
        pd-> g(3.14f);   //   Derived::g(int)   3   (surprise!) 
        //   Bad   :   behavior   depends   on   type   of   the   pointer 
        pb-> h(3.14f);   //   Base::h(float)   3.14   (surprise!) 
        pd-> h(3.14f);   //   Derived::h(float)   3.14 
} 

这是结果,我完全不知道为什么,我只知道
那个基类中被声明为virtual也就是虚函数的f()方法是很特别的
这里有几个推测:
1.它实现了所谓的多态
2.如果1成立,那么多态和所谓的覆盖有关系…
3.如果1成立,那么多态和什么基类的指针有关系…不管是调用基类的指针还是子类的指针,调用的方法都是子类的进行覆盖的那个实现…
疑问就是:
1.为什么多态和覆盖有关系,有什么关系
2.多态的实现是通过虚函数实现的,虚函数是有一个叫做虚函数表的神奇玩意儿…这东西的实现原理是什么
3.串起来关系以后…虚函数表和父类的指针又有什么关系,为什么衍生类多了以后,虚函数多了以后,用基类的指针比较方便(百度的说法…)

接下来就是慢慢探索问题答案的部分…探索完了估计也就了解得差不多了。

2017_7_8_2031
看到虚函数和覆盖比较完整的解释:

如果将基类中的某个成员函数声明为虚函数,那么其子类中与该函数具有相同原型的成员函数就也成为虚函数,并对基类中的版本构成覆盖(override)

关于多态的解释

通过一个指向子类对象的基类指针,或者引用子类对象的基类引用,调用这个虚函数时,实际被调用的将是子类中的覆盖版本。这种特性被称为多态。
http://blog.youkuaiyun.com/hnlyyk/article/details/49450245 资料来源

int main(void)
{
    /*子类对象的基类引用*/

    Apple apple;
    Fruit &b = apple;
    b.hello();

    /*子类对象的基类引用*/

    Fruit *pb = new Apple;
    pb->hello();

    //运行结果都是apple::hello world!
    return 0;
}

基类指针和引用都可以用来访问派生类对象:一个派生类对象一定是一个基类对象。
上述理论还可以体现为:

派生类对象可以赋值给基类对象。
Apple d;
Fruit b = d; // Fruit是基类Apple是派生类…

资料中关于覆盖的说法:

关于覆盖
1.基类中成员函数必须是虚函数。
2.子类中成员函数必须与基类中的虚函数拥有完全相同的函数名、形参表和常属性。
3.如果基类中的虚函数返回基本类型,那么子类覆盖版本的返回类型必须与基类完全相同。如果基类中的虚函数返回类类型的指针或者引用,那么子类覆盖版本的返回类型可以是基类返回类型的子类。
4.子类中的覆盖版本不能比基类版本抛出更多的异常。
5.子类中覆盖版本与基类版本的访控属性无关。

可以理解的抛出更少的异常的说法:

//我们用的时候,通常会这样用:
Foo f=new FooImpl();
f.hello();

/*如果FooImpl里实现的hello方法抛了异常,而Foo定义的hello实际没throws异常的,那上写这个调用方法,定义的f是Foo类型的,Foo类里的hello方法没异常,不是就冲突了吗? 
*/

子类private

class A { 
public: 
virtual int foo (void) { … } 
}; 
class B : public A { 
private 
int foo (void) { … } 
}; 
B b; 
b.foo (); // ERROR ! 
A* p = &b; 
p -> foo (); // OK !

推测出了几个答案:
1.虚函数是覆盖的必要要求。覆盖又是形成多态特性的前提。

多态的实现离不开基类与子类之间的覆盖关系

2017_7_9_1437

在探索指针问题的时候发现这个资料

http://www.cnblogs.com/malecrab/p/5572730.html
http://www.cnblogs.com/malecrab/p/5572119.html

  1. 数据成员指针
    对于普通指针变量来说,其值是它所指向的地址,0表示空指针。
    而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示。例:
struct X {
    int a;
    int b;
};
#define VALUE_OF_PTR(p)     (*(long*)&p)
int main() {
    int X::*p = 0;  // VALUE_OF_PTR(p) == -1
    p = &X::a;      // VALUE_OF_PTR(p) == 0
    p = &X::b;      // VALUE_OF_PTR(p) == 4
    return 0;
}

这里必须要用强制类型转化才会出现地址偏移量!

直接用引用符然后打印出来的是bool类型值 0或1.

    struct Test
    {
        int a;
        int b;
        int c;
    };
    int Test::*p = 0;

    p = &Test::a;
    cout<<*(long*)&p<<endl;//输出偏移量0
    cout<<p<<endl;         //输出bool值1

    Fruit b;
    Apple d;

    void (Fruit::*p)() = NULL;
    p = &b.hello;

    cout<<&b.hello<<endl;
    cout<<p<<endl; //两种都输出的布尔值1







class Person
{
public:
    /*这里稍稍注意一下,我将speak()函数设置为普通的成员函数,而hello()函数设置为虚函数*/
    int value;
    void speak()
    {
        cout << "I am a person!" << endl;
        printf ("%p\n", &Person::speak); /*在这里验证一下,输出一下地址就知道了!*/
    }
    virtual void hello()
    {
        cout << "Person say \"Hello\"" << endl;
    }
    Person()
    {
        value = 1;
    }

};

关于成员函数指针参考资料

2017_7_10_005
好吧刚刚不管怎么测试打印成员函数地址打印的都是1。
发现一个规律
成员函数不是虚函数的
在函数内部printf出来的可以是自己的地址在外部printf的也是自己的地址

但是但是但是成员函数中,虚函数不管怎么样都不打印自己的地址.
在类外边cout不管怎样都是打印1,但是printf(”%p”)就不同了,打印出来的是地址偏移量。第一个虚函数打印的是1,第二个打印的是5。

这里写图片描述
1数据成员指针 写在上面了
2非虚函数成员指针 没写出来 在上面那发现规律的地方差不多

http://www.cnblogs.com/malecrab/p/5572119.html
http://blog.youkuaiyun.com/dazhong159/article/details/7906688
http://www.cnblogs.com/malecrab/p/5572730.html
http://www.cnblogs.com/malecrab/p/5572119.html

大概意思就是 单继承子类的虚函数表是跟在基类后面的。。。
所以偏移量你会算了啊?
然后就是多重继承的子类的虚函数表是跟在主继承也就是第一个继承的基类的虚函数表后面 这里说的子类的虚函数表中只包含了自己的新函数,也就是说不再拷贝一份基类的函数的指针,而是直接跟在后面,相当于直接包含了,多重继承中
偏移量问题,子类继承的主基类的函数指针在虚函数表中偏移量还是1,也就是说相当于没有复制任何继承来的函数,该继承的函数还是在原来的函数表中,要访问的时候用父类的指针指向子类的对象就行。
虚函数表大概就是这么个样子。终于明白了;

a:: foo()  quz()
b:: bar()

c继承了a和b类
quz是c类自己的虚函数。
foo和bar指针偏移量都是1
也就是说调用这些被继承的函数的时候还是去原来的基类的虚函数表进行寻找


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值