首页 > 编程语言 >辨析C++多态的概念

辨析C++多态的概念

时间:2024-08-19 15:55:08浏览次数:15  
标签:函数 辨析 多态 C++ virtual func 重写 public

1.什么是多态

我们都知道面向对象语言的三大特性,封装,继承,多态;

  • 封装:封装就是将数据封装在一个类里面,提供对数据更好的管控;
  • 继承:继承就是类设计层次的代码复用。

那多态是什么呢?多态是一种现象,这种现象要通过封装和继承才能实现。多态就是在同一继承体系下,不同的类的对象 调用相同的函数,表现出不同的的结果。

2.如何实现多态

2.1多态的条件

多态的构成是有条件的:

  • 条件一:子类必须完成父类中虚函数的重写(多态调用下的重写是实现重写
  • 条件二:通过父类的指针or引用调用对应的虚函数。
  • 被virtual修饰的类成员函数叫做虚函数

2.2如何重写父类中的虚函数

子类中如何完成虚函数的重写呢?虚函数的重写需要满足三同函数名相同、函数参数相同、函数返回值类型相同。注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写 (因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性) ,但是不建议这样写。多态实现示例代码:

class person
{
public:
	virtual void func()
	{
		cout << "person:" << endl;
	}
};
class student : public person
{
public:
	virtual void func()
	{
		cout << "student:" << endl;
	}
};
int main()
{
	person* ptr1 = new student;
	person* ptr2 = new person;

	ptr1->func();
	ptr2->func();

	delete ptr1;
	delete ptr2;

	return 0;
}
程序运行结果:
student:
person:

2.2.1虚函数重写的两个例外

虚函数的返回值不同:完成虚函数重写的时候,如果子类中重写的虚函数的返回值是子类对象的指针,父类中被重写的虚函数的返回值是父类对象的指针。此时,虽然父子类中虚函数的返回值不同,但是也构成重写,这种情况叫做协变。示例代码如下:

class A
{};
class B : public A
{};

class person
{
public:
	virtual A* func()
	{
		return new A;
	}
};
class student : public person
{
public:
	virtual B* func()
	{
		return new B;
	}
};

虚函数的函数名不同:只适用于父子类的析构函数;如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写;虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。示例代码如下:

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

2.2.2虚函数重写的检查 

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

final关键字:如果一个类不想被其他类继承,可以使用final关键字;同样,如果一个虚函数不想被重写,也可以使用final关键字修饰。示例代码如下:父类中的func虚函数不能被重写。

class Person
{
public:
virtual void func() final {}
};
class Student : public Person
{
public:
virtual void func() {cout << "不能重写" << endl;}
};

override关键字:如果我们明确要对哪个虚函数进行重写,需要检查是否对该虚函数进行了重写,我们可以使用override关键字帮助我们检查,示例代码如下:如果该虚函数没有被重写,会报错。

class Person
{
public:
virtual void func() {}
};
class Student : public Person
{
public:
virtual void func() override {}
};

3.抽象类

3.1抽象类简介

什么是抽象类?在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象

纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

抽象类使用代码如下:

class Person // 抽象类
{
public:
    virtual void func() = 0; // 纯虚函数
};
class Student : public Person
{
public:
    virtual void func()
    {
        cout << "hello" << endl;
    }
};

3.2接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。

虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写达成
多态
,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

4.一个例题

请问下面这段程序输出什么?

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();
	p->func();

	return 0;
}
  • p->test():p是一个子类对象的指针,调用test()函数,B类中没有实现test函数,但是,B类中继承了A类的test函数,所以调用的是A类中的test函数(注:B类继承A类,并不是说A类中的成员变量和成员方法都要拷贝一份给B类,而是B类的对象可以使用A类中的成员变量和成员方法),A类中的test函数的参数是 A* this;在test函数中,通过this指针(父类的指针)调用func函数,同时,B类中完成了func函数的重写(多态调用的重写是实现重写),多态的两个条件都满足,所以此时的func是多态调用,也就是说父类的指针指向谁就调用谁。所以调用的是B类中的func函数,那是不是输出B->0呢?不是的,因为虚函数的继承是接口继承,所以使用的还是A类中的func函数的声明。最后输出B->1。
  • p->func():此时,通过子类的指针调用重写的虚函数,不满足多态调用的所有条件,所以是普通调用,普通调用看的是指针或者引用或者对象的类型,所以此时调用的是B类中的func函数;因为不是多态调用,所以不需要使用父类中的func函数的声明。所以输出B->0。

