返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++多态的全面讲解
  • 307
分享到

C++多态的全面讲解

2024-04-02 19:04:59 307人浏览 八月长安
摘要

目录1.多态的定义和实现多态的浅层理解多态的构成条件2.虚函数虚函数的重写规则虚函数重写条件的两个例外1.协变(返回值不同)2.析构函数的重写(函数名不同)3.c++11 overr

1.多态的定义和实现

多态的浅层理解

多态,即多种形态,也就是说,不同的对象在完成某个行为时会产生不同的状态。

举个例子,买火车票时,普通人正常买票,学生半价买票,军人优先买票。

在C++中,多态就是对于同一个函数,当调用的对象不同,他的操作也不同。

多态的构成条件

多态是继承体系中的一个行为,如果要在继承体系中构成多态,需要满足两个条件:

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

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

解释1:因为子类和父类的虚表各自一份,倘若能够通过对象传递的方式同时传递虚表的话,那么父类就可能拿到子类的虚表,不合理。

解释2:有虚函数就有虚函数表,对象当中就会存放一个虚基表指针,通过虚基表指针指向的内容来访问对应的函数。若子类没有重写父类的虚函数内容,则子类也会调用父类的函数。

2.虚函数

虚函数就是被virtual修饰的类成员函数(这里的virtual和虚继承的virtual虽然是同一个关键字,但是作用不一样)

class Person
{
public:
	virtual void func()
	{
		cout << "普通人->正常买票" << endl;
	}
};

虚函数的重写规则

虚函数,即被virtual关键字重写的类成员函数。

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

示例代码如下:

class Person
{
public:
	virtual void func()
	{
		cout << "普通人->正常买票" << endl;
	}
};
class Student : public Person
{
public:
	//子类必须重写父类的虚函数
	virtual void func()
	{
		cout << "学生->半价买票" << endl;
	}
};
//必须是父类的指针或引用去调用虚函数
//这里的参数类型不能是对象,否则是一份临时拷贝,则无法构成多态
void F(Person& ps)
{
	ps.func();
}
int main()
{
	Person ps;
	Student st;
	F(ps);
	F(st);
	return 0;
}

笔试选择题常考点(选自Effective C++):

如果不满足虚函数重写的条件,例如参数不同则会变成重定义。

思考如下代码输出结果:

#include <iOStream>
class Base{
public:
    virtual void Show(int n = 10)const{    //提供缺省参数值
        std::cout << "Base:" << n << std::endl;
    }
};
class Base1 : public Base{
public:
    virtual void Show(int n = 20)const{     //重新定义继承而来的缺省参数值
        std::cout << "Base1:" << n << std::endl;
    }
};
int main(){
    Base* p1 = new Base1;        
    p1->Show();           
    return 0;
}

输出的是Base1:10,

因为如果子类重写了缺省值,此时的子类的缺省值是无效的,使用的还是父类的缺省值。原因是因为多态是动态绑定,而缺省值是静态绑定。对于P1,他的静态类型也就是这个指针的类型是Base,所以这里的缺省值是Base的缺省值,而动态类型也就是指向的对象是Base1,所以这里调用的虚函数则是Base1中的虚函数,所以这里就是Base1中的虚函数,Base中的缺省值,也就是Base1:10。

简单概括就是:虚函数的重写只重写函数实现,不重写缺省值。

虚函数重写条件的两个例外

1.协变(返回值不同)

当基类和派生类的返回值类型不同时,如果基类对象返回基类对象的引用或者指针,派生类对象也返回的是派生类对象的引用或者指针时,就会引起协变。协变也能完成虚函数的重写

例1:指针

class A {};
class B :public A {};
class Person
{
public:
	virtual A* func()
	{
		cout << "virtual A* func()" << endl;
		return new A;
	}
};
class Student : public Person
{
public:
	virtual B* func()
	{
		cout << "virtual B* func()" << endl;
		return new B;
	}
};

例2:引用

class Human
{
public:
	virtual Human& print()
	{
		cout << "i am a human" << endl;
		return *this;
	}
};
class Student : public Human
{
public:
	virtual Student& print()
	{
		cout << "i am a student" << endl;
		return *this;
	}
};

2.析构函数的重写(函数名不同)

析构函数虽然函数名不同,但是也能构成重写,因为站在编译器的视角,他所调用的析构函数名称都叫做destructor

