我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++多态的全面讲解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++多态的全面讲解

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++多态内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++多态的全面讲解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

全面讲解RAID

RAID,为RedundantArraysofIndependentDisks的简称,中文为廉价冗余磁盘阵列。RAID磁盘阵列简单的解释,就是将多台硬盘透过RAIDController(分Hardware,Software)结合成虚拟单台大容量的硬盘使用。编程学习网教育
全面讲解RAID
2024-04-23
2024-04-02

全面讲解动态路由协议之RIP配置步骤

     欢迎阅读本篇文章的小伙伴们。今天,我们就来讲一下动态路由协议之RIP配置步骤。有需要的小伙伴,可以参考一下。文章有很多自己总结的知识点,还望小伙伴们认真阅读哦!  动态路由是网络中路由器之间互相通信,传递路由信息,利用收到的路由信息更新路由表的过程。它能实时地适应网络结构的变化。
全面讲解动态路由协议之RIP配置步骤
2024-04-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录