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

简介: C++学习——虚函数与纯虚函数

引言:


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


一、虚函数的定义和使用


可以在程序运行时通过调用相同的函数名而实现不同功能的 函数称为虚函数。定义格式为:


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;
}

结果如下:


相关文章
|
3月前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
82 0
|
29天前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
|
1月前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
21 1
|
3月前
|
编译器 C++ 索引
C++虚拟成员-虚函数
C++虚拟成员-虚函数
|
4月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
4月前
|
人工智能 分布式计算 Java
【C++入门 一 】学习C++背景、开启C++奇妙之旅
【C++入门 一 】学习C++背景、开启C++奇妙之旅
|
4月前
|
存储 自然语言处理 编译器
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
|
4月前
|
小程序 C++
【C++入门 二 】学习使用C++命名空间及其展开
【C++入门 二 】学习使用C++命名空间及其展开
|
4月前
|
存储 C++ 索引
|
4月前
|
C++
C++基础知识(四:类的学习)
类指的就是对同一类对象,把所有的属性都封装起来,你也可以把类看成一个高级版的结构体。