1、虚函数的定义

  • 虚函数就是在基类中被关键字 virtual 说明,并在派生类中重新定义的函数。
  • 虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
  • 虚函数的定义是在基类中进行的,它是在基类中在那些需要定义为虚函数的成员函数的声明中冠以关键字 virtual 。定义虚函数的方法如下:
virtual 函数类型 函数名(形参表){函数体;
}

  在基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义。在派生类中重新定义时,其函数原型,包括函数类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同。

例 1:虚函数的使用

#include<iostream>
using namespace std;
class B0{public:virtual void print(char *p){    //定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B1:public B0{public:virtual void print(char *p){  //重新定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B2:public B1{public:virtual void print(char *p){  //重新定义虚函数 print cout<<p<<"print()"<<endl;}
};
int main(){B0 ob0,*op;  //定义基类对象 ob0 和对象指针 opop=&ob0; op->print("B0::");    //调用基类 B0 的 print B1 ob1;  //定义派生类 B1 的对象 op=&ob1;op->print("B1::");  //调用派生类 B1 的 print B2 ob2;op=&ob2;op->print("B2::");return 0;
}

执行结果:

说明:
(1)若在基类中,只声明虚函数原型(需加上 virtual),而在类外定义虚函数时,则不必再加 virtual。例如:

class B0{public:virtual void print(char *p);     //声明虚函数原型,需加上 virtual
};

在类外,定义虚函数时,不要加 virtual:

void B0::print(char *p){cout<<p<<"print()"<<endl;
}

(2)在派生类中,虚函数被重新定义时,其函数的原型与基类中的函数原型(即包括函数类型、函数名、参数个数、参数类型的顺序)都必须完全相同。
(3)C++ 规定,当一个成员函数被定义为虚函数后,其派生类中符合重新定义虚函数要求的同名函数都自动称为虚函数。因此,在派生类中重新定义该虚函数时,关键字 virtual 可以不写。 但是,为了使程序更加清晰最好在每一层派生类中定义该函数时都加上关键字 virtual。
(4)如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数。一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。 例如:

class B0{···public:virtual void show();  //在基类定义 show 为虚函数
};
class B1:public B0{···
};

若在公有派生类 B1 中没有重新定义虚函数 show ,则函数 show 在派生类中被继承,仍是虚函数。
(5)虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。
(6)使用对象名和点运算符的方式调用虚函数是在编译时进行的,是静态联编,没有利用虚函数的特性。只有通过基类指针访问虚函数时才能获得运行时的多态性。

例 2:使用对象名和点运算符的方式调用虚函数

#include<iostream>
using namespace std;
class B0{public:virtual void print(char *p){    //定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B1:public B0{public:virtual void print(char *p){cout<<p<<"print()"<<endl;}
};
class B2:public B1{public:virtual void print(char *p){cout<<p<<"print()"<<endl;}
};
int main(){B0 ob0;ob0.print("B0::");B1 ob1;ob1.print("B1::");B2 ob2;ob2.print("B2::");return 0;
}

2、虚析构函数

  在 C++ 中,不能声明虚构造函数,但是可以声明虚析构函数。
  https://blog.csdn.net/aaqian1/article/details/84915540 中介绍了先执行派生类的析构函数,再执行基类的析构函数。

例 3:虚析构函数的引例 1:

#include<iostream>
using namespace std;
class B{public:~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){D obj;return 0;
}

  本程序运行结果符合预想,即先执行派生类的析构函数,再执行基类的析构函数。但是,如果在主函数中用 new 运算符建立一个派生类的无名对象和定义了一个基类的对象指针,并将无名对象的地址赋给这个对象指针。当用 delete 运算符撤销无名对象时,系统只执行基类的析构函数,而不执行派生类的析构函数。

例 4:虚析构函数的引例2

#include<iostream>
using namespace std;
class B{public:~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){B *p;    //定义指向基类 B 的指针变量 pp=new D;
//用运算符 new 为派生类的无名对象动态地分配了一个存储空间,并将地址赋给对象指针 pdelete p;
//用 delete 撤销无名对象,释放动态存储空间return 0;
}

执行结果:

  当撤销指针 P 所指的派生类的无名对象,而调用析构函数时,采用了静态联编方式,只调用了基类 B 的析构函数。
  如果希望程序执行动态联编方式,在用 delete 运算符撤销派生类的无名对象时,先调用派生类的析构函数,再调用基类的析构函数,可以将基类的析构函数声明为虚析构函数。

例 5:虚析构函数的使用

#include<iostream>
using namespace std;
class B{public:virtual ~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:virtual ~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){B *p;    //定义指向基类 B 的指针变量 pp=new D;
//用运算符 new 为派生类的无名对象动态地分配了一个存储空间,并将地址赋给对象指针 pdelete p;
//用 delete 撤销无名对象,释放动态存储空间return 0;
}

