您的位置:首页 > 游戏 > 游戏 > 详解c++多态---上

详解c++多态---上

2024/9/24 21:26:58 来源:https://blog.csdn.net/2301_77479435/article/details/142280838  浏览:    关键词:详解c++多态---上

目录

virtual关键字

多态的两个条件

虚函数重写的两个例外:

协变(基类与派生类虚函数返回值类型不同)

析构函数的重写(基类与派生类析构函数的名字不同)

C++11 override 和 final

final

override

重载、覆盖(重写)、隐藏(重定义)的对比

抽象类

 纯虚函数

接口继承和实现继承

vfptr

虚函数存在那?

虚函数表存在那?

代码证明

动态绑定与静态绑定

单继承和多继承关系的虚函数表

单继承中的虚函数表

函数指针的定义

 打印虚函数表

多继承中的虚函数表

多态练习题

1.子类缺省参数不起作用/虚继承

多继承中指针偏移问题

virtual关键字

1.可以修饰原函数,为了完成虚函数的重写,满足多态的条件之一。

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后
基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*//*void BuyTicket() { cout << "买票-半价" << endl; }*/
};void Func(Person& p)
{ p.BuyTicket(); }int main()
{Person ps;Student st;Func(ps);Func(st);return 0;
}

2.可以在菱形继承中,去完成虚继承,解决菱形继承数据冗余和二义性。

多态的两个条件

1.虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类 型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

2.父类对象的指针或引用(父类可以接受子类->切割/切片,子类不一定能接受父类)去调用虚函数

void Func(Person& p)
{ p.BuyTicket(); }

将&去掉后就无法满足多态。 

满足多态:跟指向的对象有关,指向那个对象就调用其虚函数。

不满足多态:跟调用对象的类型有关,类型决定其调用的虚函数。

虚函数重写的两个例外:

协变(基类与派生类虚函数返回值类型不同)

class A{};
class B : public A {};class Person {
public:virtual A* f() {return new A;}
};class Student : public Person {
public:virtual B* f() {return new B;}
}

父类写了virtual子类可以不写->但是这样的话不太标准。~~~~~~~~~~

析构函数的重写(基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的 析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规 则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处 理成destructor。

class Person{
public:virtual ~Person() {cout<<"~Person()"<<endl;}
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成
多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}
 virtual ~Student() { cout << "~Student()" << endl; }
};

 子类和父类的析构函数的函数名均会被处理为destructor。

这样使用没有问题。

 delete p2;不构成多态时,调用的指针(Person)类型决定了要调用(Person)的析构函数。

构成多态时,调用的指针指向谁,就调用谁的析构函数。

C++11 override 和 final

final

final:修饰虚函数,表示该虚函数不能再被重写

final:修饰类,表示该类不能被继承<----->构造函数私有化

override

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Car{
public:virtual void Drive(){}
};class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

重载、覆盖(重写)、隐藏(重定义)的对比

 重写时为了重写函数实现。

重写比重定义要求更严格。

抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};

 纯虚函数

virtual void Drive() = 0;

1.强制子类去完成重写。 

2.表示抽象(在现实中没有对应的实体)的类型。

接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的 继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所 以如果不实现多态,不要把函数定义成虚函数。

vfptr

虚函数表指针(简称虚表指针)。

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;
};

 虚函数表其实就是一个函数指针数组。通常以0x00000000 结束。

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

 满足多态时:

 运行时 到指向对象的虚函数表中查找对应的虚函数的地址

不满足多态时:

编译时 直接通过p的类型决定要调用函数的地址。

重写之后子类的虚函数会将父类的虚函数(virtual void BuyTicket())覆盖掉。

虚函数存在那?

普通函数和虚函数最终都会编译为指针,存在代码段。

注:虚函数不是存在虚表,虚表中存的是虚函数的指针。

虚函数表存在那?

代码段(常量区)同类型的对象共用一张虚表。

