C++怎么实现多态?
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++怎么实现多态?的更多相关文章
- Java中的多态
1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal {public abstract void Say();} 子类: public class ...
- C# 工厂模式+虚方法(接口、抽象方法)实现多态
面向对象语言的三大特征之一就是多态,听起来多态比较抽象,简而言之就是同一行为针对不同对象得到不同的结果,同一对象,在不同的环境下得到不同的状态. 实例说明: 业务需求:实现一个打开文件的控制台程序的d ...
- C#非常重要基础之多态
前几天看了一位同志的博客,写的是关于他自己去支付宝面试的经历.过程大体是这样的:问答的时候,前面部分,作者都应答如流,说起自己经验如何之丰富,最后面试官问了作者一个问题:请简述多态的概念和作用.结果这 ...
- C++多态详解
多态是面向对象的程序设计的关键技术.多态:调用同一个函数名,可以根据需要但实现不同的功能.多态体现在两个方面,我们以前学过的编译时的多态性(函数重载)和现在我们这一章将要学习的运行时的多态性(虚函数) ...
- 【那些年关于java多态应用】
1.多态:具有表现多种形态的能力的特征 父类: public abstract class Animal { public abstract void Say();} 子类: public class ...
- JAVA多态
多态是指当系统A访问系统B的服务时,系统B可以通过多种方式来提供服务,而这一切对系统A是透明的.比如动物园的饲养员能够给各种各样的动物喂食.下图显示了饲养员Feeder,食物Food和动物Animal ...
- C#多态“说来也说”——逻辑层BLL中的多态使用
本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址 http://www.cnblogs.com/tdws/p/5861842.html 昨天晚上,有个朋友说学了好久,依然没搞 ...
- java多态的理解
面向对象语言中的类有三个特征,封装.继承.多态.封装与继承很好理解,那什么是多态呢? 1.什么是多态? 多态的定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同 ...
- java中如何实现多态
复习基础知识 多态,就是重载和重写.重载发生在一个类中.重写发生在子类,意思就是子类重写父类相同名称的方法.刚学语言有的东西,不必搞得那么清楚,只有知道怎么用就行了,有的问题你要想真正把它搞得很懂,短 ...
- OC多态
要点: 1.多种形态,引用的多种形态对于一个引用变量,可以指向任何类的对象.对于一个父类的引用(类与类之间有一种继承关系),可以指向子类,也可以指向本类,指向的类型不同.当通过此引用向对象发送消息,调 ...
随机推荐
- SQL语句优化、mysql不走索引的原因、数据库索引的设计原则
SQL语句优化 1 企业SQL优化思路 1.把一个大的不使用索引的SQL语句按照功能进行拆分 2.长的SQL语句无法使用索引,能不能变成2条短的SQL语句让它分别使用上索引. 3.对SQL语句功能的拆 ...
- Nacos 使用
Nacos(一)-下载安装 https://blog.csdn.net/qq_21067307/article/details/103895607 转载 ...
- 以B tree和B+ tree的区别来分析mysql索引实现
B树是一种多路自平衡搜索树,它类似普通的二叉树,但是B书允许每个节点有更多的子节点.B树示意图如下: Paste_Image.png B树的特点: (1)所有键值分布在整个树中 (2)任何关键字出现且 ...
- 数据库MySql的学习(1)--基本操作
转自 博客园-hoojo-http://www.cnblogs.com/hoojo/archive/2011/06/20/2085390.html 一.数据库简单介绍 1. 按照数据库的发展时间顺序, ...
- 什么是 Ribbon负载均衡?
(1)Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具. (2)Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等.简单的说,就是在配 ...
- 数据库中的ACID
参考链接:什么是ACID 数据库中的锁机制
- 学习heartbeat-04 原理及部署
1. Heartbeat介绍 1.1 Heartbeat作用 通过它可以将资源(IP及程序服务等资源)从一台故障计算机快速转移到另一台运转正常的机器继续提供服务,在实际生产应用场景中,heartbea ...
- 学习saltstack (四)
一.salt常用命令 salt 该命令执行salt的执行模块,通常在master端运行,也是我们最常用到的命令 salt [options] '<target>' <function ...
- 1. 了解Git和Github
1. 了解Git和Github 1.1 什么是Git Git是一个免费.开源的版本控制软件 1.2 什么是版本控制系统 版本控制是一种记录一个或若干个文件内容变化,以便将来查阅特定版本修订情况得系统. ...
- 线性二次型控制器(LQR)——轨迹跟踪器
1 概念 2 线性时变系统的跟踪问题 3 线性定常系统的跟踪问题 公式18--22为求解的关键 根据20.21分别求出P.g的值则通过18可求得期望的输出u 4 实例分析 5 仿真实验 先将上 ...