教育类网站建设方案,抖音代运营工作怎么样,广州网站开发外包,滨海做网站哪家最好【c随笔13】多态 多态性#xff08;Polymorphism#xff09;在面向对象编程中是一个重要概念#xff0c;它允许以统一的方式处理不同类型的对象#xff0c;并在运行时动态确定实际执行的方法或函数。一、什么是多态性#xff1f;1、关键概念#xff1a;C的多态性2、多态定… 【c随笔13】多态 多态性Polymorphism在面向对象编程中是一个重要概念它允许以统一的方式处理不同类型的对象并在运行时动态确定实际执行的方法或函数。一、什么是多态性1、关键概念C的多态性2、多态定义3、没有 静态多态、动态多态 二、多态的详细介绍1、多态的构成条件2、覆盖override——重写3、多态构成的两个意外3.1、协变——构成多态3.2、父虚子非虚——构成多态 4、析构函数的重写1. 直接定义对象2. 使用new操作符在堆上创建对象3、结论在堆上构建对象且基类指针指向派生类的情况下如果不加virtual会发生内存泄漏派生类不会析构。 5、final C116、overrideC117、重载、覆盖、隐藏的对比 三、抽象类1、纯虚函数2、 抽象类abstract class3、抽象类指针4、- 抽象类实例化5、接口继承Interface Inheritance和实现继承Implementation Inheritance是面向对象编程中的两种继承方式。 原创作者郑同学的笔记 原创地址https://zhengjunxue.blog.csdn.net/article/details/131858812 qq技术交流群921273910
多态性Polymorphism在面向对象编程中是一个重要概念它允许以统一的方式处理不同类型的对象并在运行时动态确定实际执行的方法或函数。
一、什么是多态性
1、关键概念C的多态性
我们查看《C Primer 第5版》第15.3章节 虚函数中的介绍p537页 OOP的核心思想是多态性polymorphism。多态性这个词源自希腊语其含义是“多种形式”。我们把具有继承关系的多个类型称为多态类型因为我们能使用这些类型的“多种形式”而无须在意它们的差异。引用或指针的静态类型与动态类型不同这一事实正是C语言支持多态性的根本所在。 当我们使用基类的引用或指针调用基类中定义的一个函数时我们并不知道该函数真正作用的对象是什么类型因为它可能是一个基类的对象也可能是一个派生类的对象。如果该函数是虚函数则直到运行时才会决定到底执行哪个版本判断的依据是引用或指针所绑定的对象的真实类型。 另一方面对非虚函数的调用在编译时进行绑定。类似的通过对象进行的函数虚函数或非虚函数调用也在编译时绑定。对象的类型是确定不变的我们无论如何都不可能令对象的动态类型与静态类型不一致。因此通过对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本上。 Note当且仅当对通过指针或引用调用虚函数时才会在运行时解析该调用也只有在这种情况下对象的动态类型才有可能与静态类型不同。 2、多态定义
我们依然查看《C Primer 第5版》第15章节末尾 术语表中的介绍p576页 多态性polymorphism当用于面向对象编程的范畴时多态性的含义是指程序能通过引用或指针的动态类型获取类型特定行为的能力。 动态类型dynamic type对象在运行时的类型。引用所引对象或者指针所指对象的动态类型可能与该引用或指针的静态类型不同。基类的指针或引用可以指向一个派生类对象。在这样的情况中静态类型是基类的引用或指针而动态类型是派生类的引用或指针。 静态类型static type对象被定义的类型或表达式产生的类型。静态类型在编译时是已知的。 3、没有 静态多态、动态多态
我们看网上有很多资料介绍动态时都会提到多态分为静态多态比如函数重载等和动态多态而当我们看了上面书中的定义和介绍后会明白网上的说法是有问题的。
在c领域
只有多态、不区分静态多态和动态多态网上说的c动态多态就是指的c中的多态;网上说的静态多态不符合《C Primer 第5版》多态的概念静态多态按照《C Primer 第5版》中书写demo无法实现多态
二、多态的详细介绍
动态多态性是在运行时确定方法或函数的调用根据实际对象的类型进行动态绑定。这种多态性通过虚函数和基类指针或引用来实现。 简单来说 多态 就是多种形态不同的对象去完成同样的事情会产生不同的结果。 举个例子就拿购票系统来说不同的人对于购票这个行为产生的结果就是不同的学生购票时购买的是半价票普通人购票的时候购买的是全价票。 1、多态的构成条件
继承中想要构成多态必须满足以下两个条件
① 必须是子类的虚函数重写成父类函数重写三同 虚函数 ② 必须是父类的指针或者引用去调用虚函数。 三同指的是同函数名、同参数、同返回值。虚函数即被 virtual 修饰的类成员函数。 指针调用
#include iostream
using namespace std;class Person {
public:Person(const char* name): _name(name) {}// 虚函数virtual void BuyTicket() {cout _name : Person- 买票 全价 100 endl;}protected:string _name;
};class Student : public Person {
public:Student(const char* name): Person(name) {}// 虚函数 函数名/参数/返回 - 重写覆盖virtual void BuyTicket() {cout _name : Student- 买票 半价 50 endl;}
};class Soldier : public Person {
public:Soldier(const char* name): Person(name) {}// 虚函数 函数名/参数/返回 - 重写覆盖virtual void BuyTicket() {cout _name : Soldier- 优先买预留票 全价 100 endl;}
};/* 接收身份 */
void Pay(Person* ptr) {ptr-BuyTicket(); // 到底是谁在买票取决于传来的是谁delete ptr;
}int main()
{Person* p1 new Person(小明爸爸);Student* stu new Student(小明);Soldier* so new Soldier(小明爷爷);Pay(p1);Pay(stu);Pay(so);return 0;
}输出 引用调用
/* 接收身份 */
void Pay(Person ptr) {ptr.BuyTicket(); // 到底是谁在买票取决于传来的是谁
}int main()
{Person p1(小明爸爸);Student stu(小明);Soldier so(小明爷爷);Pay(p1);Pay(stu);Pay(so);return 0;
}2、覆盖override——重写
我们依然查看《C Primer 第5版》第15章节末尾 术语表中的介绍p576页
覆盖override派生类中定义的虚函数如果与基类中定义的同名虚函数有相同的形参列表则派生类版本将覆盖基类的版本。 覆盖也被有的文章叫做”重写“。用 virtual 虚函数并且做到函数名、参数和返回值相同就能够达到 “重写” 的效果 重写是为了将一个已有的事物进行某些改变以适应新的要求。 重写是子类对父类的允许访问的方法的实现过程进行重新编写返回值和形参都不能改变。 即“外壳不变核心重写。” 3、多态构成的两个意外
刚才说了三同虚函数就能达到重写的效果也就是多态。但是还有两个意外也能达成多态的效果。
3.1、协变——构成多态 C中的协变Covariance指的是派生类可以返回基类中相同函数签名的返回类型的子类型。 在C中当一个虚函数在基类中使用了virtual关键字声明为虚函数时派生类可以对该虚函数进行重写并且在派生类中返回类型可以是基类返回类型的子类型。这种返回类型的子类型关系称为协变。 协变的类型必须是父子关系。 观察下面的代码并没有达到 “三同” 的标准它的返回值是不同的但依旧构成多态
class A {};
class B : public A {};class Person {
public:virtual A* f() {cout virtual A* Person::f() endl;return nullptr;}
};class Student : public Person {
public:virtual B* f() {cout virtual B* Student:::f() endl;return nullptr;};
};int main(void)
{Person p;Student s;Person* ptr p;ptr-f();ptr s;ptr-f();return 0;
}输出 当class A、class B是父子关系时就不能协变 3.2、父虚子非虚——构成多态
现在来讲第二个例外。
父类的虚函数没了无法构成多态但是子类的虚函数没了却能构成多态
#include iostream
using namespace std;class A {};
class B : public A {};class Person {
public:virtual A* f() {cout virtual A* Person::f() endl;return nullptr;}
};class Student : public Person {
public:B* f() {cout virtual B* Student:::f() endl;return nullptr;};
};int main(void)
{Person p;Student s;Person* ptr p;ptr-f();ptr s;ptr-f();return 0;
}输出 4、析构函数的重写
1. 直接定义对象
#include iostream
using namespace std;class Person {
public:~Person() { //不加virtual// virtual ~Person() { //加virtualcout ~Person() endl;}
};class Student : public Person {
public:~Student() {cout ~Student() endl;}
};int main(void)
{Person p;Student s;return 0;
}加virtual输出 不加virtual输出
2. 使用new操作符在堆上创建对象
#include iostream
using namespace std;class Person {
public:~Person() { //不加virtual//virtual ~Person() { //加virtualcout ~Person() endl;}
};class Student : public Person {
public:~Student() {cout ~Student() endl;}
};int main(void)
{cout 不加virtual\n;Person *ptr new Person();delete ptr;cout \n;Student *ptr2 new Student();delete ptr2;cout \n;Person *ptr3 new Student();delete ptr3;return 0;
} 不加virtual 加virtual 刚才我们看到了如果这里不加 virtual~Student 是没有调用析构的。 这其实是非常致命的是不经意间会发生的内存泄露。 3、结论在堆上构建对象且基类指针指向派生类的情况下如果不加virtual会发生内存泄漏派生类不会析构。
5、final C11
在C中final是一个关键字用于修饰类、函数或虚函数具有不同的作用。
修饰类使用final关键字修饰类时表示该类是最终类不能被其他类继承。例如
class Base final {// ...
};class Derived : public Base { // 错误Derived不能继承自final类Base// ...
};在上述示例中Base类被声明为final因此Derived类不能继承自Base类。
修饰函数使用final关键字修饰成员函数时表示该函数是最终版本不能被派生类重写。例如
class Base {
public:virtual void func() final {// ...}
};class Derived : public Base {
public:void func() override { // 错误无法重写被声明为final的函数// ...}
};在上述示例中Base类中的func()函数被声明为final因此Derived类无法对其进行重写。
修饰虚函数与修饰普通成员函数类似使用final关键字修饰虚函数时表示该虚函数是最终版本不能被派生类重写。例如
class Base {
public:virtual void func() final {// ...}
};class Derived : public Base {
public:void func() override { // 错误无法重写被声明为final的虚函数// ...}
};在上述示例中Base类中的虚函数func()被声明为final因此Derived类无法对其进行重写。
通过使用final关键字可以显式地阻止类、函数或虚函数被继承、重写或覆盖从而提高程序的安全性和可靠性。
6、overrideC11
override是C11引入的关键字用于显式地标记派生类中对基类虚函数的重写。它的主要作用是增加代码的可读性和可维护性并提供编译器的静态检查避免错误的重写行为。
在C中当派生类要重写基类的虚函数时可以使用override关键字进行标记。通过使用override关键字可以确保派生类的函数签名与基类的虚函数完全匹配否则编译器会发出错误。这有助于及时发现错误的重写行为。
以下是使用override关键字的示例
class Base {
public:virtual void func() const {// ...}
};class Derived : public Base {
public:void func() const override {// ...}
};在上述示例中Base类中的虚函数func()被定义为virtual void func() const而在Derived类中重写的函数也被定义为void func() const并使用override关键字进行标记。如果Derived类的函数签名与基类的虚函数不匹配或者没有正确使用override关键字编译器将会报错。
7、重载、覆盖、隐藏的对比 三、抽象类
1、纯虚函数
我们依然查看《C Primer 第5版》第15章节末尾 术语表中的介绍p576页
纯虚函数pure virtual在类的内部声明虚函数时在分号之前使用了0。一个纯虚函数不需要但是可以被定义。含有纯虚函数的类是抽象基类。如果派生类没有对继承而来的纯虚函数定义自己的版本则该派生类也是抽象的。 纯虚函数是通过在函数声明后面加上 0来声明的表示该函数没有实现派生类必须重写它。 virtual void pureVirtualFunction() 0;在上述示例中纯虚函数pureVirtualFunction()。
纯虚函数是否可以实现 纯虚函数也是可以实现的
/* 抽象类 */
class Car {
public:// 实现没有价值因为压根没有对象会调用它virtual void Drive() 0 { // 纯虚函数cout Drive() endl; }
};2、 抽象类abstract class
包含纯虚函数的类就是 抽象类abstract class也叫接口类。
class AbstractClass {
public:virtual void pureVirtualFunction() 0;
};在上述示例中AbstractClass是一个抽象类它具有一个纯虚函数pureVirtualFunction()。派生类必须重写这个函数。 抽象类可以包含纯虚函数没有实现和带有实现的函数 3、抽象类指针
虽然父类是抽象类不能定义对象但是可以定义指针。
定义指针时如果 new 父类对象因为是纯虚函数自然是 new 不出来的但是可以 new 子类对象
#include iostream
using namespace std;/* 抽象类 */
class Car {
public:virtual void Drive() 0;
};class Benz : public Car {
public:virtual void Drive() {cout Benz-舒适 endl;}
};int main(void)
{Car* pBenz1 new Benz;pBenz1-Drive();Benz* pBenz2 new Benz;pBenz2-Drive();return 0;
}4、- 抽象类实例化
抽象类不能实例化出对象子类即使在继承后也不能实例化出对象除非子类重写。
5、接口继承Interface Inheritance和实现继承Implementation Inheritance是面向对象编程中的两种继承方式。
接口继承指的是一个类从一个或多个接口中继承方法声明但并不继承这些方法的具体实现。接口只包含纯虚函数在C中使用纯虚函数定义接口或者抽象方法在其他语言中。通过接口继承一个类可以实现多个接口从而表达出它具备了多个行为或功能。
实现继承指的是子类从父类中继承方法声明和实现。实现继承建立了类的层次结构允许子类继承并重用父类的代码。子类可以通过继承父类的属性和方法并且可以根据需要添加新的属性和方法甚至可以重写父类的方法来改变其行为。
普通函数的继承是一种实现继承子类继承了父类函数可以使用函数继承的是函数的实现。虚函数的继承是一种接口继承子类继承的是父类虚函数的接口目的是为了重写达成多态继承的是接口。所以如果不实现多态不要把函数定义成虚函数。出现虚函数就是为了提醒你重写的以实现多态。如果虚函数不重写那写成虚函数就没价值了。