IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++【多态】 -> 正文阅读

[C++知识库]C++【多态】

目录

一、什么是多态

多态的两个条件?

练习

?二、多态的原理


一、什么是多态

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

比方说去买票:普通成年人买普通的票,学生买半价的学生票,军人不需要排队买票

这里的普通人,学生和军人就是三个不同类型的对象。

①正常卖票 ? ? ? ?②半价买票 ? ? ? ?③优先买票

还有比方说红包

新用户要鼓励使用 所以红包多给一点 rand()%99

老用户并且经常使用?????????????????????????????rand()%2

老用户且不经常使用?????????????????????????????rand()%66

如何用代码来实现多态?

多态的实现条件是虚函数!(这个virtual是虚函数,不是上一节的虚拟继承!!)

只用成员变量才能添加virtual,变成虚函数。

在继承的时候,三同(函数名、参数、返回值)都相同的时候,就构成了虚函数重写(覆盖条件)

不符合重写,就是隐藏关系

多态的两个条件?

多态的两个条件

1、虚函数的重写

2、父类指针或者引用去调用虚函数

#include <iostream>
using namespace std;
class Person {
public:
    //只有成员函数才能添加virtual,变成虚函数
    virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

class Student : public Person {
public:
    //这里的重名的BuyTicket就是虚函数重写/覆盖(函数名,参数和返回值都要求相同)
    virtual void BuyTicket() { cout << "买票-半价" << endl; }
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/
};
class Soldier : public Person {
public:
    virtual void BuyTicket() { cout << "优先-买票" << endl; }
};
void Func(Person &P)
{
    P.BuyTicket();
}

int main()
{
    Person ps;
    Student st;
    Soldier sd;
    Func(ps);
    Func(st);
    Func(sd);
    return 0;
}

?这里我们发现只要传入的指针或引用指向的对象不同,调用的函数和方法也不同!!

1、不是父类的指针或者引用

如果我们改成这样,也就是没有使用父类指针或者引用去调用函数,我们就发现我们的多态效果就失效了。

void Func(Person P)
{
    P.BuyTicket();
}

2.不符合重写 --(不是虚函数)

我们将父类的virtual,让其不是虚函数,破坏了我们上面的条件,多态就失效了

class Person {
public:
    //只有成员函数才能添加virtual,变成虚函数
    void BuyTicket() { cout << "买票-全价" << endl;}
};

特例:子类的方法的virtual去掉了还是虚函数,因为默认是将父类的虚函数继承下来了。所以子类的函数不加virtual依旧构成重写。(实际中最好还是加上)

class Student : public Person {
public:
    void BuyTicket() { cout << "买票-半价" << endl; }
};

2.不符合重写--参数不同(三同中去掉一个)

#include <iostream>
using namespace std;
class Person {
public:
    //只有成员函数才能添加virtual,变成虚函数
    virtual void BuyTicket(int) { cout << "买票-全价" << endl;}
};

class Student : public Person {
public:
    //这里的重名的BuyTicket就是虚函数重写/覆盖(函数名,参数和返回值都要求相同)
    virtual void BuyTicket(char) { cout << "买票-半价" << endl; }
};
class Soldier : public Person {
public:
    virtual void BuyTicket(long) { cout << "优先-买票" << endl; }
};
void Func(Person &P)
{
    P.BuyTicket(1);
}

int main()
{
    Person ps;
    Student st;
    Soldier sd;
    Func(ps);
    Func(st);
    Func(sd);
    return 0;
}

?特例:重写的协变。返回值可以不同,要求必须是父子关系的指针或者引用,这样也是多态,是可以的!!!!。

#include <iostream>
using namespace std;
class Person {
public:
    //只有成员函数才能添加virtual,变成虚函数
    virtual Person* BuyTicket() {
        cout << "买票-全价" << endl;
        return this;
    }
};

class Student : public Person {
public:
    //这里的重名的BuyTicket就是虚函数重写/覆盖(函数名,参数和返回值都要求相同)
    virtual Student* BuyTicket() {
        cout << "买票-半价" << endl;
        return this;
    }
};
void Func(Person &P)
{
    P.BuyTicket();
}

int main()
{
    Person ps;
    Student st;
    Func(ps);
    Func(st);
    return 0;
}

只要是父子指针或者引用就可以,不一定是要当前类的父类和子类!,比方说下面我们的返回值就是A和B的指针也是可以构成多态的!

#include <iostream>
using namespace std;


class A {
public:
    //只有成员函数才能添加virtual,变成虚函数
    virtual A* BuyTicket() {
        cout << "买票-全价" << endl;
        return this;
    }
};

class B : public A {
public:
    //这里的重名的BuyTicket就是虚函数重写/覆盖(函数名,参数和返回值都要求相同)
    virtual B* BuyTicket() {
        cout << "买票-半价" << endl;
        return this;
    }
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/
};

class Person {
public:
    //只有成员函数才能添加virtual,变成虚函数
    virtual A* BuyTicket() {
        cout << "买票-全价" << endl;
        A a;
        return &a;
    }
};

class Student : public Person {
public:
    //这里的重名的BuyTicket就是虚函数重写/覆盖(函数名,参数和返回值都要求相同)
    virtual B* BuyTicket() {
        cout << "买票-半价" << endl;
        B b;
        return &b;
    }
};
void Func(Person &P)
{
    P.BuyTicket();
}

int main()
{
    Person ps;
    Student st;
    Func(ps);
    Func(st);
    return 0;
}

如果不是父子关系的指针或者引用就会报错

同时子类和父类的指针不能颠倒!!

#include <iostream>
using namespace std;
class Person {
public:
    //只有成员函数才能添加virtual,变成虚函数
    virtual Person BuyTicket() {
        cout << "买票-全价" << endl;
        return *this;
    }
};

class Student : public Person {
public:
    //这里的重名的BuyTicket就是虚函数重写/覆盖(函数名,参数和返回值都要求相同)
    virtual Student BuyTicket() {
        cout << "买票-半价" << endl;
        return *this;
    }
};
void Func(Person &P)
{
    P.BuyTicket();
}

int main()
{
    Person ps;
    Student st;
    Func(ps);
    Func(st);
    return 0;
}

练习

以下程序输出结果是什么()

#include <iostream>
using namespace std;
class A
{
public:
    virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
    virtual void test(){ func();}
};
class B : public A
{
public:
    void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
    B*p = new B;
    p->test();
    return 0;
}

?A: A->0

B: B->1

C: A->1

D: B->0

E: 编译出错

F: 以上都不正确

p->test()子类的指针调用test(),多态的条件没有一个是满足的,所以将虚函数当初普通函数处理(虚函数和普通函数也没啥区别,就是虚函数能完成多态的处理)

调用test,需要传参,test()(这里我们的test是无参的,所以就是将自身的this指针传进去,也就是一个A*this贺子珍)需要将A*this传递进去,调用func();是this指针调用func()(this->func());符合多态调用(父子指针)。

这个this指向谁就调用谁。而A*的后面这个this是指向子类的,因为我们的p就是指向子类的,所以调用子类的func函数。

但是这道题的子类B中虽然并没有加virtual,但是按照我们上面的说法,这个子类依旧构成重写,是我们上面讲过的一个特例,所以调用的依旧是我们B中的func(),也就是B->

然而虚函数重写是接口继承,重写实现

普通函数继承是实现继承。

这里的父类就像是一个接口,就是我们子类将父类的接口给拿过来

缺省参数一不一样是不会影响我们的继承的,但是这里的传入的类型不写是不行的。

所以我们的接口是这一段,所以默认的缺省参数也就是1!!!

(这里的p传给A*this的时候会发生切片,将B中A的部分切割出来)

B

?(不推荐写这样的代码!!!!)

?正常的话应该改成这样,这样的结果还是B->1

#include <iostream>
using namespace std;
class A
{
public:
    virtual void func(int val){ std::cout<<"A->"<< val <<std::endl;}
    virtual void test(){ func(1);}
};
class B : public A
{
public:
    void func(int val){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
    B*p = new B;
    p->test();
    return 0;
}

那如果我们将指针p改成A类型的指针,结果会是什么?

#include <iostream>
using namespace std;
class A
{
public:
    virtual void func(int val){ std::cout<<"A->"<< val <<std::endl;}
    void test(){ func(1);}
};
class B : public A
{
public:
    void func(int val){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
    A*p = new B;
    p->test();
    return 0;
}

test()处的this指针还是A* this,这里的this指针也就是p,变成了父类的指针,也就是this->func(1)

多态的两个条件

①父类指针或者引用调用虚函数(这里的this指针是父类的指针)

②func是虚函数的重写,这里就是多态调用,跟this的类型根本没有关系)指向谁调用谁,这个父类的指针p所指向的对象是子类!!!

所以我们看的是指针所指向的对象是不是子类和父类的关系,并不是指针的类型是父类的指针还是子类的指针!!!?

?

?这样写调用的才是父类

#include <iostream>
using namespace std;
class A
{
public:
    virtual void func(int val){ std::cout<<"A->"<< val <<std::endl;}
    void test(){ func(1);}
};
class B : public A
{
public:
    void func(int val){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
    A*p = new A;
    p->test();
    return 0;
}

?

?二、多态的原理

#include <iostream>
using namespace std;
class Base
{
public:
    virtual void Func1()
    {
        cout<<"Func1()"<<endl;
    }

private:
    int _b=1;
    char _ch='A';
};

int main()
{
    cout<<sizeof (Base)<<endl;
}

?这里的Base中会多存一个指针,称为虚表指针!虚函数是会进入虚表的。

虚函数是会把它的地址放入虚表的 virtual function table

所以我们这里int为四个字节,char为一个字节,这样就变成了我们的第一个8字节对齐,然后再加上一个虚表指针,就变成了16字节。

对象里面没有虚表,有的是虚表的指针

虚表的本质上是一个数组,是一个函数指针的数组。

class Person {
public:
    virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
    virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
    p.BuyTicket();
}
int main()
{
    Person Mike;
    Func(Mike);
    Student Johnson;
    Func(Johnson);
    return 0;
}

1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。
3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
4. 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。

5. 再通过下面的汇编代码分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。?

多态调用

p->BuyTicket();
// p中存的是mike对象的指针,将p移动到eax中
001940DE mov eax,dword ptr [p]
// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx
001940E1 mov edx,dword ptr [eax]
// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax
00B823EE mov eax,dword ptr [edx]
// call eax中存虚函数的指针。这里可以看出满足多态的调用,
// 不是在编译时确定的,是运行起来以后到对象的中取找的。
001940EA call eax
00头1940EC cmp esi,esp

普通调用

// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,
// 所以这里是普通函数的调用转换成地址时,
// 是在编译时已经从符号表确认了函数的地址,直接call 地址
mike.BuyTicket();
00195182 lea ecx,[mike]
00195185 call Person::BuyTicket (01914F6h)

总结:多态的本质原理,符合多态的两个条件

那么调用时,会到指向对象的虚表中找到对应的虚函数地址,进行调用。

多态调用,程序运行时去指向对象的虚表中找到函数的地址,进行调用。

普通函数的调用编译链接确定函数的地址,运行时直接调用。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:13:43  更:2022-10-17 12:16:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 23:50:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码