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. python-用代码实现队列,处理斐波那契数列

    队列在进行数据操作时必须遵循"先进先出(Firstin Firstout,FIFO)"的原则,这一特点决定了队列的基本操作需要在其两端进行 队列(Queue)的基本操作通常在队列的 ...

  2. C# 将CSV转为Excel

    CSV(Comma Separated Values)文件是一种纯文本文件,包含用逗号分隔的数据,常用于将数据从一个应用程序导入或导出到另一个应用程序.通过将CSV文件转为EXCEL,可执行更多关于数 ...

  3. 动态规划优化算法——wqs二分 and 折线优化

    坑先扔着,督促自己以后来补!!!

  4. JavaScript 里的 'this' 的一般解释

    本文旨在帮助自己和大家理解 JS 里的 this, 翻译.整理并改写自本人关注的一个博主 Dmitri Pavlutin,原文链接如下: https://dmitripavlutin.com/gent ...

  5. 保姆教程系列二、Nacos实现注册中心

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 上篇我们介绍到  保姆教程系列一.Linux搭建Nacos 注册中心原理 一.环境准备 Java版本:1.8+   (Linux ce ...

  6. Hystrix相关注解?

    @EnableHystrix:开启熔断 @HystrixCommand(fallbackMethod="XXX"):声明一个失败回滚处理函数XXX,当被注解的方法执行超时(默认是 ...

  7. 使用 JDBC 操作数据库时,如何提升读取数据的性能?如 何提升更新数据的性能?

    要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的 setFetchSize() 方法指定每次抓取的记录数(典型的空间换时间策略):要提升更新数据的性能 可以使用 PreparedS ...

  8. Axure在Chrome浏览解决方案

    AXURE RP EXTENSION FOR CHROME Google Chrome浏览器需要扩展程序才能查看本地存储的项目.或者,将您的RP文件上传到Axure Cloud或使用其他浏览器.您也可 ...

  9. 如何从https://developer.mozilla.org上查询对象的属性、方法、事件使用说明和示例

    在https://developer.mozilla.org搜索要在前面加上指令 搜索之后点进去 进入之后就是这样的 在页面左边你可以选择自己要查询的对象 里面就是会有属性.方法.事件使用说明和示例.

  10. 解决IDEA包重叠在一起的问题

    问题显现: 解决方法: