一. 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[];
}; int main(){
Parent p;
Child ch;
cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
return ;
} /*
  运行结果:
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[];
}; int main(){
Parent p;
Child ch;
cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
return ;
}
/*
  执行结果:
  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[];
}; int main(){
Parent p;
Child ch;
cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
return ;
}
/*
  执行结果:
  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[];
}; int main(){
Parent p;
Child ch;
cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
return ;
}
/*
  执行结果:
  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[];
}; 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 ;
}
/*
  执行结果:
[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[];
}; int main(){
Parent p;
Child ch;
ch.fun();
cout<<"Parent size : "<<sizeof(p)<<", Child size : "<<sizeof(ch) <<endl;
return ;
}
/*
  执行结果:
[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[];
}; int main(){
Child ch;
ch.fun();
cout<<"Parent size : "<<sizeof(Parent)<<", Child size : "<<sizeof(Child) <<endl;
return ;
}
/*
  执行结果:
  

  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 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

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

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

  1. sizeof求类的大小

    用sizeof求类的大小,http://blog.csdn.net/szchtx/article/details/10254007(sizeof浅析(三)——求类的大小),这篇博文给出了非常详尽的举例 ...

  2. MySQL JDBC常用知识,封装工具类,时区问题配置,SQL注入问题

    JDBC JDBC介绍 Sun公司为了简化开发人员的(对数据库的统一)操作,提供了(Java操作数据库的)规范,俗称JDBC,这些规范的由具体由具体的厂商去做 对于开发人员来说,我们只需要掌握JDBC ...

  3. C++ 类的继承、虚拟继承、隐藏、占用空间

    主函数: #include <iostream> #include "test.h" #include "testfuc.h" using name ...

  4. c/c++面试12-18------关与sizeof那些事儿

    12 使用sizeof计算普通变量所占空间大小 (1)不同数据类型所占字节数不同(32位 64位系统不同) int----->4 double----->8 char-------> ...

  5. Java && Python 算法面试常用类以及方法总结

    数据结构 逻辑结构上: 包括集合,线性结构,非线性结构. 存储结构: 顺序存储,链式存储,索引存储,散列存储. Java 常见数据结构 大专栏  Java && Python 算法面试 ...

  6. Unity游戏开发面试基础知识

    面试第一次知识总结: 一.Unity基本操作 1.unity提供哪几种光源? 点光源.平行光.聚光灯.区域光. 2.物体发生碰撞的必要条件什么? 两个物体必须有碰撞体Collider组件,一个物体上必 ...

  7. Java面试必备知识

    JAVA面试必备知识 第一,谈谈final, finally, finalize的区别. 第二,Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可 ...

  8. javascript常用知识点集

    javascript常用知识点集 目录结构 一.jquery源码中常见知识点 二.javascript中原型链常见的知识点 三.常用的方法集知识点 一.jquery源码中常见的知识点 1.string ...

  9. Java常用的加密解密类(对称加密类)

    Java常用的加密解密类 原文转载至:http://blog.csdn.net/wyc_cs/article/details/8793198 原创 2013年04月12日 14:33:35 1704 ...

随机推荐

  1. 【SRM】518 Nim

    题意 \(K(1 \le K \le 10^9)\)堆石子,每堆石子个数不超过\(L(2 \le 50000)\),问Nim游戏中先手必败局面的数量,答案对\(10^9+7\)取模. 分析 容易得到\ ...

  2. [译]App Framework 2.1 (1)之 Quickstart

    最近有移动App项目,选择了 Hybrid 的框架Cordova  和  App Framework 框架开发. 本来应该从配置循序渐进开始写的,但由于上班时间太忙,这段时间抽不出空来,只能根据心情和 ...

  3. 基础3.Jquery操作Dom

                  1 内部插入节点 <body> <ul id="city"> <li id="bj" name=&qu ...

  4. 新入门node.js必须要知道的概念

    一.对于一个刚入门node.js的朋友来说,一定要了解一些基础概念: 今年我正式进入社会后,发现自己所知道的IT方面的知识,真的只是牛毛,原来人外有人,山外有山,还需要继续努力.下面是一些我的自学习心 ...

  5. 关于nginx.pid丢失的解决办法

    在停掉nginx的过程中突然出现如下的提示:

  6. jquery常见获取高度

    jquery获取文档高度和窗口高度,$(document).height().$(window).height() $(document).height():整个网页的文档高度 $(window).h ...

  7. @autowired和@resource的区别

    @Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自 动注入罢了.@Resource有两个属性是比较重要的, ...

  8. 流行的JavaScript库 ——jQuery

    1.为了简化 JavaScript 的开发, 一些 JavsScript 库诞生了. JavaScript 库封装了很多预定义的对象和实用函数.能帮助使用者建立有高难度交互的 Web2.0 特性的富客 ...

  9. SageCRM 页面加载完后,用下拉框联动修改放大镜字段的取值范围

    原理很简单就是修改放大镜属性中的sql. 函数如下:第一个参数是字段的名称.第二个参数是需要控制这个放大镜的sql.可以跟进下拉框的值来组织这个sql. /*--------------- For C ...

  10. 基于AT89C51单片机的贪吃蛇电子游戏(仿真)

    有关贪吃蛇的历史发展可以看一下这个网址,贪吃蛇最初的设计和现在并不相同..http://www.techweb.com.cn/internet/2013-02-21/1278055.shtml 该项目 ...