【摘要】

要求理解覆盖、重载、隐藏的概念与相互之间的差别。熟记类继承中对象、函数的訪问控制;掌握虚函数、虚函数表、虚函数指针的联系;理解区分虚函数和虚继承在虚方法、虚指针在空间分配上的重点与难点;熟练使用多重继承。要求能区分基类的同名函数和基类的空间布局。

【正文】

类继承中的覆盖

#include<iostream>
using namespace std; class A
{
protected:
int m_data;
public:
A(int data = 0)
{
m_data = data;
}
int GetData()
{
return doGetData();
}
virtual int doGetData()
{
return m_data;
}
}; class B : public A
{
protected:
int m_data;
public:
B(int data = 1)
{
m_data = data;
}
int doGetData()
{
return m_data;
}
}; class C : public B
{
protected:
int m_data;
public:
C(int data = 2)
{
m_data = data;
}
}; int main ()
{
C c(10); cout << c.GetData() <<endl;
//C中没有定义,故调用B中的,可是B中也没有定义,故调用A中的GetData()。由于A中的doGetData()是虚函数。所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data, 故输出 1。
cout << c.A::GetData() <<endl;
//由于A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出 1。
cout << c.B::GetData() <<endl;
//肯定是B类的返回值 1 了。 cout << c.C::GetData() <<endl;
//C类中未重定义GetData(),故调用从B继承来的GetData(),可是B类也没有定义,所以调用A中的GetData(),由于A中的doGetData()是虚函数,所以调用B类的doGetData(),股输出为1 cout << c.doGetData() <<endl;
//B类的返回值 1 了。 cout << c.A::doGetData() <<endl;
//由于直接调用了A的doGetData() ,所以输出0。
cout << c.B::doGetData() <<endl;
//调用了B的doGetData()。所以输出 1。
cout << c.C::doGetData() <<endl;
//调用了B的doGetData(),所以输出 1。
return 0;
}

这里须要注意的是。第二个和第六个输出,此处单独列出并解析

    cout << c.A::GetData() <<endl;
cout << c.A::doGetData() <<endl;

这里两个尽管都是调用A类中的函数,可是。一个是成员函数调用虚函数一个是直接调用虚函数;

第一个函数是成员函数调用虚函数,本类中无该虚函数的定义声明。故找到近期的基类系虚函数;

第二个函数直接调用虚函数,直接去调用类中查找。故找到调用类结果就可以。

  • 这里要注意存在一个就近调用。假设父类存在相关接口则优先调用父类接口,假设父类也不存在相关接口则调用祖父辈接口。

  • 关于覆盖的概念:指针的数据类型是实函数的类型。指针指向的对象的数据类型,是虚函数的数据类型。
  • 详见:C++
    覆盖 重载 隐藏 浅析
  • 详址:http://blog.csdn.net/u013630349/article/details/46706299

类继承中的訪问控制

公有继承(public)

保护继承(protected)

私有继承(private)

派生类对基类的訪问控制

公有及保护成员可见

公有及保护成员可见

公有及保护成员可见

派生类对象对基类的訪问控制

公有成员

全部成员不可见

全部成员不可见

派生类中基类成员的訪问控制属性

基类公有与保护成员訪问控制属性不变

基类公有与保护成员作为派生类的保护成员

基类公有与保护成员作为派生类的私有成员

备注

基类成员作为派生类的(保护\私有)成员不被子类訪问

private: 本类函数和友元函数能够訪问。

protected: 本类函数、友元函数和子类函数能够訪问。

public: 本类函数、友元函数、子类函数和本类的对象能够訪问。

和公有继承、保护继承和私有继承没有关系。保护继承和私有继承影响的是子类的继承关系。

例:类B从类A派生,私有继承,仅仅能说基类A的public和protected成员到了子类B后。都变为private。B再往下继承时。其子类C是不能訪问A的public和protected成员的,可是对于B来说。A的protected和public是能够訪问的。



虚函数继承和虚继承

