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. chap8-fluent python

    浅拷贝 VS 深拷贝 # In[] # list 生成一个新的引用对象,只是用alst完成初始化 alst = [1,2,3,4,5] blst=list(alst) alst.append(6) p ...

  2. 使用ntp 实现时间同步

    1.首先安装NTP [root@localhost /]# yum install ntp -y2.查看是否安装成功 rpm -qa|grep ntp [root@hadoop102 桌面]# vi ...

  3. Ansible的原理与配置

    镜像下载.域名解析.时间同步请点击 阿里云开源镜像站 Ansible原理 Ansible 是一款开源自动化平台.它是一种简单的自动化语言,能够在Ansible Playbook 中完美地描述 IT 应 ...

  4. Apache+PHP+Mysql安装手册(Linux)

    一. 检查系统环境 1.确认centos版本 [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Co ...

  5. ModSecurity的规则

    一.ModSecurity的规则 基本格式 SecRule VARIABLES OPERATOR ACTIONS SecRule:ModSecurity主要的指令,用于创建安全规则. VARIABLE ...

  6. manjaro下使用scrcpy安卓设备投屏

    scrcpy 安卓版本要大于等于 5.0 安装scrcpy及安卓工具包 yay -S scrcpy-git android-tools 打开手机 开发者模式->USB调试,将手机通过数据线连接电 ...

  7. 【编程教室】Python绘制冬奥吉祥物“冰墩墩”

    大家好,欢迎来到 Crossin的编程教室 ! 这两天,随着北京冬奥会的开幕,吉祥物"冰墩墩"可是火出了圈,多少人排长队都买不到.据说甚至有人把价格炒到了几千元. 就连昨天的&qu ...

  8. InnoDB什么时候会锁表?

    我们常常说InnoDB是行锁,但是这里介绍一下它锁表的情况. InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的.InnoDB ...

  9. InnoDB中加锁?

    InnoDB 实现了两种类型的行锁,共享锁(S)与排他锁(X).然后由于 InnoDB引擎又支持表级锁,所以它内部又有意向共享锁(IS)与意向排他锁(IX).这两种表锁,都是InnoDB内部自动处理, ...

  10. springDataRedis忽略实体指定的属性

    如果是通过 RedisRepository定义的实体,可能存在想要忽略的属性,那么,就可以 使用 org.springframework.data.annotation.Transient 注解,就可 ...