  由于使用了虚析构函数,程序执行了动态联编,实现了运行的动态性。虽然派生类的析构函数与基类的析构函数名字不相同,但是如果将基类的析构函数定义为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数。

3、虚函数与重载函数的关系

  在一个派生类中重新定义基类的虚函数是函数重载的另一种形式,但它不同于一般的函数重载。
  当普通的函数重载时,其函数的 参数参数类型 有所不同,函数的 返回类型 也可以不同。但是,当重载一个虚函数时,即在派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同。①如果仅仅返回类型不同,其余均相同,系统会给出错误信息;②若仅仅函数名相同,而参数的个数,类型或顺序不同,系统将它作为普通的函数重载,这时虚函数的特性将丢失。

例 6:虚函数与重载函数的关系

#include<iostream>
using namespace std;
class Base{public:virtual void fun1();virtual void fun2();virtual void fun3();void fun4();
};
class Derived:public Base{public:virtual void fun1();   //fun1 是虚函数,这里可不写 virtual void fun2(int x);      //与基类中的 fun2 作为普通函数重载,虚特性消失//    char fun3();            //错误,因为与基类只有返回类型不同,应删去void fun4();
};
void Base::fun1(){cout<<"---Base fun1---"<<endl;
}
void Base::fun2(){cout<<"---Base fun2---"<<endl;
}
void Base::fun3(){cout<<"---Base fun3---"<<endl;
}
void Base::fun4(){cout<<"---Base fun4---"<<endl;
}
void Derived::fun1(){cout<<"---Derived fun1---"<<endl;
}
void Derived::fun2(int x){cout<<"---Derived fun2---"<<endl;
}
/*
void Derived::fun3(){cout<<"---Derived fun3---"<<endl;
}*/
void Derived::fun4(){cout<<"---Derived fun4---"<<endl;
}
int main(){Base d1,*bp;Derived d2;bp=&d2;bp->fun1();bp->fun2();bp->fun4();return 0;
}

执行结果:

4、多重继承与虚函数

  多重继承可以视为多个单继承的组合。因此,多重继承情况下的虚函数调用与单继承情况下的虚函数调用有相似之处。

例 7:多重继承与虚函数的例子

#include<iostream>
using namespace std;
class Base1{public:virtual void fun(){      //定义 fun 是虚函数 cout<<"--Base1--\n";}
};
class Base2{public:void fun(){      //定义 fun 是普通的成员函数 cout<<"--Base2--\n";}
};
class Derived:public Base1,public Base2{public:void fun(){cout<<"--Derived--\n";}
};
int main(){Base1 *ptr1; //定义指向基类 Base1 的对象指针 ptr1Base2 *ptr2;   //定义指向基类 Base2 的对象指针 ptr2Derived obj3;  //定义派生类 Derived 的对象 obj3 ptr1=&obj3;       ptr1->fun();
//此处的 fun为虚函数,因此调用派生类 Derived 的虚函数 funptr2=&obj3;ptr2->fun();
//此处的 fun为非虚函数,而 ptr2 为类 Base2 的对象指针,因此调用基类 Base2 的函数 funreturn 0;
}

执行结果:
  相对于 Base1 的派生路径,由于 Base1 中的 fun 是虚函数,当声明为指向 Base1 的指针指向派生类 Derived 的对象 obj3 时,函数 fun 呈现出虚特性。
  相对于 Base2 的派生路径,由于 Base2 中的 fun 是
一般成员函数,所以此时它只能是一个普通的重载函数,当声明为指向 Base2 的指针指向 Derived 的对象 obj3 时,函数 fun 只呈现普通函数的重载特性。

5、虚函数举例

例 8:应用 C++ 的多态性,计算三角形、矩形和圆的面积。

#include<iostream>
using namespace std;
class Figure{   //定义一个公共基类 protected:double x,y;public:Figure(double a,double b){x=a;y=b;}virtual void area(){    //定义一个虚函数,作为界面接口 cout<<"在基类中定义的虚函数,";cout<<"为派生类提供一个公共接口,";cout<<"以便派生类根据需要重新定义虚函数。";}
};
class Triangle:public Figure{   //定义三角形派生类 public:Triangle(double a,double b):Figure(a,b){  //构造函数 }void area(){    //虚函数重新定义,用作求三角形的面积 cout<<"三角形的高是:"<<x<<",底是:"<<y;cout<<",面积是:"<<0.5*x*y<<endl;}
};
class Square:public Figure{public:Square(double a,double b):Figure(a,b){}void area(){   //虚函数重新定义,用作求矩形的面积 cout<<"矩形的长是:"<<x<<",宽是:"<<y<<",面积是:"<<x*y<<endl; }
};
class Circle:public Figure{     //定义圆派生类public:Circle(double a):Figure(a,a){}void area(){cout<<"圆的半径是:"<<x<<",面积是:"<<3.1416*x*x<<endl;}
};
int main(){Figure *p;Triangle t(10.0,6.0);Square s(10.0,6.0);Circle c(10.0);p=&t;p->area();p=&s;p->area();p=&c;p->area();return 0;
}

运行结果:

虚函数 2 之虚函数的定义相关推荐