虚方法(虚函数)

每个含有虚方法(虚函数)对象里有虚表指针,指向虚表。

虚表里存放了虚函数的地址,虚函数表是顺序存放虚函数地址的。不须要用到链表。

所以,类中的每个对象都有一个指向顺序表的指针来存虚方法地址(顺序表和链表都是线性表)。那就是虚表。

虚函数的实现要求对象携带额外的信息,这些信息用于在执行时确定后该对象应该调用哪一个虚函数,典型的情况下,这个信息具有一种被称为 vptr 虚函数指针的指针形式,vptr 指向一个被称为 vtbl 的虚函数表。函数指针数组。每个虚函数都关联到
vtbl 。

当一个对象调用了虚函数。实际的被调用函数通过以下步骤确定:

1)找到对象的 vptr 指向的 vtbl ;

2)在 vtbl 中寻找合适的函数指针。

虚函数的缺点

虚函数最基本的缺点是运行效率较低。看一看虚拟函数引发的多态性的实现过程,你就能体会到当中的原因,另外就是因为要携带额外的信息(VPTR),所以导致类多占的内存空间也会比較大。对象也是一样的。

虚函数、虚函数表、虚函数指针的联系

每个具有虚函数的类都有一个虚函数表VTABLE。里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表VTABLE是这个类的全部对象所共同拥有的,也就是说不管用户声明了多少个类对象。可是。这个VTABLE虚函数表(数据结构的唯一性)仅仅有一个(能够理解成一个类仅仅有一种表。每个对象都复制一个表,存在一个该对象初始化的内存空间之上)。

详见:C++
关于类与对象在虚函数表上唯一性问题 浅析

详址:http://blog.csdn.net/u013630349/article/details/47068767

在每一个具有虚函数的类的对象里面都有一个VPTR虚函数指针,这个指针指向VTABLE的首地址,每一个类的对象都有这么一种指针。

每一个对象的虚函数表是一样的。指的是虚函数表的数据结构。可是,每一个对象各有一个虚函数表,指的是每一个对象的虚函数表的内存地址不同。

虚函数的继承

1)空类、单一继承的空类、多重继承的空类所占空间大小为:1(字节,下同);

2)一个类中,虚函数本身、成员函数(包含静态与非静态)和静态数据成员都是不占用类对象的存储空间的。

3)类对象的大小 = 各非静态数据成员(包含父类的非静态数据成员但都不包含全部的成员函数)的总和+ vfptr指针(多继承下可能不止一个) + vbptr指针(多继承下可能不止一个) + 编译器额外添加的字节。

4)当类中声明了虚函数(无论是1个还是多个),那么在实例化对象时。编译器会自己主动在对象里安插一个指针vPtr指向虚函数表VTable。

#include<iostream>
#include<memory.h>
#include<assert.h> using namespace std;
class A
{
char k[3]; //所占的大小为3
public:
virtual void aa(){}; //虚指针大小为4
};
class B : public A
{
char j[3];
public:
virtual void bb(){};
};
class C : public virtual A
{
char i[3];
public:
virtual void cc(){};
};
int main(int argc, char *argv[])
{
cout << "sizeof(A): " << sizeof(A) << endl; //大小为4(char)+4(虚表)=8
cout << "sizeof(B): " << sizeof(B) << endl; //大小为8(A副本)+4(char)+4(虚表)=16
cout << "sizeof(C): " << sizeof(C) << endl; //大小为8(A副本)+4(char)+4(虚表)+4(虚继承+虚函数构成指针多一个)=20
return 0;
}


什么是虚继承?它和一般的继承有什么不同?有什么用

虚拟继承是多重继承中特有的概念。虚拟基类是为了解决多重继承而出现的,能够节省内存空间

请看下图:

在图 1中。类D接触自类B和类C,而类B和类C都继承自类A,因此出现了图 2所看到的的情况。

在图 2中,类D中会出现两次A。为了节省内存空间,能够将B、C对A的继承定义为虚拟继承,而A成了虚拟基类。最后形成了图 3。

