【C++】面向对象编程的三大特性:深入解析多态机制(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【C++】面向对象编程的三大特性:深入解析多态机制


声明:

以下操作在x86程序中,涉及的指针都是4bytes。如果要其他平台下,部分代码选需要改动,如果是x64程序,则需要考虑指针是8bytes问题等等

一、多态概念

多态是指多种形态,完成某个行为,当不同的对象去完成时会产生出不同的状态

具体样例:买票-对于不同对象完成一件事,不同的状态

  • 成人票:30$
  • 学生票:15$
  • 军人票:优先买票

二、多态使用及实现

2.1 多态构造条件

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

构成多态需要三个条件:

  1. 多态是发生在继承关系中
  2. 必须通过基类的指针或者引用调用虚函数
  3. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2.2 虚函数

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

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

当前使用vertual与菱形虚拟继承使用virtua属于一词多义的关系

2.3 虚函数重写

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

多态对于虚函数重写态度:指向谁调用谁的虚函数,在讲述析构函数常见题将会有更深了解。

2.4 虚函数使用场景

使用事项:

  1. 父子类完成虚函数重写(三同:函数名、参数、返回值类型)
  2. 父类的指针或者引用去调用虚函数
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 ps;
    Student st;
    Func(ps);
    Func(st);
    return 0;
}

2.5 虚函数重写特殊场景

2.5.1 协变

派生类重写基类虚函数时,与基类虚函数返回值类型不同。属于父子类继承关系即可。

即基类虚函数返回值基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

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

2.5.2 析构函数重写

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字还是析构函数名不同,都与基类的析构函数构成重写。

虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor函数名

多态及其析构函数考察(常考)

class Person
{
    public:
     ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person 
{
    public:
     ~Student() { cout << "~Student()" << endl; }
};
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;
    delete p1;
    delete p2;
    return 0;
}

分析问题

如果是单纯定义Person p,Student s话,不管析构函数是不是析构函数不重要,也不会出现什么问题。但是对于上面这种父类指针类型指向开辟父类或者子类的空间,如果Person和Student析构函数没有完成虚函数的重写就会出现问题

Person 的析构函数不是虚函数,因此,如果你使用 delete p2; 释放 p2 指向的 Student 对象,只会调用 Person 类的析构函数,而不会调用 Student 类的析构函数。这样会导致Student 对象中的资源没有得到正确释放,重复析构是一种未定义的行为,可能导致内存泄漏或者程序崩退。

具体说明:

如果析构函数没有完成虚函数的重写,会根据对象的类型,去调用析构函数,因为编译器只知道这个是一个指向基类的指针。就会调用基类的析构。

这里从底层来看,编译器只能通过指针类型去推断调用对应的析构函数

  • p1->destructor + operator delete(p1)
  • p2->destructor + operator delete(p2)

2.5.3 派生类可以不加virtual

重写基类虚函数,派生类虚函数不加virtual关键字修饰,可以构成重写。由于继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性,但是该种写法不是很规范,不建议这样使用。

2.6 大坑题(深度理解)

具体解析:

  • 这里p->test调用B类对象的test函数,但是test 函数属于类 A,它被继承到了类 B 中。由于没有在类 B 中被重写,test 函数的实现仍然是类 A 中的版本,this是A*类型,很好解释了这一块。
  • 在类 A 中,func 是一个虚函数,这意味着在运行时,调用哪个版本的 func 取决于指向对象的实际类型。p 是指向 B 类对象的指针,所以 p->func() 会调用类 Bfunc 函数。
  • 这里需要注意的是,默认参数在编译时已经被绑定到函数调用上。test 函数在类 A 中定义,而 A::func(int val = 1) 使用默认参数 1,因此在 test 中调用 func() 时,将使用默认参数 1。
  • 虚函数重写,重写的是函数体的实现

三、C++ 11 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但事实有些情况下**由于疏忽,可能会导致无法构成重写,而着这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结构才来debug会得不偿失。**对此C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

引入场景:让父类不能被子类继承

C++98方法:父类构造函数设置为私有,子类的构造无法生成和实现,导致子类对象无法实例化。

class Car
{
    public:
    
    private:
    Car(){}
};
class Benz :public Car
{
    public:
};
int main()
{
  Benz b;
    return 0;
}

C++11方法:采用final关键字表示最终类

3.1 final与override使用

final:修饰虚函数,表示该虚函数不能再被重写。通俗一点比喻:老爹不给你留家底了,想子类体会下人生。

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

override:检查派生类虚函数是否重写基类某个虚函数,进行语法检查,如果没有发生重写将会编译报错。注意这个只是检查。

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

四、重载、覆盖(重写)、隐藏(重定义)区分

关于隐藏与重写语言存在重叠,可以看作重写属于隐藏的自己。因为构成重写需求比隐藏多。

五、抽象类

5.1 抽象类概念

在虚函数的后面写上 = 0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化对象。

派生类继承后也不能实例化出对象。只当重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外虚函数更体现出了接口继承。

class Car
{
    public:
    virtual void Drive() = 0;
};
class Benz : public Car
{
};
int main()
{
    Benz z;
    z.Drive();
    return 0;
}

5.2 抽象类与override区别

  • 抽象类:强制要求派生类完成虚函数的重写,否则都不能实例化对象
  • override:检查语句,帮助检查语法是否有问题,没有重写将编译器报错

六、实现继承与接口继承

实现继承:

  • 普通函数的继承属于实现继承,由派生类继承了基类函数,可调用函数,继承函数实现

接口继承:

  • 虚函数的继承属于接口继承,由派生类继承了基类虚函数的接口,目的是为了重写,其中重写是指函数体的实现,达成多态,继承的是接口

对此,如果不实现多态,不要把函数定义成虚函数。


【C++】面向对象编程的三大特性:深入解析多态机制(二)https://developer.aliyun.com/article/1617395

目录
打赏
0
1
1
1
21
分享
相关文章
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
38 12
|
12天前
|
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
32 4
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
120 1
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
160 1
PHP 8新特性解析与实战应用####
随着PHP 8的发布,这一经典编程语言迎来了诸多令人瞩目的新特性和性能优化。本文将深入探讨PHP 8中的几个关键新功能,包括命名参数、JIT编译器、新的字符串处理函数以及错误处理改进等。通过实际代码示例,展示如何在现有项目中有效利用这些新特性来提升代码的可读性、维护性和执行效率。无论你是PHP新手还是经验丰富的开发者,本文都将为你提供实用的技术洞察和最佳实践指导。 ####
89 1
iOS 14隐私保护新特性深度解析####
随着数字时代的到来,隐私保护已成为全球用户最为关注的问题之一。苹果在最新的iOS 14系统中引入了一系列创新功能,旨在增强用户的隐私和数据安全。本文将深入探讨iOS 14中的几大隐私保护新特性,包括App跟踪透明度、剪贴板访问通知和智能防追踪功能,分析这些功能如何提升用户隐私保护,并评估它们对开发者和用户体验的影响。 ####

热门文章

最新文章

推荐镜像

更多
AI助理

你好,我是AI助理

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