C++学习——虚函数与纯虚函数

C++学习——虚函数与纯虚函数引言:若要访问派生类中相同名字的函数,必须将基类中的同名函数定义为 虚函数,这样,将不同的派生类对象的地址赋给基类的指针变量后, 就可以动态地根据这种赋值语句调用不同类中的函数。一、虚函数的定义和使用可以在程序运行时通过调用相同的函数名而实现不同功能的 函数称为虚函数。定义格式为:virtual FuncName();一旦把基类的成员函数定义为虚函数,由基类所派生出来的所 有派生类中,…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

引言:

若要访问派生类中相同名字的函数,必须将基类中的同名函数定义为 虚函数,这样,将不同的派生类对象的地址赋给基类的指针变量后, 就可以动态地根据这种赋值语句调用不同类中的函数。

一、虚函数的定义和使用

可以在程序运行时通过调用相同的函数名而实现不同功能的 函数称为虚函数。定义格式为:
virtual FuncName();
一旦把基类的成员函数定义为虚函数,由基类所派生出来的所 有派生类中,该函数均保持虚函数的特性。
在派生类中重新定义基类中的虚函数时,可以不用关键字
virtual来修饰这个成员函数 。
虚函数是用关键字virtual修饰的某基类中的protected或public成员函数。 它可以在派生类中重新定义,以形成不同版本。只有在程序的执行过
程中,依据指针具体指向哪个类对象,或依据引用哪个类对象,才能确定激活哪一个版本,实现动态聚束。

关于虚函数,说明以下几点:

1、当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型、顺序、参数的个数必须一一对应,函数的返回的类型也相同。若函数名相同,但参数的个数不同或者参数的类型不同时,则属于函数的重载,而不是虚函数。若函数名不同,显然这是不同的成员函数。
2、实现这种动态的多态性时,必须使用基类类型的指针变量,并使该 指针指向不同的派生类对象,并通过调用指针所指向的虚函数才能实现 动态的多态性。
3、虚函数必须是类的一个成员函数,不能是友元函数,也不能是静态 的成员函数。
4、在派生类中没有重新定义虚函数时,与一般的成员函数一样,当调 用这种派生类对象的虚函数时,则调用其基类中的虚函数。

5、可把析构函数定义为虚函数,但是,不能将构造函数定义为虚函数。
6、虚函数与一般的成员函数相比较,调用时的执行速度要慢一些。为 了实现多态性,在每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现的。因此,除了要编写一些通用的程序,并一定要使用虚函数才能完成其功能要求外,通常不必使用虚函数。7、一个函数如果被定义成虚函数,则不管经历多少次派生,仍将保持 其虚特性,以实现“一个接口,多个形态”。

虚函数的访问

用基类指针访问与用对象名访问的区别:
1、用基指针访问虚函数时,指向其实际派生类对象重新定义的函数。实 现动态聚束。
2、通过一个对象名访问时,只能静态聚束。即由编译器在编译的时候决 定调用哪个函数。

二、纯虚函数

基类中不对虚函数给出有意义的实现,它只是在派生类中有具体的意义。这时基类中的虚函数只是一个入口,具体的目的地由不同的派生类中的对象决定。这个虚函数称为纯虚函数。

class <基类名>
{ virtual <类型><函数名>(<参数表>)=0;

};
1、在定义纯虚函数时,不能定义虚函数的实现部分。
2、把函数名赋值为0,本质上是将指向函数体的指针值赋为初值0。与定义空函数不一样,空函数的函数体为空,即调用该函数时,不执行任何动作。没有在派生类重新定义这种虚函数之前,是不能调用这种纯虚函数的。
3、把至少包含一个纯虚函数的类,称为抽象类。这种类只能作为派生 类的基类,不能用来创建对象。
其理由是明显的:因为虚函数没有实现部分,所以不能产生对象。但 可以定义指向抽象类的指针,即指向这种基类的指针。当用这种基类 指针指向其派生类的对象时,必须在派生类中重载纯虚函数,否则会 产生程序的运行错误。
4、在以抽象类作为基类的派生类中必须有纯虚函数的实现部分,即必 须有重载纯虚函数的函数体。否则,这样的派生类也是不能产生对象的。 综上所述,可把纯虚函数归结为:抽象类的唯一用途是为派生类提供基 类,纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多 态性。

