C++通过函数重载模板实现编译期多态(静态绑定),通过虚函数实现运行时多态(动态绑定)。

1.函数重载

#include <stdio.h>

int add(int a, int b)
{
return a + b;
} int add(double a, double b)
{
return a + b;
} void testAdd()
{
int sum = add(1.1, 3.2);
printf("sum:%d\n", sum);
}

以上代码用g++编译可以通过,如果用gcc编译则会报错(说明C语言不支持函数重载):

看下编译后的函数名:

可以看到,编译器在add函数名后加上了参数类型ii(int,int) dd(double,double)。

2.模板

函数模板

template <typename T>
T sub(T a, T b)
{
return a + b;
} void testSub()
{
int subi = sub(1, 3);
printf("subi:%d\n", subi);
double subd = sub(1.1, 3.3);
printf("subd:%f\n", subd);
}

这种方式只需要写一套代码,编译器会根据用户调用 生成不同的函数定义,这里生成了两个函数(int和double)

类模板

template <typename T>
class Foo
{
public:
static T mul(T a, T b)
{
return a * b;
}
}; void testMul()
{
int mul = Foo<int>::mul(2, 3);
printf("mul:%d\n", mul);
}

类模板和函数模板类似,可以先把变量类型用模板占位,编译时通过用户指定的类型来生成具体定义。

3.虚函数

class Base
{
public:
virtual void print()
{
printf("Base::print()\n");
} int m_b;
}; class Derive : public Base
{
public:
virtual void print()
{
printf("Derive::print()\n");
}
virtual void show()
{
printf("Base::show()\n");
} int m_d;
}; class Derive2 : public Base
{
public:
virtual void print()
{
printf("Derive2::print()\n");
}
}; void testVirtual()
{
Derive d;
Derive2 d2;
Base *p = &d;
p->print();
p = &d2;
p->print();
}

C++通过virtual关键字实现虚函数,运行结果如下:

可以看到,父类指针指向不同的派生类,就能够调用不同派生类的实现,这就是所谓的运行时多态。

C++对象内存布局

C/C++程序的内存格局通常分为四个区:全局数据区,代码区,栈区,堆区。

C++由于类和虚函数的存在,会复杂一点:

  • 如果是没有继承关系的普通类,那么类成员变量的内存排列和结构体一样;
  • 如果是继承的子类,那么新的成员变量位于父类的成员变量之后;
  • 如果类里只存在普通函数,普通函数位于代码区,这个类不会增加额外的空间;
  • 如果类里有virtual函数,那么编译器会在头部增加一个虚表指针,指向虚函数表,运行时通过虚函数表来调用不同的虚函数。
    比如上面的Base和Derive类,我们写一个测试函数
void testMemory()
{
Derive d;
printf("sizeof(Derive):%ld\n", sizeof(d)); d.m_b = 10;
d.m_d = 2;
printf("vtable ptr=%p, &m_b=%p, &m_d=%p\n", &d, &(d.m_b), &(d.m_d));
}

64位机器上输出结果:

可以看到虚表指针位于最开始的位置,大小是8byte。基类的成员变量m_b位于中间位置,大小是4byte。子类的成员变量m_d位于最后。

有virutal函数的每个类对象都挂着一个或多个(如果有多继承)虚函数表。
基类虚函数表存的是基类的虚函数地址:

派生类维护一套自己的虚函数表,默认和基类的虚函数地址一样。如果派生类重写了某一个虚函数,则会覆盖基类的虚函数:

现在有一个类,用到多继承(实际编码中不推荐多继承)

class Base2
{
public:
virtual void msg(){}
}; class MultiDerive : public Base, public Base2
{
public:
virtual void print()
{
printf("MultiDerive::print()\n");
}
int m_d;
}; void testMulMemory()
{
MultiDerive md;
printf("sizeof(MultiDerive):%ld\n", sizeof(md));
printf("vtable ptr=%p, &m_b=%p, &m_d=%p\n", &md, &(md.m_b), &(md.m_d));
}

运行结果如下:

基类Base的虚表指针和成员变量(int 4byte内存对齐成了8byte)位于最开始,基类Base2的虚表指针位于中间,最后是派生类MultiDerive的成员变量。
图示如下:

4.常见问题

  • 模板和虚函数能混用吗?

模板类可以包含虚函数,普通成员函数可以使用模板,代码如下:

template <typename P>
class Math1
{
public:
template <typename T>
T add(T a, T b)
{
return a + b;
} virtual int sub(int a, int b)
{
return a - b;
} P m_a;
};

virtual成员函数不能使用模板,以下代码会报错:

class Math2
{
public:
template <typename T>
virtual T add(T a, T b)
{
return a + b;
}
};


原因是:编译器需要在编译时确定虚函数表的大小,并为每一个虚函数指针分配存储空间。由于不确定模板虚函数会实例化多少个,也就没办法分配空间,所以禁止这么做。

