博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++
座右铭:“不要等到什么都没有了,才下定决心去做”
大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点
目录
虚函数
虚函数的定义和作用
虚函数的注意事项
虚析构函数
纯虚函数
抽象类
虚函数允许实现与函数体之间的联系在运行时建立,也就是在运行时才决定如何动作,即所谓的功能晚绑定虚函数的定义和作用
虚函数的定义是在基类中进行的,在成员函数原型的声明语句之前加上关键字virtual,从而提供一种接口。一般成员函数的定义语法:
virtual 返回类型 函数名(参数列表) { 函数体; }
当基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义。在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型的顺序,都必须与基类中的原型完全相同。
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过指向基类对象的指针或基类对象的引用来访问基类和派生类的同名函数。
例如:
#include
using namespace std; class Wood { public: virtual void show()//虚函数 { cout<<"木头"< show(); return 0; } 结果:沙发 注意:如果基类里的show函数不是虚函数,则运行结果为:木头,因为Wood的指针只能调用Wood的类的成员。继承,虚函数,指向基类对象的指针或引用的结合可使C++支持运行时的多态性,而多态性对面向对象的程序设计非常重要的,实现了在基类中定义派生类所用的通用接口,而在派生类中定义具体的实现方法,即同一接口,多种方法。例如:#include
using namespace std; class Vehicle { public: virtual void message() { cout<<"Call vehicle's message function."< message(); p=&m; p->message(); p=&c; p->message(); p=&t; p->message(); } 结果: Call vehicle's message function. Call MotorVehicle's message function. Call Car's message function. Call Truck's message function. 程序只在基类Vehicle中显式定义了message为虚函数。C++规定,如果在派生类中,没有virtual显式地给出虚函数的声明,这时系统就会遵循以下的规则来判断一个成员函数是不是虚函数:
(1)该函数与基类的虚函数有相同的名称
(2)该函数与基类的虚函数有相同的参数个数以及相对应参数类型
(3)该函数与基类的虚函数有相同的返回类型或者满足兼容规则的指针,引用型返回类型
派生类的函数满足了上述条件,就会自动确定为虚函数。因此,在本程序的派生类MotorVehicle,Car和Truck的message仍为虚函数
虚函数的注意事项
(1)通过定义虚函数来使用C++提供的多态机制时,派生类应该从它的基类公用派生。之所以有这个要求,是因为在赋值兼容规则的基础上来使用虚函数的,而赋值兼容规则成立的前提条件是派生类从其基类公用派生。
(2)必须首先在基类定义虚函数。由于“基类”与“派生类”是相对的,因此,这项说明并不表明必须在类等级的最高层类中声明虚函数。在实际应用中,应该在类等级内需要具有有动态多态性的几个层次中的最高类内首先声明虚函数
(3)在派生类中对基类声明的虚函数进行了重新定义时,关键字virtual可以写也可以不写。但是为了增强程序的可读性,最好加上virtual
(4)虽然使用对象名和点运算符的方式也可以调用虚函数,c.message()可以调用虚函函数Car::message()。但是这种调用是在编译时的绑定(早绑定),他没有充分利用虚函数的特性。只有通过指向基类的指针或引用访问虚函数时才能获得运行时多态性。
(5)一个虚函数无论被公用继承多少次,它仍然保持其虚函数的特性。
(6)虚函数必须是其所在类的成员函数,而不能是友元函数,静态成员函数。但是虚函数可以在另一个类中被声明为友元函数。
(7)内联函数不能是虚函数,因为内联函数不能在运行中动态确定其位置的。
(8)构造函数不能是虚函数。因为虚函数运行时动态确定的,而构造函数是在对象产生之前运行的。
(9)析构函数可以是虚函数
虚析构函数的声明语法:
virtual ~类名();
例如:
#include
using namespace std; class Wood { public: virtual ~Wood() { cout<<"Wood的析构函数"< ~Wood(); delete w1; return 0; } 结果为: Sofa的析构函数 Wood的析构函数 如果Wood类析构函数的返回类型前不加virtual,结果为: Wood的析构函数 先调用了Sofa的析构函数,在调用了基类Wood的析构函数。
当基类的析构函数为虚函数时,无论指针指向的是同一类族的哪一个类对象,系统都会采用动态关联,调用相应的析构函数,对该对象所涉及的额外内存空间进行清理。最好把基类的析构函数声明为虚析构函数,这将使所有派生类的析构函数自动成虚析构函数。这样,如果程序中显式地使用了delete运算符准备删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会首先调用派生类的析构函数,再调用基类的析构函数,这样整个派生类的对象被完全释放。
纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派生类中必须定义自己的版本,或重新说明为纯虚函数纯虚函数的定义形式:class 类名 { … virtual 返回类型 函数名(参数列表)=0; };
与一般的虚函数定义格式基本相同。纯虚函数只是后面多了”=0”。声明为纯虚函数之后,基类就不能再给出函数的实现部分(函数体)。纯虚函数的函数体由派生类给出。
例如:#include
#include using namespace std; class shape { public: virtual double area()=0; virtual void display()=0; }; class rectangle:public shape { public: rectangle(double a=1,double b=1) { x=a; y=b; } double area() override { return x*y; } void display() override { cout<<"矩形面积为:"<display(); p=new triangle(3,4,5); p->display(); p=new circles(2); p->display(); } 结果为: 矩形面积为:6 三角形的面积为:29.3939 园形面积为:12.56 Shape是一个基类,它表示一个封闭的平面几何图形,从它可以派生出矩形类,三角形类和圆类。显然,在基类中定义area函数来求面积和定义display函数来显示它们的面积信息没有任何意义的,它只是用来提供派生类使用的公用的借口,所以程序中定义为纯虚函数,但在派生类中,则根据他们自身的需要,重新具体地定义虚函数。
抽象类至少有一个纯虚函数,那么就称该类为抽象类。关于抽象类有几点规定:
(1)由于抽象类中至少包含一个纯虚函数,抽象类只能作为基类来使用,不能建立抽象类对象,它只能用来为派生类提供一个接口。
(2)不允许具体类派生出抽象类。
(3)抽象类不能用作参数类型、返回类型、强制转化类型
(4)可以声明抽象类的指针,指针指向它的派生类对象
(5)如果派生类没有重新定义纯虚函数,则派生类只是简单继承基类的纯虚函数,则这个派生类仍是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不是抽象类,可以创建对象
(6)在抽象类中也可以定义普通成员函数或虚函数,虽然不能为抽象类声明对象,但是可以通过派生类对象来调用这些不是纯虚函数的函数。
如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家!