区分虚函数和虚继承

虚拟继承是多重继承中特有的概念,是为解决多重继承的。用虚继承能够节省内存空间

虚函数是面向对象多态性的主要方式,通过继承基类中的虚函数在子类中重载实现不同操做。继承的虚函数在子类中不须要加virtual,默认就是虚函数。

能够被它的子类覆盖。

假设不是虚继承的类(普通继承),即便有虚函数也不会因此添加存储空间。假设是虚继承的类,没有虚函数就加入一个虚指针空间,有虚函数不论多少个,就加入两个虚指针空间!!!

详见:C++
深入理解 虚继承、多重继承和直接继承

详址:http://blog.csdn.net/u013630349/article/details/47057929

多重继承

多重继承优缺点

长处:

简单、清晰、更加有利于复用,对象能够调用多个基类中的接口

缺点:

1)二义性。比如类A派生了B和C,而B和C共同派生了D,麻烦就出现了,这样的中间大两头小的继承树有个形象的名字:叫做砖石型继承树(DOD)。

2)使得父类指针指向子类对象变得非常麻烦。得用C++的dynamic_cast来执行强制转换,这个东西也非常麻烦,由于它是执行期间而非编译期间进行转换的,它要求编译器同意RTTI;

3)多重继承还会使子类的vtable变得不同平常,由于子类的vtable中绝对不可能包括完整的有序的两个父类的vtable,因此每一个父类对象都加入了一个指针。

多重继承优缺点简版

长处:多种功能。加快任务实现。

缺点:多重性格,易得精神分裂。

多重继承的声明:

声明一个类Jetplane。它是从Rocket和Airplane继承而来的

class JetPlane:public Rocket, public Airplane

在多继承的时候,假设一个类继承同一时候继承自class A和class B,而class A和B中有一个函数叫foo(),怎样明白地在子类中指出覆盖的是哪个父类的foo()

#include<iostream>
#include<memory.h>
#include<assert.h> using namespace std;
class A
{
public:
void foo(){};
};
class B
{
public:
<span style="white-space:pre"> </span>void foo(){};
};
class D:public A, public B
{
};
int main()
{
D d;
d.A::foo();
return 0;
}

基类和派生类的地址和布局的问题

#include <iostream>
using namespace std; class A
{
int m_a;
}; class B
{
int m_b;
}; class C: public A , public B
{
int m_c;
}; int main(int argc, char* argv[])
{
C *pc=new C;
B *pb=dynamic_cast<B*>(pc);
A *pa=dynamic_cast<A*>(pc); if (pc==pb)
{
cout<<"equal"<<endl;
}
else
{
cout<<"unequal"<<endl;
} if ((int)pc==(int)pb)
{
cout<<"equal"<<endl;
}
else
{
cout<<"unequal"<<endl;
}
delete pc;
return 0;
}