C++怎么实现多态?的更多相关文章

  1. Java中的多态

    1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal {public abstract void Say();} 子类: public class ...

  2. C# 工厂模式+虚方法(接口、抽象方法)实现多态

    面向对象语言的三大特征之一就是多态,听起来多态比较抽象,简而言之就是同一行为针对不同对象得到不同的结果,同一对象,在不同的环境下得到不同的状态. 实例说明: 业务需求:实现一个打开文件的控制台程序的d ...

  3. C#非常重要基础之多态

    前几天看了一位同志的博客,写的是关于他自己去支付宝面试的经历.过程大体是这样的:问答的时候,前面部分,作者都应答如流,说起自己经验如何之丰富,最后面试官问了作者一个问题:请简述多态的概念和作用.结果这 ...

  4. C++多态详解

    多态是面向对象的程序设计的关键技术.多态:调用同一个函数名,可以根据需要但实现不同的功能.多态体现在两个方面,我们以前学过的编译时的多态性(函数重载)和现在我们这一章将要学习的运行时的多态性(虚函数) ...

  5. 【那些年关于java多态应用】

    1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal { public abstract void Say();} 子类: public class ...

  6. JAVA多态

    多态是指当系统A访问系统B的服务时,系统B可以通过多种方式来提供服务,而这一切对系统A是透明的.比如动物园的饲养员能够给各种各样的动物喂食.下图显示了饲养员Feeder,食物Food和动物Animal ...

  7. C#多态“说来也说”——逻辑层BLL中的多态使用

    本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址 http://www.cnblogs.com/tdws/p/5861842.html 昨天晚上,有个朋友说学了好久,依然没搞 ...

  8. java多态的理解

    面向对象语言中的类有三个特征,封装.继承.多态.封装与继承很好理解,那什么是多态呢? 1.什么是多态? 多态的定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同 ...

  9. java中如何实现多态

    复习基础知识 多态,就是重载和重写.重载发生在一个类中.重写发生在子类,意思就是子类重写父类相同名称的方法.刚学语言有的东西,不必搞得那么清楚,只有知道怎么用就行了,有的问题你要想真正把它搞得很懂,短 ...

  10. OC多态

    要点: 1.多种形态,引用的多种形态对于一个引用变量,可以指向任何类的对象.对于一个父类的引用(类与类之间有一种继承关系),可以指向子类,也可以指向本类,指向的类型不同.当通过此引用向对象发送消息,调 ...

随机推荐

  1. 阿里一面,说说你对Mysql死锁的理解

    又到了金三银四的时候,大家都按耐不住内心的躁动,我在这里给大家分享下之前面试中遇到的一个知识点(死锁问题),如有不足,欢迎大佬们指点指点. 1.什么是死锁? 死锁指的是在两个或两个以上不同的进程或线程 ...

  2. 查看mysql是否开启慢查询

    说明: slow_query_log 慢查询开启状态 slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录) l ...

  3. 【SVN】Please execute the 'Cleanup' command.

    背景 项目有个新的bug,我需要提取一个新的分支,但是提取之后,更新分支出现了这个问题 Please execute the 'Cleanup' command. 原因 由于使用SVN更新文件出错,导 ...

  4. 4月4日 python学习总结 os pickle logging

    1.序列化和反序列化 我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling. 反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickl ...

  5. springcloud学习03-spring cloud eureka(下)

    7.配置服务提供者(生产者) 7.1.配置resources/application.yml. 值eureka.client.service-url(或serviceUrl).defaultZone是 ...

  6. python 包之 xlwt 操作 excel 教程

    一.安装 pip install xlwt 二.创建表格并写入 创建表格,设置sheet名称 写入指定行列的数据,将表格进行保存 import xlwt # 创建一个workbook并设置编码 wor ...

  7. Ubuntu16.04 oh_my_zsh 安装与配置

    参考文章 Ubuntu上使终端显示Git分支(oh-my-zsh) oh-my-zsh的安装与基本配置 Ubuntu 下安装oh-my-zsh 前言 之前学习Laravel的时候,经常要切换git分支 ...

  8. java小项目

    https://blog.csdn.net/redarmy_chen/article/details/11794145#(贪吃蛇) https://blog.csdn.net/likunkun__/a ...

  9. mybatis是如何分页的,分页插件的原理是什么

    mybatis是如何分页的,分页插件的原理是什么 代码之尖关注 12018.12.28 17:11:12字数 529阅读 19,877 1. SQL 分页 <select id="qu ...

  10. 在Spring的事务体系中,事务传播特性:Required和RequiresNew有何不同?

    Required 如果当前存在一个事务,则加入当前事务.如果不存在任何事务,则创建一个新的事务.总之,要至少保证在一个事务中运行.PROPAGATION_REQUIRED通常作为默认的事务传播行为.p ...