c++面试常用知识(sizeof计算类的大小,虚拟继承,重载,隐藏,覆盖)

简介:

一. sizeof计算结构体

  注:本机机器字长为64位

1.最普通的类和普通的继承

复制代码
#include<iostream> 
using namespace std;

class Parent{
public:
    void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Child : public Parent{
public:
    void fun(){
        cout<<"Child fun"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}

/*
运行结果:
Parent size : 1, Child size : 5
*/
复制代码

  分析:那么为什么类(对象)的大小为什么会是1个字节呢?那是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址。也就是说这个char是用来标识类的不同对象的。因为如果不是1,当定义这个类的对象数组时候A objects[5]; objects[0]和objects[1]就在同一个地址处,就无法区分。

2.基类中含有私有成员

 

复制代码
#include<iostream> 
using namespace std;

class Parent{
public:
    void fun(){
        cout<<"Parent fun"<<endl;
    }
private:
    int x;    
}; 

class Child : public Parent{
public:
    void fun(){
        cout<<"Child fun"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
执行结果:
Parent size : 4, Child size : 12
*/
复制代码

 

  分析:基类里的私有成员在派生类里仍占有内存。在派生类里,基类的int占4个字节,char ch[5]占用5个字节,考虑内存的对齐,变成4+(5+3)=12个字节。

 

3.类中含有虚函数

 

复制代码
#include<iostream> 
using namespace std;

class Parent{
public:
    virtual void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Child : public Parent{
public:virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
执行结果:
Parent size : 8, Child size : 16
*/
复制代码

 

  分析:有虚函数的类有个virtual table(虚函数表),里面包含了类的所有虚函数,类中有个virtual table pointers,通常成为vptr指向这个virtual table,占用8个字节的大小。成员类Child public继承于Parent,类Child的虚函数表里实际上有两个虚函数Parent::fun()和Child::hjzgg(),类B的大小等于char ch[5]的大小加上一个指向虚函数表指针vptr的大小,考虑内存对齐为16。一个类里若有虚函数,无论有多少个虚函数都只有一个指向虚表的指针,虚表中的每一个表项保存着一个虚函数的入口地址。当调用虚函数时,先找到虚表中它对应的表项,找到入口地址再执行

4.多重继承

复制代码
#include<iostream> 
using namespace std;

class Parent{
public:
    virtual void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Father{
public:
    virtual void fun(){
        cout<<"Father fun"<<endl;
    }
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
执行结果:
Parent size : 8, Child size : 24
*/
复制代码

  分析:Child中除了char ch[5]这5个字节,Child现有一个虚函数表,里边有Child自身定义的虚函数以及从Parent中继承过来的虚函数,然后又另一张虚函数表来存放Father中过来的虚函数,也就是Child对应两个虚函数表的指针。总共内存空间5+8+8=21,考虑内存的对齐,为24字节。

 5.虚继承

  C++虚拟继承

  ◇概念:

     C++使用虚拟继承(Virtual Inheritance),解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。          这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。

  ◇解决问题:

  解决了二义性问题,也节省了内存,避免了数据不一致的问题。 
 同义词: 
  虚基类(把一个动词当成一个名词而已)
  当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个   公共基类说明为虚基类。

  ◇执行顺序

     首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造;

   执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;

   执行成员对象的构造函数,多个成员对象的构造函数按照申明的顺序构造;

   执行派生类自己的构造函数;

     析构以与构造相反的顺序执行;

  注:

    从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类           的构造函数而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。

    在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。

 5.1 没有用虚拟继承

复制代码
#include<iostream> 
using namespace std;

class Parent{
public:
    virtual void fun(){
        cout<<"Parent fun"<<endl;
    }
}; 

class Father{
public:
    virtual void fun(){
        cout<<"Father fun"<<endl;
    }
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    ch.fun();

    ch.Parent::fun();//这样调用是对的
 ch.Father::fun();

    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
执行结果:
[Error] request for member 'fun' is ambiguous
*/


复制代码

    分析:因为派生类中的虚函数表会继承来自各个基类的虚函数。所以Child对应的虚函数表中会有Parent 和 Father各自的fun()函数,所以在调用的时候就会出现歧义,不知道应该调用哪个!

  同样,和下面一样的写法也是错误的,增加一个Super类。

复制代码
#include<iostream> 
using namespace std;

class Super{
public:
    virtual void fun(){
        cout<<"Super fun"<<endl;
    }
};

class Parent :  public Super{
public:
     
}; 

class Father : public Super{
public:
      
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Parent p;
    Child ch;
    ch.fun();
    cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
    return 0;
}
/*
执行结果:
[Error] request for member 'fun' is ambiguous
*/
复制代码

 5.2 使用虚拟继承#include<iostream> 

复制代码
using namespace std;

class Super{
public:
    Super(){
        cout<<"Super construction"<<endl;
    }
    virtual void fun(){
        cout<<"Super fun"<<endl;
    }
};

class Parent : virtual public Super{
public:
     Parent(){
         cout<<"Parent construction"<<endl;
     }
}; 

class Father : virtual public Super{
public:
      Father(){
          cout<<"Father construction"<<endl;
      }
};

class Child : public Parent, public Father{
public:
    virtual void hjzgg(){
        cout<<"呵呵"<<endl;
    }
    char ch[5];
};

int main(){
    Child ch;
    ch.fun();
    cout<<"Parent size : "<<sizeof(Parent)<<", Child size : "<<sizeof(Child) <<endl;
    return 0;
}
/*
执行结果:

   Super construction
 Parent construction
 Father construction
 Super fun
 Parent size : 8, Child size : 24


*/
复制代码

  分析:

  1.在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。
  2.声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。(Super的构造函数只执行了一次,如果不是有虚基类,那么Super的构造函数     将会执行两次。)
  3.观察类构造函数的构造顺序,拷贝也只有一份。

二. c++重载、覆盖、隐藏的区别和执行方式

  1.成员函数被重载的特征
(1)相同的范围(在同一个类中); 
(2)函数名字相同; 
(3)参数不同; 
(4)virtual 关键字可有可无。 
  2.“覆盖”是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类); 
(2)函数名字相同; 
(3)参数相同; 
(4)基类函数必须有virtual 关键字。 
  3.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,特征是:

  (1)不同的范围(分别位于派生类与基类);

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

  小结:说白了就是如果派生类和基类的函数名和参数都相同,属于覆盖,这是可以理解的吧,完全一样当然要覆盖了;如果只是函数名相同,参数并不相同,则属 于隐藏。










本文转自 小眼儿 博客园博客,原文链接:http://www.cnblogs.com/hujunzheng/p/4948421.html,如需转载请自行联系原作者
目录
相关文章
|
20天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
70 4
|
3月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
2月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
26 1
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
85 11
|
2月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
54 1
|
2月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
39 1
|
2月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
20 0
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
31 0
|
2月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
33 0
|
2月前
|
Serverless 编译器 C语言
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
下一篇
无影云桌面