分享

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

 流楚丶格念 2022-01-14

文章目录

引言:

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

一、虚函数的定义和使用

可以在程序运行时通过调用相同的函数名而实现不同功能的 函数称为虚函数。定义格式为:
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;
}

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

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多