为什么编译器要通过这种方式让析构函数也能构成重写呢?

假设我用一个基类指针或者引用指向派生类对象,如果不构成多态会怎样?

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

上述代码只会调用Human的析构函数,即如果不构成多态,那么指针是什么类型的,就会调用什么类型的析构函数,这也就导致了一种情况,如果派生类的析构函数中有资源释放,而这里却没有释放掉那些资源,就会导致内存泄漏的问题。

所以为了防止这种情况,必须要将析构函数定义为虚函数。这也就是编译器将析构函数重命名为destructor的原因

class Human
{
public:
	virtual ~Human()
	{
		cout << "~Human()" << endl;
	}
};
class Student : public Human
{
public:
	virtual ~Student()
	{
		cout << "~Student()" << endl;
	}
};
int main()
{
	Human* h = new Student;
	delete h;
	return 0;
}

3.C++11 override 和 final

override

override关键字是用来检测派生类虚函数是否构成重写的关键字。

如基类虚函数没有virtual或者派生类虚函数名拼错等问题,这些问题不会被编译器检查出来,发生错误时也很难一下子定,所以C++增添了override这一层保险,当修饰的虚函数不构成重写时就会编译错误。

class A
{
public:
	virtual void func() {}
};
class B : public A
{
public:
	//未重写则报错
	virtual void func() override {};
};

final

使用final修饰的虚函数不能被重写。

如果某一个虚函数不想被派生类重写,就可以用final来修饰这个虚函数

class Human
{
public:
	virtual void print() final
	{
		cout << "i am a human" << endl;
	}
};
class Student : public Human
{
public:
	virtual void print()
	{
		cout << "i am a student" << endl;
	}
};

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

4.抽象类

虚函数后面加上=0就是纯虚函数,包含纯虚函数的类即为抽象类(接口类)。抽象类不能实例化出对象,派生类继承抽象类后若没有重写纯虚函数那么仍为抽象类,亦不能实例化出对象。纯虚函数规范了派生类必须重写虚函数,并且更加体现出了接口继承。

抽象类就像是一个蓝图,为派生类描述好一个大概的架构,派生类必须实现完这些架构,至于要在这些架构上面做些什么,增加什么,就属于派生类自己的问题。

class Human
{
public:
	virtual void print() = 0;
};
class Student : public Human
{
public:
	virtual void print()
	{
		cout << "i am a student" << endl;
	}
};
class Teacher : public Human
{
public:
	virtual void print()
	{
		cout << "i am a teacher" << endl;
	}
};

接口继承与实现继承

普通函数的继承就是接口继承,派生类可以使用基类的函数;而虚函数的重写则是实现继承,派生类继承的仅仅是基类的函数接口,目的是为了重写基类虚函数的函数体,达成多态。因此如果不实现多态,则不要将函数定义为虚函数。

到此这篇关于C++多态的全面讲解的文章就介绍到这了,更多相关C++多态内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++多态的全面讲解

