欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

虚函数

最编程 2024-04-14 09:27:14
...
武汉源创会回归,4月20聊聊大模型”

(ORZ,又是一系列的问题……)

###第一问 virtual关键字的用法?

这个没什么好说的了,基本就是书上的答案,我答的时候分为两类来答:

  1. 虚继承(虚拟基类继承),是为了避免多继承带来的重复继承问题,所有直接继承类必须声明为虚继承模式2

     <!-- lang: cpp -->
         class child:virtual public parent{
     };
    
  2. 多态,这个就是重点要的考点了。这里的多态是运行时多态,即它允许函数调用与函数体之间的联系在运行时才建立,即在运行时才决定如何动作。

     <!-- lang: cpp -->
         class parent
     {
     public:
         virtual void print(){
         }
     }
    
     class child: public parent
     {
     public:
         virtual void print(){
         }
     }
    
     int main(void)
     {
         parent *p = new child();
         p->print();
         return 0;
     }
    

基本就是上面这个用法,但是……诶,其实这个是最表面的东西,Baidu的面试是一定会问得比较深入的。如果这个我答不上来也就罢了,可是偏偏我就懂一些用法,深层次的就跪了……

###第二问 编译器是怎样去实现多态的?

虚函数是通过一张虚函数表来实现的,在对象中存储的是一个指针,指向虚函数表。在这个表中,存储了虚函数的地址,解决了继承、覆盖的问题,保证反应实际调用函数。通常,这个指针从对象的首地址开始存放。虚函数表的最后一个节点存放的是101代表后面还有虚函数表(多重继承),0代表结束。

图就不画了,皓哥这里画的很好了3

主要是两个特点:

  1. 虚函数按照其声明顺序放于表中。
  2. 父类的虚函数在子类的虚函数前面。

可以分为**一般继承(有虚函数覆盖),多重继承(无虚函数覆盖),多重继承(有虚函数覆盖)**这三类去讨论。

一般继承(有虚函数覆盖),这种情况中,覆盖的函数会放在原来父类虚函数的位置,其他的函数顺序按照上面所说的特点来分配,这里只有一个父类,只有一个虚函数表指针。

多重继承(无虚函数覆盖),因为有多个父类,所以实际上是有多个虚函数表指针的,子类的成员函数在第一个声明的父类虚函数表后面。

多重继承(有虚函数覆盖),结合上述两者,有多个虚函数表指针,又替换原来父类的位置。

具体的验证的话,可以参照皓哥放在最后面的程序,很清晰3

来一道简单的考题:

    <!-- lang: cpp -->
        typedef void(*Fun)(void);

class parent
{
private:
    virtual void foo1(){
        printf("parent:foo1\n");
    }
public:
    virtual void foo2(){
        printf("parent:foo2\n");
    }
};

class child:public parent
{
public:
    void foo2(){
        printf("child:foo2\n");
    }
    virtual void foo3(){
        printf("child:foo3\n");
    }
};
int main()
{
    child c;
    Fun p_fun;
    for(int i = 0;i<3;i++){
        p_fun = (Fun)*((int*)*(int*)(&c)+i);
        p_fun();
    }
    return 0;
}

结果: 在此输入图片描述

第三问

构造函数能否为虚函数?

用过virtual的都知道,析构函数是要virtual的,因为析构函数的调用是在子类中隐含的。因此,如果调用了析构函数,那么先调用子类的,然后调用父类的。

那么,究竟构造函数能否为虚函数呢? 答案是不能的,首先,c++中,父类的构造函数一定是先于子类调用的,假设将构造函数定义为virtual,那么,代表了该构造函数需要调用子类的具体实现,然而,子类构造的时候又需要先调用父类的构造函数,所以,显然是构造函数不能为虚函数

第四问

    <!-- lang: cpp -->
        class parent
{
};

int main()
{
    printf("%d\n",sizeof(parent));
}

求输出的答案?

这道题是从《剑指Offer》上看来的,答案是1(g++),因为空类型是会占用一定的地址空间的,由编译器决定具体的大小。

    <!-- lang: cpp -->
        class parent
{
public:
    virtual void foo(){
    }
};

int main()
{
    printf("%d\n",sizeof(parent));
}

有了前面的虚函数铺垫,相信答案就比较明显了,在32位的机器上就是4

再来一段:

    <!-- lang: cpp -->
        class father
{
private:
    virtual void foo1(){
        printf("father:foo1\n");
    }
public:
    virtual void foo2(){
        printf("father:foo2\n");
    }
};

class mother
{
private:
    virtual void foo4(){
    }
};

class child:public father, public mother
{
public:
    void foo2(){
        printf("child:foo2\n");
    }
    virtual void foo3(){
        printf("child:foo3\n");
    }
};
int main()
{
    printf("%d\n",sizeof(child));
    return 0;
}

这里的答案就是8了,两条虚地址表记录。

扩展问(内存对齐初步)

    <!-- lang: cpp -->
        class parent{
public:
    int a;
    char b;
};

int main()
{
    printf("%d\n",sizeof(parent));
    return 0;
}

答案是8,这个问题以后再深入讨论。