多态

问题引出(赋值兼容性原则遇上函数重写)

    面向对象新需求

    C++提供的多态解决方案

    多态案例

    多态工程意义

        面向对象三大概念、三种境界(封装、继承、多态)

    多态成立条件

        总结条件、看代码的时候要看出多态

1多态

1.1问题引出

如果子类定义了与父类中原型相同的函数会发生什么?

函数重写

在子类中定义与父类中原型相同的函数

函数重写只发生在父类与子类之间

class
Parent

{

public:

    void print()

    {

        cout << "Parent:print() do..." << endl;

    }

};

 

class
Child : public
Parent

{

public:

    void print()

    {

        cout << "Child:print() do..." << endl;

    }

};

int main()

{

    run00();

 

    /*

    Child child;

    Parent *p = NULL;

    p = &child;

    child.print();

    child.Parent::print();

    */

 

    system("pause");

    return 0;

}

父类中被重写的函数依然会继承给子类

默认情况下子类中重写的函数将隐藏父类中的函数

通过作用域分辨符::可以访问到父类中被隐藏的函数

 

 

/*

C/C++是静态编译型语言

在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象

*/

/*

1、在编译此函数的时,编译器不可能知道指针 p 究竟指向了什么。

2、编译器没有理由报错。

3、于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。

*/

 

//面向对象新需求

//如果我传一个父类对象,执行父类的print函数

//如果我传一个子类对象,执行子类的print函数

 

 

//现象产生的原因

//赋值兼容性原则遇上函数重写 出现的一个现象

//1 没有理由报错

//2 对被调用函数来讲,在编译器编译期间,我就确定了,这个函数的参数是p,是Parent类型的。。。

//3静态链编

 

//工程开发中如何判断是不是多态存在?

 

/*

在同一个类里面能实现函数重载

继承的情况下,发生重写

重载不一定;

重写的定义

静态联编 重载是

动态联编

*/

#include
<iostream>

 

using
namespace std;

 

class
Parent

{

public:

    void print()

    {

        cout <<
"Parent:print() do..."
<< endl;

    }

};

 

class
Child : public
Parent

{

public:

    void print()

    {

        cout <<
"Child:print() do..."
<< endl;

    }

};

 

/*

1、在编译此函数的时,编译器不可能知道指针 p 究竟指向了什么。

2、编译器没有理由报错。

3、于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。

*/

 

void howToPrint(Parent* p)

{

    p->print();

}

 

void run00()

{

    Child child;

    Parent* pp = &child;

    Parent& rp = child;

 

    //child.print();

 

    //通过指针

    //pp->print();

    //通过引用

    //rp.print();

 

    howToPrint(&child);

}

int main()

{

    run00();

 

    /*

    Child child;

    Parent *p = NULL;

    p = &child;

    child.print();

    child.Parent::print();

    */

 

    system("pause");

    return 0;

}

 

1.2面向对象新需求

编译器的做法不是我们期望的

    根据实际的对象类型来判断重写函数的调用

    如果父类指针指向的是父类对象则调用父类中定义的函数

    如果父类指针指向的是子类对象则调用子类中定义的重写函数

1.3解决方案

  • C++中通过virtual关键字对多态进行支持
  • 使用virtual声明的函数被重写后即可展现多态特性

1.4多态实例

    

#include
"iostream"

using
namespace std;

 

class
HeroFighter

{

public:

 

public:

    virtual
int ackPower()

    {

        return 10;

    }

};

 

class
AdvHeroFighter : public
HeroFighter

{

public:

    virtual
int ackPower()

    {

        return
HeroFighter::ackPower() * 2;

    }

};

 

class
enemyFighter

{

public:

    int destoryPower()

    {

        return 15;

    }

};

 

//如果把这个结构放在动态库里面

//写了一个框架,可以调用

//我的第3代战机代码出现的时间晚于框架出现的时间。。。。

//框架 有使用后来人 写的代码的能力。。。

//面向对象3大概念

