C++——多态|多态的概念|多态的定义及实现|虚函数|多态的原理|虚函数表构成虚表的条件

简介: 笔记

多态的概念


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

1.png



多态的定义及实现


多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了

Person。Person对象买票全价,Student对象买票半价。

那么在继承中要构成多态还有两个条件:

1. 必须通过基类的指针或者引用调用虚函数

2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2.png



虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数


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


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


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


继承中要构成多态还有两个条件:

1. 必须通过基类的指针或者引用调用虚函数

2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写


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

3.png


不满足多态

4.png



当子类没有关键字virtual修饰,但父类有,此时也构成虚函数重写


特例1:子类虚函数不加virtual,依旧构成重写,实际中最好加上

5.png

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


派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指

针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解)

6.png

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


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



子类指针调用test,但test不满足虚函数重写,为什么能调到呢?


test调用需要传参,调用virtual void test(A*this),,func是A的成员函数,所以参数是A* this,test里面调用func(),其实是this->func();这里的this指向子类,因为是p->test();p是B*类型,所以打印B->


为啥val会是1?


因为虚函数重写是接口继承(主要继承接口),普通函数继承是实现继承(主要继承函数的实现),接口继承就是说子类继承父类的接口,这里用的接口是virtual void func(int val = 1)。所以会打印B->1。


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


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


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


虚函数重写跟缺省参数,形参名无任何关系。

7.png

把父类func参数改为0,此时会打印0,这是调用了父类接口 virtual void func(int val = 1),我们传了参数,所以会打印


class A
{
public:
  virtual void func(int val = 1)
  {
  std::cout << "A->" << val << std::endl;
  }
  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[])
{
  A* p = new B;
  p->test();
  return 0;
}


打印结果:B->1


注意这里P是A*,P指向B类型的空间


这里满足:父类指针或引用调用虚函数,func是虚函数重写,所以是多态。P指向B,


所以调用子类的func。

8.png

此时调用父类,因为this指向父类

9.png



多态的原理


虚函数表

10.png

大小不是4,而是8,因为除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。

创建一个对象,我们可以看到对象中含有_vfptr,我们还可以看到func1的地址被存进了虚表中,虚表是一个函数指针数组

11.png

class Person {
public:
  virtual void BuyTicket()
 { cout << "买票-全价" << endl; }
  virtual void Func()
{ cout << "Func()" << 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;
}


这里父对象和子对象的Func函数地址不一样,Buyticket地址一样,因为Buyticket被重写了

12.png

当对象调用函数的时候,会call一个地址,这个地址存在虚表中,通过地址找到虚函数。


多态的本质原理:当符合多态条件时,调用时,到指向对象的虚表中找到对应的函数地址,进行调用。


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


构成多态时调用

13.png



非多态调用函数

14.png

构成虚表的条件


虚函数不是存在虚表中,而是在代码段,虚表中存的是虚函数的地址。


当基类没有被virtual修饰,而子类被virtual所修饰,此时子类没有虚表,因为不构成虚函数。就跟调用普通函数一样。

15.png



当父类有虚函数,子类没有重写,但此时有虚表。编译器会通过虚表找到虚函数。当父类有虚函数,并且是通过父类的指针或引用调用虚函数,就会通过虚表去找该函数。此时调用的是父类的虚函数。

16.png

虚表的条件:父类函数为虚函数,是否父类指针或引用调用虚函数


目录
打赏
0
0
0
0
1
分享
相关文章
|
24天前
|
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
70 6
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
108 0
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
126 0
【C++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
51 1
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
325 4
|
6月前
|
C++入门12——详解多态1
C++入门12——详解多态1
78 2
C++入门12——详解多态1
|
6月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
82 1
|
6月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
110 1
|
6月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
75 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等