C++复习:多态

时间:2023-03-08 15:43:33

多态

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

    面向对象新需求

    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面向对象新需求

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

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

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

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

C++复习:多态

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 运算符正确析构动态对象

C++复习:多态C++复习:多态

其他

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

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

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

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

C++复习:多态

3多态原理探究

理论知识:

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

3.1 多态的实现原理

C++中多态的实现原理

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

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

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

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

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

C++复习:多态

C++复习:多态

C++复习:多态

说明1:

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

说明2:

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

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

C++复习:多态

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++复习:多态