/*

封装

突破了C语言函数的概念。。

 

继承

代码复用 。。。。我复用原来写好的代码。。。

 

多态

多态可以使用未来。。。。。80年代写了一个框架。。。。。。90人写的代码

多态是我们软件行业追寻的一个目标。。。

////

*/

//

void objPK(HeroFighter *hf, enemyFighter *enemyF)

{

    if (hf->ackPower() >enemyF->destoryPower())

    {

        printf("英雄打败敌人。。。胜利\n");

    }

    else

    {

        printf("英雄。。。牺牲\n");

    }

}

 

void main()

{

    HeroFighter hf;

    enemyFighter ef;

 

    objPK(&hf, &ef);

 

    AdvHeroFighter advhf;

 

    objPK(&advhf, &ef);

    system("pause");

}

 

1.5多态工程意义

//面向对象3大概念

/*

封装

    突破了C语言函数的概念。。

 

继承

    代码复用 。。。。我复用原来写好的代码。。。

 

多态

    多态可以使用未来。。。。。80年代写了一个框架。。。。。。90人写的代码

    多态是我们软件行业追寻的一个目标。。。

    //写了一个框架,可以调用后来人,写的代码的能力

////

*/

1.6多态成立的条件

//间接赋值成立的3个条件

//1 定义两个变量。。。

//2 建立关联 。。。。

//3 *p

 

//多态成立的三个条件

//1 要有继承

//2 要有函数重写。。。虚函数

//3 要有父类指针(父类引用)指向子类对象

//多态是设计模式的基础,多态是框架的基础

1.7多态的理论基础

01静态联编和动态联编

1、联编是指一个程序模块、代码之间互相关联的过程。

2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。

重载函数使用静态联编。

3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。

switch 语句和 if 语句是动态联编的例子。

4、理论联系实际

1、C++与C相同,是静态编译型语言

2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。

3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象

从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。

2多态相关面试题

面试题1:请谈谈你对多态的理解

多态的实现效果

多态:同样的调用语句有多种不同的表现形态;

多态实现的三个条件

    有继承、有virtual重写、有父类指针(引用)指向子类对象。

多态的C++实现

virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用→多态的理论基础

动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。

多态的重要意义

设计模式的基础 是框架的基石。

实现多态的理论基础

函数指针做函数参数

C函数指针是C++至高无上的荣耀。C函数指针一般有两种用法(正、反)。

多态原理探究

与面试官展开讨论

 

面试题2:谈谈C++编译器是如何实现多态                    

c++编译器多态实现原理

面试题3:谈谈你对重写,重载理解

函数重载

必须在同一个类中进行

子类无法重载父类的函数,父类同名函数将被名称覆盖

重载是在编译期间根据参数类型和个数决定函数调用

函数重写

必须发生于父类与子类之间

并且父类与子类中的函数必须有完全相同的原型

使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)

多态是在运行期间根据具体对象的类型决定函数调用

 

#include
<cstdlib>

#include
<iostream>

 

using
namespace std;

 

class
Parent01

{

public:

    Parent01()

    {

        cout <<
"Parent01:printf()..do"
<< endl;

    }

public:

    virtual
void func()

    {

        cout <<
"Parent01:void func()"
<< endl;

    }

 

    virtual
void func(int
i)

    {

        cout <<
"Parent:void func(int i)"
<< endl;

    }

 

    virtual
void func(int
i, int
j)

    {

        cout <<
"Parent:void func(int i, int j)"
<< endl;

    }

};

 

class
Child01 : public
Parent01

{

 

public:

 

    //此处2个参数,和子类func函数是什么关系

    void func(int
i, int
j)

    {

        cout <<
"Child:void func(int i, int j)"
<<
" "
<<
i + j
<< endl;

    }

 

    //此处3个参数的,和子类func函数是什么关系

    void func(int
i, int
j, int
k)

    {

        cout <<
"Child:void func(int i, int j, int k)"
<<
" "
<<
i + j + k
<< endl;

    }

};

 

void run01(Parent01* p)

{

    p->func(1, 2);

}

 

int main()

