引言:  在c++中司空见惯的事情就是:可以通过指针和引用可以实现多态,而对象不可以。  那为什么?让我们来解开这神秘的暗纱!

1、 类对象的存储方式:

在一个类的实例中,只会存放非静态的成员变量。 如果该类中存在虚函数的话,再多加一个指向虚函数列表指针—vptr。

例如声明如下两个类,并分别实例化两个对象,它们的内存分配大致如下:(vptr具体在什么位置,与编译器有关,大多数都在开始处)

class base
{
public:
virtual ~base() {};
virtual string GetName() { return "base"; }
GetA();
int a;
}; class derived : public base
{
public:
virtual ~derived() {};
virtual string GetName() { return "derived";}
GetB();
int b;
}; base B1, B2;
derived D1, D2;

内存分布大致如下:

1. 类对象中,只有成员变量与vptr.

2. 普通成员函数在内存的某一位置放着。它们与c语言中定义的普通函数没有区别。 当我们通过对象或对象指针调用普通成员函数时, 编译器会拿到它。怎么拿到呢?当然是通过名字了,编译器都会对我们写的函数的名字进行修饰映射,让它们变成内存中唯一的函数名。

3. 无论基类还是子类,每一种类类型的虚函数表只有一份,它里面存放了基类的类型信息和指向基类中的虚函数的指针。 某一类类型的所有对象都指向了相同的虚函数表。

2.  无论通过对象还是指针,能使用的方法只与它们静态类型有关。

例如:下面的 base类型的对象B1或指针pB1,只能使用GetName() 和GetA()方法。 无论它们是如何来的!!!!!

// 直接构造得到
base B1;
base* pB1 = new base(); // 即使从子类转换而来, 通过B1或pB1也永远访问不到GetB()方法。
derived d1;
B1 = d1;
pB1 = new derived();

3.  不同类型的指针有什么区别?

本质上它们没有任何区别,在32/64位系统中都是4/8字节的一个变量。 唯一不同的就是编译器解释它们的方式,即通过指针来寻址出来的对象类型不同,大小不同 ,指针类型来告诉编译器如何解释该指针。

4. 指针与引用来实现多态

有代码如下 :

derived* _pD = new derived();
base* _pB = _pD;
_pB.GetName(); // 返回 derived.

想要知道如何通过指针来实现的多态,就要看看对基类指针赋值是发生了什么! 具体来说 如下图所示:

我们会发现,对指针的赋值,仅仅是让基类指针_pB指向的子类对象的地地址。 当我们使用基类指针调用GetName()函数(该函数是虚函数,它的地址在函数表中)时, 会由_pB指向的地址找到子类的虚函数表指针vptr_上海,再由vptr_上海在虚函数表中找到子类的GetName(),从而调用它。就这样实现了多态。

5. 对象不能实现多态

有代码如下:

base B1;
derived D1;
B1 = D1;
B1.GetName(); // 返回 base base B2 = D1
B2.GetName(); // 返回 base

上面代码中无论赋值操作还是赋值构造时, 只会处理成员变量,一个类对象里面的vptr永远不会变,永远都会指向所属类型的虚函数表,操作如下图所示:

因此,通过对象调用虚函数时,就没有必要进行动态解析了,白白增加了间接性,浪费性能。编译器直接在编译时就可以确认具体调用哪一个函数了,因此没有所谓的多态。

补充说明:

1. 引用本质上也是通过指针的解引用(即*_point)来实现的,可以<<参考std源码剖析》一本书,所以引用也可以实现多态。

2. 即使通过 基类的指针调用基类的虚函数 或 通过子类的指针调用子类的虚函数 以及通过子类指针调用基类的虚函数,  也是通过多态机制来完成的(即一步步的间接性来完成)。

3.  一个空的class的对象的大小为1个字节, 编译器之所以要这么做,是为了区别同一个类类型的不同对象!