实验结果:第一个同样是由于父类指针指向子类对象的时候。採用多重继承之后用dynamic_cast。导致相等输出equal。第二指针pc和pb值是不同的,所以,转换为int型也是不同的。输出unequal。???(尚未理解!

C++ 继承与接口 知识点 小结(一)的更多相关文章

  1. C++重要知识点小结---2

    C++重要知识点小结--1 :http://www.cnblogs.com/heyonggang/p/3246631.html 1.C++允许程序员声明一个不能有实例对象的类,这样的类惟一的用途是被继 ...

  2. 对java中继承、接口、组合的思考

    1.在c++中有继承和多重继承,而java中只有单继承.继承的好处在于可以复用一些东西,但缺陷在于后续不好扩展.此外,可读性方面继承也不好. 2.java中多了一个接口的概念,而接口的功能和其名字表达 ...

  3. React及Nextjs相关知识点小结

    React及Nextjs知识点小结 函数式组件和类组件区别是什么 1.函数式组件是用于创建无状态的组件,组件不会被实例化,无法访问this中的对象,无法访问生命周期方法,是无副作用的,相比于类组件函数 ...

  4. Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

    Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript ...

  5. C#类继承和接口继承时一些模棱两可的问题[转]

    原文地址:http://www.cnblogs.com/harleyhu/archive/2012/11/29/2794809.html 1.在father定义的方法若含有virtual关键字,chi ...

  6. Java继承和接口

    接口最关键的作用,也是使用接口最重要的一个原因:能上溯造型至多个基础类.使用接口的第二个原因与使用抽象基础类的原因是一样的:防止客户程序员制作这个类的一个对象,以及规定它仅仅是一个接口.这样便带来了一 ...

  7. 基础学习day07---面向对象三---继承,接口与 抽象类

    一.继承 1.1.继承概念 将对象的共性抽取出来.提取出一个单独的类. 继承使用复用以前的代码非常容易,能够大大的缩短开发周期,降低开发成本,同时增加程序的易维护性 继承使重一个类A能够直接使用另外一 ...

  8. C++重要知识点小结---3

    C++重要知识点小结---1:http://www.cnblogs.com/heyonggang/p/3246631.html C++重要知识点小结---2:http://www.cnblogs.co ...

  9. java 类的继承和接口的继承

    父类 public class person { String name; int age; void eat(){ System.out.println("吃饭"); } voi ...

随机推荐

  1. 转:获取GridView中RowCommand的当前索引行

    获取GridView中RowCommand的当前索引行 前台添加一模版列,里面添加一个LinkButton 前台 (如果在后台代码中用e.CommandArgument取值的话前台代码就必须在按钮中设 ...

  2. day01_08.三大控制结构

    编程三要素:变量,运算,控制 控制: 有选择性的控制让你某部分代码执行,某部分不执行,或者来回反复执行某段代码 控制的三种基本机构:顺序,选择,循环 1.顺序 程序从上到下,顺序执行 <?php ...

  3. python踩坑系列——报错后修改了.py文件,但是依然报错

    一开始.py文件中的函数名大小写错了,但是在终端是对的,报错: 'module' object has no attribute '某函数名' 后来就去修改.py文件.结果重新import该.py文件 ...

  4. [uiautomator篇][1] 官网译文

    http://www.jianshu.com/p/7718860ec657 2016.07.25 20:59 字数 3675 Android UIAutomator浅谈 --------------- ...

  5. CodeM初赛B轮

    做什么啊,我这么菜,应该弃赛的 [编程|1500分] 子串 时间限制:3秒空间限制:32768K 题目描述 给出一个正整数n,我们把1..n在k进制下的表示连起来记为s(n,k),例如s(16,16) ...

  6. Tyk-Hybrid模式安装—抽象方法论,重用它

    最近,公司有计划运用API网关.那么,在经过权衡之后,使用了Tyk的Hybrid模式!现在环境没问题了,API调用也测通了.我得想想合并服务,监控API实时情况的东西.但在这个环境搭建的过程中,我目前 ...

  7. 异常详细信息: System.Data.SqlClient.SqlException: 用户 'NT AUTHORITY\IUSR' 登录失败解决办法

    最近在做.net项目,因为本人以前做java较多,所以对.net不熟悉,在项目完成后部署到IIS服务器上出现诸多问题,以上其中之一,若有时间,在更新其他问题的解决办法! 异常详细信息: System. ...

  8. 在Notepad++里配置python环境

    首先在语言里选择Python 然后点击运行,在弹出的对话框里输入: cmd /k cd /d "$(CURRENT_DIRECTORY)" &  python " ...

  9. Welcome-to-Swift-16自动引用计数(Automatic Reference Counting)

    Swift使用自动引用计数(ARC)来跟踪并管理应用使用的内存.大部分情况下,这意味着在Swift语言中,内存管理"仍然工作",不需要自己去考虑内存管理的事情.当实例不再被使用时, ...

  10. HDU——1286找新朋友(欧拉函数+质数打表)

    找新朋友 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...