本文链接: https://www.lsjlt.com/news/150574.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++多态的全面讲解
    目录1.多态的定义和实现多态的浅层理解多态的构成条件2.虚函数虚函数的重写规则虚函数重写条件的两个例外1.协变(返回值不同)2.析构函数的重写(函数名不同)3.C++11 overr...
    99+
    2024-04-02
  • C++超全面讲解多态
    目录多态的概念多态的定义及实现构成条件虚函数虚函数的重写虚函数重写的两个例外抽象类抽象类的概念接口继承和实现继承多态的原理虚函数表多态的原理多态的概念 概念:通俗的来说就是多种形态,...
    99+
    2024-04-02
  • C++:多态讲解
    多态 1.多态的概念2.多态的定义和实现2.1多态构成条件2.2虚函数2.3虚函数的重写(覆盖)2.4 C++11 override 和 final2.5重载、重写(覆盖)、隐藏(重定义)的对...
    99+
    2023-10-19
    c++ 开发语言 学习方法 笔记 经验分享
  • 超级详细讲解C++中的多态
    目录多态概念引入1、C++中多态的实现1.1 多态的构成条件1.2 虚函数1.3虚函数的重写1.4 C++11 override && final1.5 重载,覆盖(...
    99+
    2024-04-02
  • C++ smart pointer全面深入讲解
    目录我们为什么需要smart pointersmart pointer基本概念之引用计数smart pointer之shared_ptr自定义deleter(也就是自定义删...
    99+
    2024-04-02
  • Java多态的全面系统解析
    目录Java基础知识(多态)多态多态的定义和存在的必要条件多态的定义:多态存在的三个必要条件多态的案例多态的好处多态的弊端引用类型转换1. 引用类型转换是什么,为什么需要它?2. 向...
    99+
    2024-04-02
  • C++全面细致讲解复数类
    目录一、复数类应该具有的操作二、利用操作符重载三、注意事项四、小结一、复数类应该具有的操作 运算:+,- ,*,/比较:== ,! =赋值:=求模:modulus 二、利用操作符重载...
    99+
    2024-04-02
  • C语言数组全面详细讲解
    目录1.基础知识2.数组的分类2.1按元素类型分类2.2按维数分类3.数组定义和初始化3.1 一维数组3.2 二维数组4.数组元素的引用方法5.字符数组的定义1.基础知识 C语言中使...
    99+
    2024-04-02
  • C语言各类操作符全面讲解
    目录1 算术操作符2 移位操作符3 位操作符4 赋值操作符5 单目操作符5.1 ! 逻辑反操作5.2 sizeof 和数组6 关系操作符7 逻辑操作符8 条件操作符9 逗号表达式10...
    99+
    2024-04-02
  • C语言全面细致讲解文件操作
    目录什么是文件程序文件数据文件文件名文件指针文件的打开和关闭文件的顺序读写字符输入输出函数字符串输入输出函数(fgets,fputs)格式化输入输出函数(fscanf,fprintf...
    99+
    2024-04-02
  • C语言超全面讲解字符串函数
    目录1、gets函数2、puts函数3、strcat函数4、strcpy函数(strncpy函数)5、strcmp函数(strncmp函数)6、strlen函数7、strlwr函数8...
    99+
    2024-04-02
  • C++全面覆盖内存管理知识讲解
    目录前言一、C++内存管理方式1.1new/delete操作内置类型二、operator new与operator delete函数2.1operator new与oper...
    99+
    2024-04-02
  • 【C++】面向对象---多态(万字详解)
           🔥🔥 欢迎来到小林的博客!!       🛰️博客主页:✈️小林爱敲代码       🛰️文章专栏:✈️小林的C++之...
    99+
    2023-09-01
    c++ java 开发语言
  • C#多态详解
    目录1.定义2.实现多态的两个因素3.案例4、使用多态的好处5、string类1、值类型2、引用类型6.运算符重载:总结1.定义 多态是同一个行为具有多个不同表现形式或形态的能力。 ...
    99+
    2024-04-02
  • C语言全面讲解顺序表使用操作
    目录一、顺序表的结构定义二、顺序表的结构操作1.初始化2.插入操作3.删除操作4.扩容操作5.释放操作6.输出三、示例编程环境为 ubuntu 18.04。 顺序表需要连续一片存储空...
    99+
    2024-04-02
  • C语言超全面讲解函数的使用方法上
    目录一、函数的分类1.库函数2.自定义函数3.库函数的分类二、函数调用1.传值调用2.传址调用三、函数的声明四、函数定义 五、函数的参数1.形式参数(形参)2.实际参数(实...
    99+
    2024-04-02
  • C语言超全面讲解函数的使用方法下
    目录一、函数的嵌套调用二、函数的链式访问三、函数递归递归的优缺点必要条件使用场景函数递归的细节说明 举例说明对两个必要条件的理解四、递归练习C语言超全面讲解函数的使用方法上...
    99+
    2024-04-02
  • 全面讲解CocosCreator热更新
    目录前言什么是热更cocos热更概述manifest工程资源和游戏包内资源的区别searchPaths搜索路径cocos的基础热更流程_localManifest:当前包内的mani...
    99+
    2024-04-02
  • Golang超全面讲解并发
    目录1. goroutine1.1 定义1.2 goroutine切换点2. channel2.1 语法2.2 channel作为参数2.3 channel作为返回值2.4 chan...
    99+
    2024-04-02
  • Java超详细讲解多态的调用
    概念:多态是什么它就相当于区别对待,比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优 先买票。再者就是再举个详细的例子: 最近为了争夺在线支付市场...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作