c++中为什么可以通过指针或引用实现多态,而不可以通过对象呢?的更多相关文章

  1. <javaScript>谈谈JavaScript中的变量、指针和引用

    1.变量我们可能产生这样一个疑问:编程语言中的变量到底是什么意思呢?事实上,当我们定义了一个变量a时,就是在存储器中指定了一组存储单元,并将这组存储单元命名为a.变量a的值实际上描述的是这组存储单元中 ...

  2. 谈谈JavaScript中的变量、指针和引用

    1.变量 我们可能产生这样一个疑问:编程语言中的变量到底是什么意思呢? 事实上,当我们定义了一个变量a时,就是在存储器中指定了一组存储单元,并将这组存储单元命名为a.变量a的值实际上描述的是这组存储单 ...

  3. [转] C++中为什么要用指针,而不直接使用对象?

    原文点击这里 问题描述 我刚从 Java 转到使用 C++ 进行面向对象开发,我发现一个很让我非常困惑的问题:C++ 中经常出现使用对象指针,而不是直接使用对象本身的代码,比如下面这个例子: C++ ...

  4. C++树的插入和遍历(关于指针的指针,指针的引用的思考)

    题目 写一个树的插入和遍历的算法,插入时按照单词的字典顺序排序(左边放比它"小"的单词,右边放比它"大"的单词),对重复插入的单词进行计数. 程序源码 #inc ...

  5. Python 中的赋值、拷贝、引用

    在 python 中赋值语句总是建立对象的引用值,而不是复制对象.因此,python 变量更像是指针,而不是数据存储区域. 如图所示,当改变一个变量的值,另一个的值也会跟着改变.也就是浅拷贝. 若要实 ...

  6. C++中两个类相互包含引用问题

    在构造自己的类时,有可能会碰到两个类之间的相互引用问题,例如:定义了类A类B,A中使用了B定义的类型,B中也使用了A定义的类型 class A { int i; B b; } class B { in ...

  7. C++-const_cast只能用于指针和引用,对象的const到非const可以用static_cast

    Static_cast可以对对象也可以对指针也可以对引用,但是const_cast只可以对指针和引用使用,后者不可以对对象用,如果你要把一个const值转化为非const值只能用隐式执行或通过使用st ...

  8. Java中到底是值传递还是引用传递?

    Java中到底是值传递还是引用传递? 我们先回顾一下基本概念 实参和形参 参数在编程语言中是执行程序需要的数据,这个数据一般保存在变量中.在Java中定义一个方法时,可以定义一些参数, 举个例子: p ...

  9. C++ 中指针与引用的区别

    指向不同类型的指针的区别在于指针类型可以知道编译器解释某个特定地址(指针指向的地址)中的内存内容及大小,而void*指针则只表示一个内存地址,编译器不能通过该指针所指向对象的类型和大小,因此想要通过v ...

随机推荐

  1. IntelliJ Idea更新jsp文件后浏览器端不更新的问题

    选择war exploded进行部署 然后设置这两项为即时更新

  2. Linux启动或禁止SSH用户及IP的登录,只允许密钥验证登录模式

    启动或禁止SSH用户登录 一般情况下,在使用Linux操作系统都不会去机房来操作机器,都是使用一些第三方的工具来操作. 比如使用SSH Secure File Transfer Client工具来传输 ...

  3. JS 模仿块级作用域

    function outputNumbers(count) { for (var i=0; i<count; i++) { console.log(i); } var i;  // 重新声明变量 ...

  4. Java 动态代理 Demo

    相比于静态代理,动态代理避免了开发者编写各个繁锁的静态代理类,只需指定一组接口及目标类对象就能动态地获取代理对象. 使用动态代理的六大步骤: 1 通过实现InvocationHandler接口来自定义 ...

  5. csrf 跨站请求伪造相关以及django的中间件

    django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware来完成. 1.django中常用的中间件? - proces ...

  6. 几个常用的JS鼠标键盘事件例子

    首先普及一下鼠标左右键值的相关方法: 1.event.x 鼠标横轴  2.event.y 鼠标纵轴  3.event.keycode 键盘值  4.events.button==0 默认.没有按任何按 ...

  7. IO流(6)—转换流

    1.处理流之二:转换流 InputStreamReader和OutputStreamWriter 2.当作用的文件就是一个文本文件且使用字节流传输时,需要把它转换成字符流,再在外面加上缓冲流以加速传输 ...

  8. Linux中的普通命令如何以管理员身份运行

    set uid, gid, sticky bit 权限 一个文件都有一个所有者, 表示该文件是谁创建的.同时, 该文件还有一个组编号, 表示该文件所属的组, 一般为文件所有者所属的组.如果是一个可执行 ...

  9. Docker搭建带有访问认证的私有仓库

    2017年06月10日 17:30:26 阅读数:2581 以下步骤均为本人亲自踩坑,历经数次失败,最终搭建成功 一.环境信息: 操作系统:CentOS 7 Docker版本:1.12.5 (更高版本 ...

  10. Android 安全开发之 ZIP 文件目录遍历

    1.ZIP文件目录遍历简介 因为ZIP压缩包文件中允许存在"../"的字符串,攻击者可以利用多个"../"在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原 ...