{

    Parent01 p;

 

    p.func();

    p.func(1);

    p.func(1, 2);

 

    Child01 c;

    //c.func(); //问题1

    c.Parent01::func();

    c.func(1, 2);

 

    run01(&p);

    run01(&c);

 

    system("pause");

    return 0;

}

 

//问题1:child对象继承父类对象的func,请问这句话能运行吗?why

//c.func(); //因为名称覆盖,C++编译器不会去父类中寻找0个参数的func函数,只会在子类中找func函数。

 

//1子类里面的func无法重载父类里面的func

//2当父类和子类有相同的函数名、变量名出现,发生名称覆盖(子类的函数名,覆盖了父类的函数名。)

//3//c.Parent::func();

//问题2 子类的两个func和父类里的三个func函数是什么关系?

面试题4:是否可类的每个成员函数都声明为虚函数,为什么。            c++编译器多态实现原理

面试题5:构造函数中调用虚函数能实现多态吗?为什么?                c++编译器多态实现原理    

面试题6:虚函数表指针(VPTR)被编译器初始化的过程,你是如何理解的?

        c++编译器多态实现原理

面试题7:父类的构造函数中调用虚函数,能发生多态吗?                 c++编译器多态实现原理

面试题8:为什么要定义虚析构函数?

在什么情况下应当声明虚函数

  • 构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
  • 析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象

其他

父类指针和子类指针的步长

  1. 铁律1:指针也只一种数据类型,C++类对象的指针p++/--,仍然可用。
  2. 指针运算是按照指针所指的类型进行的。

    p++ <==> p=p+1 //p = (unsigned int)basep + sizeof(*p) 步长。

  3. 结论:父类p++与子类p++步长不同;不要混搭,不要用父类指针++方式操作数组。

3多态原理探究

理论知识:

  • 当类中声明虚函数时,编译器会在类中生成一个虚函数表
  • 虚函数表是一个存储类成员函数指针的数据结构
  • 虚函数表是由编译器自动生成与维护的
  • virtual成员函数会被编译器放入虚函数表中
  • 当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行howToPrint(Parent *base)函数是,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可。)
  • VPTR一般作为类对象的第一个成员

3.1 多态的实现原理

C++中多态的实现原理

当类中声明虚函数时,编译器会在类中生成一个虚函数表

虚函数表是一个存储类成员函数指针的数据结构

虚函数表是由编译器自动生成与维护的

virtual成员函数会被编译器放入虚函数表中

存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)

说明1:

通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。

说明2:

出于效率考虑,没有必要(即可以)将所有成员函数都声明为虚函数

说明3 :C++编译器,执行HowToPrint函数,不需要区分是子类对象还是父类对象

3.2如何证明vptr指针的存在

#include
<iostream>

using
namespace std;

 

class
A

{

public:

    void printf()

    {

        cout <<
"aaa"
<< endl;

    }

protected:

private:

    int a;

};

 

class
B

{

public:

    virtual
void printf()

    {

        cout <<
"aaa"
<< endl;

    }

protected:

private:

    int a;

};

 

void main()

{

    //加上virtual关键字 c++编译器会增加一个指向虚函数表的指针 。。。

    printf("sizeof(a):%d, sizeof(b):%d \n", sizeof(A), sizeof(B));

    cout <<
"hello..."
<< endl;

    system("pause");

    return;

}

3.3构造函数中能调用虚函数,实现多态吗

1)对象中的VPTR指针什么时候被初始化?

 

对象在创建的时,由编译器对VPTR指针进行初始化

只有当对象的构造完全结束后VPTR的指向才最终确定

父类对象的VPTR指向父类虚函数表

子类对象的VPTR指向子类虚函数表

2)分析过程

画图分析

 