5.区分三个概念 

重载:重载通常是指函数重载;

  • 重载的条件:同一作用域中,函数名相同,参数不同,就构成重载。

重写:重写也叫覆盖,重写是语法层的概念,覆盖是原理层的概念;

  • 重写的条件:分别在父子类的作用域中,两个函数都是虚函数,且函数名,函数参数类型,函数返回值类型都相同。(除了那两个例外)

隐藏:隐藏也叫重定义;

  • 父子类中的同名成员如果不构成重写,就构成隐藏。

标签:函数,辨析,多态,C++,virtual,func,重写,public
From: https://blog.csdn.net/D5486789_/article/details/141322060

相关文章

  • 深入理解C++多态
    目录一、引言二、多态的基本概念   1.定义   2.分类三、动态多态的实现原理   1.虚函数   2.虚函数表   3.动态绑定四、多态的应用   1.父类指针指向子类对象   2.纯虚函数与抽象类五、总结        本文将详细介绍C++多态的概念、......
  • c++ 命名空间别名踩坑
    c++命名空间别名踩坑遇到的问题在现代c++以前(c++11),实现别名的主要方式主要是通过typedef关键字实现的.今天写boost/asio代码的时候,遇到了这样的一个问题,催生了我的这篇文章加深我的印象.//我想写boost.asio中的内容,经常使用boost::asio::ip::xxxxxxx.//正是因为......
  • C++批量核验身份证真伪、实名认证接口、身份证识别
    实名认证接口是指一个系统或程序,它能够接收用户的个人信息(如姓名、身份证号码等),并与官方记录进行匹配以验证这些信息的真实性。这种接口可以集成到各种应用程序和服务中,以增强安全性并遵守相关法律法规的要求。批量核验身份证真伪以及进行实名认证是许多在线服务平台为了......
  • 迪杰斯特拉(Dijkstra)算法(C/C++)
    迪杰斯特拉(Dijkstra)算法是一种用于在加权图中找到单个源点到所有其他顶点的最短路径的算法。它是由荷兰计算机科学家艾兹格·迪科斯彻(EdsgerDijkstra)在1956年提出的。Dijkstra算法适用于处理带有非负权重的图。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法,每次遍历到始......
  • 学懂C++(三十七):深入详解C++网络编程开发
            目录一、网络编程基础概念与原理1.1套接字(Socket)1.2IP地址和端口1.3TCP/IP协议二、C++网络编程核心技术2.1套接字编程2.1.1创建套接字2.1.2绑定地址2.1.3监听和接受连接2.1.4发送和接收数据三、C++网络编程高级技术3.1异步I/O3.2多线......
  • 学懂C++(三十八):深入详解C++网络编程:套接字(Socket)开发技术
    目录一、概述与基础概念1.1套接字(Socket)概念1.2底层原理与网络协议1.2.1网络协议1.2.2套接字工作原理二、C++套接字编程核心技术2.1套接字编程的基本步骤2.2套接字编程详细实现2.2.1创建套接字2.2.2绑定地址2.2.3监听和接受连接(服务端)2.2.4客户端连接2.......
  • C++实现设计模式——Builder模式
    C++实现设计模式——Builder模式建造者模式定义建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品......
  • Linux C++ 开发4 - 入门makefile一篇文章就够了
    1.make和Makefile1.1.什么是make?1.2.什么是Makefile?1.3.make与Makefile的关系2.Makefile的语法2.1.基本语法2.2.变量2.3.伪目标2.4.模式规则2.5.自动变量2.6.条件判断3.示例演示3.1.编译HelloWorld程序3.2.编译多文件项目3.2.1.项目......
  • C++-练习-21
     题目:编写一个程序,它要求用户输入其名,然后输入其姓。然后程序使用一个逗号和空格将姓和名组合起来,并存储和显示结果。请使用string对象和头文string中的函数。源代码:#define_CRT_SECURE_NO_WARNINGS //vs版本不加这个无法使用strcat等函数#include<iostream>#include......
  • C++-练习-22
    题目:结构CandyBar包含3个成员,第一个成员存储了糖块的品牌;第二个成员存储糖块的重量(可以有小数);第三个成员存储了糖块的卡路里含量(整数)。创建一个包含3个元素的CandyBar数组(使用new来动态分配数组),并将它们初始化为所选择的值,然后显示每个结构的内容源代码:#define_CRT_SECURE_......