三、补充内容

const 、volatile对象和成员函数

用const修饰的对象,只能访问该类中用const修饰的成员函数,而其它的成员函数 是不能访问的。
用volatile修饰的对象,只能访问该类中用volatile修饰的成员函数, 不能访问其它的成员函数。
当希望成员函数只能引用成员数据的值,而不允许成员函数修改数据成员的值时, 可用关键词const修饰成员函数。一旦在用const修饰的成员函数中出现修改成员数 据的值时,将导致编译错误。
const和volatile成员函数
在成员函数的前面加上关键字const,则表示这函数返回一个常量,其值不可改变。

const成员函数则是指将const放在参数表之后,函数体之前.
其一般格式为:FuncName() const ;

其语义是指明这函数的this指针所指向的对象是一个常量,即规定了const成员函数 不能修改对象的数据成员,在函数体内只能调用const成员函数,不能调用其它的成 员函数。
用volatile修饰一个成员函数时,其一般格式为:
FuncName() volatile;
其语义是指明成员函数具有一个易变的this指针,调用这个函数时,编译程序把属
于此类的所有的数据成员都看作是易变的变量,编译器不要对这函数作优化工作。

由于关键字const和volatile是属于数据类型的组成部分,因此,若在类定义 之外定义const成员函数或volatile成员函数时,则必须用这二个关键字修饰,否则 编译器认为是重载函数,而不是定义const成员函数或volatile成员函数。
指向类成员的指针
在C++中可以定义一种特殊的指针,它指向类中的成员函数或类中的数据成员。并 可通过这样的指针来使用类中的数据成员或调用类中的成员函数。

指向类中数据成员的指针变量

定义一个指向类中数据成员的指针变量的一般格式为:
ClassName:: PointName;
其中type是指针PointName所指向数据的类型,它必须是类ClassName中某一数据成 员的类型
1、指向类中数据成员的指针变量不是类中的成员,这种指针变量应在类外定义。
2、与指向类中数据成员的指针变量同类型的任一数据成员,可将其地址赋给这种指针变量,赋值
的一般格式为:
PointName = &ClassName::member;
这种赋值,是取该成员相对于该类的所在对象中的偏移量,即相对地址(距离开始位置的字节数)
如:mptr = &S::y;
表示将数据成员y的相对起始地址赋给指针变量mptr。
3、用这种指针访问数据成员时,必须指明是使用那一个对象的数据成员。当与对象结合使用时, 其用法为:
ObjectName.
PointName
4、由于这种指针变量并不是类的成员,所以使用它只能访问对象的公有数据成员。若要访问对象的
私有数据成员,必须通过成员函数来实现。
指向类中成员函数的指针变量
定义一个指向类中成员函数的指针变量的一般格式为:
(ClassName:: *PointName)();
其中PointName是指向类中成员函数的指针变量;ClassName是已定义的类名;type 是通过函数指针PointName调用类中的成员函数时所返回值的数据类型,它必须与 类ClassName中某一成员函数的返回值的类型相一致;是函数的形式参 数表。
在使用这种指向成员函数的指针前,应先对其赋值
PointName= ClassName::FuncName;
同样地,只是将指定成员函数的相对地址赋给指向成员函数的指针。
在调用时,用(对象名.指针)( )的形式。
比如 :int max( int a,int b)
{
return (a>b?a:b);
}
若有:int(*f)( int, int ); f=max;
则调用时(*f)(x,y);
所以:(s1.*mptr1)(); (s1.*mptr2)(100);
或: (ps->*mptr1)( ); (ps-*mptr2)(100);

对指向成员函数的指针变量的使用方法说明以下几点:

1、指向类中成员函数的指针变量不是类中的成员,这种指针变量应在类外定义。
2、不能将任一成员函数的地址赋给指向成员函数的指针变量,只有成员函数的参 数个数、参数类型、参数的顺序和函数的类型均与这种指针变量相同时,才能将成 员函数的指针赋给这种变量。
3、使用这种指针变量来调用成员函数时,必须指明调用那一个对象的成员函数, 这种指针变量是不能单独使用的。用对象名引用。
4、由于这种指针变量不是类的成员,所以用它只能调用公有的成员函数。若要访 问类中的私有成员函数,必须通过类中的其它的公有成员函数。
5、当一个成员函数的指针指向一个虚函数,且通过指向对象的基类指针或对象的 引用来访问该成员函数指针时,同样地产生运行时的多态性。
6、当用这种指针指向静态的成员函数时,可直接使用类名而不要列举对象名。这 是由静态成员函数的特性所确定的。

例题:

通过虚函数实现一个计算矩形面积和三角形状面积的程序。基类为形状类,派生类为矩形类和三角形类,通过基类指针指向不同的派生类对象,调用求面积函数,得到相应的面积结果,实现多态性。

(注:派生类没有成员变量,计算面积使用基类成员变量)

代码如下:

class Shape
{ 
   
public:
	double s;
	double a;
	double b;
	double h;
	double virtual SS(double x, double y)
	{ 
   
		cout << "调用了基类的函数" << endl;
		return 0;
	}
};

class Rectangle:public Shape
{ 
   
public:
	double SS(double x, double y);
};

double Rectangle::SS(double x, double y)
{ 
   
	cout << "调用了Rectangle类的函数" << endl;
	a = x;
	b = y;
	s = 1.0 / 2 * (x * y);
	cout << "三角形的面积为" << s << endl;
	return 0;
}

class Triangle:public Shape
{ 
   
public:
	double SS(double x, double y);
};

double Triangle::SS(double x, double y)
{ 
   
	cout << "调用了Triangle类的函数" << endl;
	a = x;
	h = y;
	s = x * y;
	cout << "长方体的面积为"<<s<< endl;
	return 0;
}
int main()
{ 
   
	Rectangle a;
	Triangle b;
	Shape *p;
	p = &a;
	p->SS(2.6, 6.6);
	p = &b;
	p->SS(9.9, 2.0);
	
	return 0;
}

结果如下:
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/171000.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • vue less CSS滚动条样式修改美化变细「建议收藏」

    vue less CSS滚动条样式修改美化变细「建议收藏」vuelessCSS滚动条样式修改美化变细

    2022年10月21日
  • 初学css list-style属性「建议收藏」

    初学css list-style属性「建议收藏」网上很多css布局中会看到这样的一句:list-style:none;那么list-style到底什么意思?中文即:列表样式:无;其实它是一个简写属性,包含了所有列表属性,具体包含list-sty

  • 见过仙女蹦迪吗?一起用python做个小仙女代码蹦迪视频「建议收藏」

    见过仙女蹦迪吗?一起用python做个小仙女代码蹦迪视频「建议收藏」前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCIIAnimator进行ASCII字符转换把转换的字符gif根据每

  • Spring和SpringMVC以及SpringBoot的区别

    Spring和SpringMVC以及SpringBoot的区别Spring是一个开源框架,为简化企业级应用开发而生。Spring可以是使简单的JavaBean实现以前只有EJB才能实现的功能。Spring是一个IOC和AOP容器框架。Spring容器的主要核心是:控制反转(IOC),传统的java开发模式中,当需要一个对象时,我们会自己使用new或者getInstance等直接或者间接调用构造方法创建一个对象。而在…

  • 理解面向对象的语言特点_面向对象的理解并举例

    理解面向对象的语言特点_面向对象的理解并举例前言:我们学习的javascript语言是一门面向对象的语言,所以这一概念我们需要理解与认识!下面是理解性的理论内容,不需要记忆,理解与思考我们的学习才能站在更高的视角!一、认识:面向对象是当今主

  • KAZE论文研读「建议收藏」

    KAZE论文研读「建议收藏」KAZE是发表在ECCV2012的一种特征点检测算法,相比于SIFT和SURF,KAZE建立的高斯金字塔是非线性的尺度空间,采用加性算子分裂算法(AdditiveOperatorSplitting,AOS)来进行非线性扩散滤波。一个很显著的特点是在模糊图像的同时还能保留边缘细节。邹宇华在CSDN中有一系列文章进行了讲解。AKAZE是加速版KAZE特征,即AcceleratedKAZE…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号