  1. c++中虚函数和纯虚函数定义

    只有用virtual声明类的成员函数,使之成为虚函数,不能将类外的普通函数声明为虚函数.因为虚函数的作用是允许在派生类中对基类的虚函数重新定义.所以虚函数只能用于类的继承层次结构中. 一个成员函数被声 ...

  2. C++ 虚函数与存虚函数

    什么是虚函数: 虚函数 是在基类中使用关键字 virtual 声明的函数,在C++ 语言中虚函数可以继承,当一个成员函数被声明为虚函数之后,其派生类中的同名函数都自动生成为虚函数, 虚函数主要体验C+ ...

  3. 一口气搞懂《虚函数和纯虚函数》

    学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...

  4. 析构函数和虚函数、纯虚函数

    置于"-"是析构函数:析构函数因使用"-"符号(逻辑非运算符),表示它为逆构造函数,加上类名称来定义.  析构函数也是特殊的类成员函数,它没有返回类型,没有参数 ...

  5. C++知识点51——虚函数与纯虚函数(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109957146 10.练习 示例 class base { public:base() ...

  6. C++知识点50——虚函数与纯虚函数(上)

    一.虚函数 1.如果一个基类希望基类中的某些成员函数在子类中实现子类的自定义版本,就可以将该成员函数定义为virtual. 2.当使用基类的指针或引用调用一个虚函数时,将会发生动态绑定(在运行时根据指 ...

  7. 虚函数与纯虚函数的区别

    虚函数:为了方便使用多态特性,常常需要在基类中定义虚函数. 纯虚函数: 1.原因与虚函数相同: 2.在很多情况下,基类本身生成的对象是不合理的: 虚函数与纯虚函数的区别: 1.类里声明为虚函数的话,这 ...

  8. C++中的虚函数与纯虚函数

    文章目录 1 C++中的虚函数 1.1 虚函数 1.2 单个类的虚函数表 1.3 使用继承的虚函数表 1.4 多重继承的虚函数表 2 C++中的纯虚函数 1 C++中的虚函数 1.1 虚函数 虚函数的 ...

  9. 【c++】28.虚析构函数、纯虚函数

    1.虚函数:在类的成员函数前面加virtual关键字的函数: 一般把虚函数定义在public区,方便在主函数中调用 如果一个类有一个虚函数,则该类就有一个虚函数列表,所有该类的对象都共享这个虚函数表: ...

最新文章

  1. 灵玖软件大数据采集技术提高出版行业效率
  2. 某计算机型号,某计算机的型号为486/33,其中33的含义是?
  3. Git 之一 起源、安装、配置
  4. mysql日志管理_关于MySQL的日志管理(binlog)
  5. Taro+react开发(86):资源文件处理
  6. python except用法和作用_Python面试题(部分附带面试标准答案) 建议收藏
  7. mlag 堆叠_S-MLAG解决方案介绍
  8. 《大数据》2015年第2期“研究”——大数据时代的数据传输网
  9. python风格迁移_图像风格迁移实战(附Python实战)
  10. 怎么利用pytorch训练好的模型测试单张图片
  11. Visual Studio 2022自定义(透明)主题和壁纸完整版
  12. (已更新)成语小秀才小程序V2.0.14完整安装包+小程序前端
  13. 6-机器学习之KNN(K-近临算法)
  14. 然而大部分工程师的期权并没有什么用
  15. 【Python数据分析学习实例】计算某个函数的一阶导数、二阶导数,并绘出图像
  16. 游戏合作伙伴专题:BreederDAO 与 Mech 一起加入战斗
  17. 【Python学习】transpose函数
  18. QT Creator 输入中文变繁体的解决方法
  19. 六、图(上):六度空间
  20. 数据通信基础(1)-数据通信概念、通信系统模型及通信方式

热门文章

  1. sas中数据输入输出格式
  2. OpenXR+Runtime:OpenXR SDK与Runtime的衔接
  3. 微信小程序rotateZ实现卡片翻转
  4. drf-路由组件:自动生成Routers路由、 使用方法、视图集中附加action的声明、自动生成路由router的两种方式的URL区别
  5. 面试问题中的十大算法
  6. java利用qrcode生成带有logo的二维码(logo位置及大小自己调)
  7. unipush+java+个推实现app消息推送
  8. 机器学习(八):CS229ML课程笔记(4)——生成学习,高斯判别分析,朴素贝叶斯
  9. 单核性能强的服务器cpu,Cpu单核性能强和多核性能强都有什么用?
  10. 基于风光储能和需求响应的微电网日前经济调度(Python代码实现)