C++复习:多态的更多相关文章

  1. C++基础 (7) 第七天 多态的原理 纯虚函数和抽象类 依赖倒置原则

    1 昨日回顾 2 多态的原理 1 要有继承 2 要有子类重写父类的虚函数 3 父类指针(或者引用)指向子类对象 (动态联编 虚函数表 3 证明vptr指针的存在 4 vptr指针在构造父类的时候是分步 ...

  2. Java基础再复习(继承、多态、方法内部类**、HashMap用法**、参数传递**)

    ###继承: package com.shiyan; public class Animal { public int legNum; //动物四肢的数量 //类方法 public void bark ...

  3. JavaSE复习日记 : 多态

    /** * 里氏替换原则 : * 能使用父类的地方,一定可以使用子类 * 什么是多态 : * 父类的引用,指向子类的对象 * 多态的前提条件 : * 有继承关系的两个类 * 多态的目的 : * ☆☆☆ ...

  4. java复习(5)---接口、继承、多态

    Java作为完全面向对象语言,接口.继承和多态是三个非常重要的概念. 1.继承. (1)关键字: extends (2)子类用super()调用父类构造函数,用super().方法 调用父类的成员方法 ...

  5. JAVA基础复习与总结<一> 对象与类的概念_内部类_继承与多态

    一.对象与类 类:类是一个模版,它描述了一类对象的行为和状态. class animal { private int color; private int size; public void eat ...

  6. 多态,封装,反射,类内置attr属性,os操作复习

    1.多态 #多态 多态是指对象如何通过他们共同的属性和动作来操作及访问,而不需要考虑他们具体的类 运行时候,多种实现 反应运行时候状态 class H2O: def __init__(self,nam ...

  7. JavaSE复习(一)继承多态与常用API

    继承与多态 在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式: 直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找 间接通过成员方法访问成员变量:该方法属于 ...

  8. C++ 基础语法 快速复习笔记(3)---重载函数,多态,虚函数

    1.重载运算符和重载函数: C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它 ...

  9. Java复习(五)接口与多态

    5.1接口 允许创建者规定方法的基本形式:方法名.参数列表以及返回类型,但不规定方法主体. 也可以包含基本数据类型的数据成员,但他们都默认为static和final 声明格式为 [接口修饰符]inte ...

随机推荐

  1. Spring MVC 原理图

    SpringMVC的工作原理图: SpringMVC流程 1.  用户发送请求至前端控制器DispatcherServlet. 2.  DispatcherServlet收到请求调用HandlerMa ...

  2. element ui中dialog相关问题

    一,今天需要在dialog里面引入另一个页面,就是打开dialog显示该页面(把页面放到dialog中),引入的语句如下: <iframe src="view?path=rkdj_b& ...

  3. Tomcat 环境部署网站. 帆软平台部署.

    主要内容. 需要使用Tomcat 部署 帆软报表平台(以下简称报表平台). 报表平台可以集成到网站, 也可独立部署. 此处是独立部署.即通过 网址:域名 独立访问这个报表平台. -- 技术要点 Tom ...

  4. prvReadAsyncOperation

    prvReadAsyncOperation privilege is the Read privilege for System Job Entity (Role Customizationtab). ...

  5. Github上关于iOS的各种开源项目集合2(强烈建议大家收藏,查看,总有一款你需要)

    资源list:Github上关于大数据的开源项目.论文等合集 Awesome Big Data A curated list of awesome big data frameworks, resou ...

  6. requests模块的cookie和代理操作

    一.基于requests模块的cookie操作 引言:有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不 ...

  7. php删除字符串最后一位

    一.前言 从数据库中select()读取一对多的信息时,经常需要将取出的数组用某个特定的字符分割,然后拼接成字符串. 常见的语法格式: foreach ($arr as $key => $val ...

  8. ASCII、Unicode和UTF-8

    转自廖雪峰的官方网站:https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/00138 ...

  9. Centos下添加用户并赋权

    创建新用户 创建一个用户名为:linuxidc [root@localhost ~]# adduser linuxidc 为这个用户初始化密码,linux会判断密码复杂度,不过可以强行忽略: [roo ...

  10. System.Data.DbType映射关系

    有如下类型的映射对照: System.Data.SqlClient.SqlDbType  System.Data.OleDb.OleDbType System.Data.Odbc.OdbcType S ...