代码证明

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;
};
void func()
{//取出对象的前4个字节Base b1;printf("vfptr虚表地址: %p\n", *(int*)&b1);int i = 0;int* p1 = &i;int* p2 = new int;const char* p3 = "hello";printf("栈变量: %p\n", p1);printf("堆变量: %p\n", p2);printf("代码段常量: %p\n", p3);printf("虚函数地址: %p\n", &Base::Func1);printf("普通函数地址: %p\n", func);}

动态绑定与静态绑定

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数 重载

2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用 具体的函数,也称为动态多态。 (运行时到虚表中找虚函数地址)

单继承和多继承关系的虚函数表

单继承中的虚函数表

函数指针的定义

void(*p)();
class Base{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:int a;
};class Derive :public Base {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }
private:int b;
};

 打印虚函数表

typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* pTable)
{for (size_t i = 0; pTable[i] != 0; ++i){printf("vfTable[%d] : %p-> ", i, pTable[i]);VF_PTR f = pTable[i];f();}cout << endl;
}

多继承中的虚函数表

class Base1 {
public:virtual void func1() {cout << "Base1::func1" << endl;}virtual void func2() {cout << "Base1::func2" << endl;}
private:int b1;
};class Base2 {
public:virtual void func1() {cout << "Base2::func1" << endl;}virtual void func2() {cout << "Base2::func2" << endl;}
private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() {cout << "Derive::func1" << endl;}virtual void func3() {cout << "Derive::func3" << endl;}
private:int d1;
};}

sizeof(Derive)=20(8+8+4)

子类继承父类,会继承父类的虚表,子类直接覆盖父类的虚表即可。

char*+1---->+1

int*+1----->+4

	Derive d;PrintVFTable((VF_PTR*)(*(int*)&d));PrintVFTable((VF_PTR*)(*(int*)((char*)&d + sizeof(Base1))));

多继承时func3()往Base1的虚表里放。

多态练习题

1.子类缺省参数不起作用/虚继承

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: 以上都不正确

virtual void test(){//A* thisfunc();
}
p->test();//p->test(p);
class B : public A
{
public:void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};

子类缺省参数不起作用。

虚函数继承的是接口(函数名,参数,返回值),唯独不继承函数体。

重写指的是对其实现进行重写。

重写虚函数的继承是一种接口继承。

p->test(p);
void test(A* this);
this->func();
{ std::cout<<"B->"<< val <<std::endl; }

1. 下面哪种面向对象的方法可以让你变得富有( )

A: 继承 B: 封装 C: 多态 D: 抽象

2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的 调用则可以关联于具体的对象。

A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定(多态)

3. 面向对象设计中的继承和组合,下面说法错误的是?()

A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为 白盒复用

B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也 称为黑盒复用

C:优先使用继承,而不是组合,是面向对象设计的第二原则

D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现

4. 以下关于纯虚函数的说法,正确的是( )

A:声明纯虚函数的类不能实例化对象

B:声明纯虚函数的类是虚基类

C:子类必须实现基类的纯虚函数

D:纯虚函数必须是空函数

5. 关于虚函数的描述正确的是( )

A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型

B:内联函数不能是虚函数

C:派生类必须重新定义基类的虚函数

D:虚函数可以是一个static型的函数

6. 关于虚表说法正确的是( )

A:一个类只能有一张虚表-》多重继承

B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表

C:虚表是在运行期间动态生成的-》编译时生成,运行时初始化

D:一个类的不同对象共享该类的虚表

7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )

A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址

B:A类对象和B类对象前4个字节存储的都是虚基表的地址

C:A类对象和B类对象前4个字节存储的虚表地址相同

D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表

多继承中指针偏移问题

下面说法正确的是( )

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

#include<iostream>
using namespace std;
class A{
public:A(char *s) { cout<<s<<endl; }~A(){}
};class B:virtual public A
{
public:B(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};class C:virtual public A
{
public:C(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};class D:public B,public C
{
public:D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1){ cout<<s4<<endl;}
};int main() {D *p=new D("class A","class B","class C","class D");delete p;return 0;
}

A:class A class B class C class D

B:class D class B class C class A

C:class D class C class B class A

D:class A class C class B class D

A只能被初始化一次。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com