C++纯虚函数和抽象类
有时候在基类中声明函数并不是基类本身的需要,而是考虑到派生类的需求,在基类中声明一个函数,函数的具体实现由派生类根据本类的需求定义。
例如,动物都有叫声,但不同的动物叫声不同,因此基类(动物类)并不需要实现描述动物叫声的函数,只需要声明即可,函数的具体实现在各派生类中完成。
在基类中,这样的函数可以声明为纯虚函数。
纯虚函数的作用是在基类中为派生类保留一个接口,方便派生类根据需要完成定义,实现多态。
派生类都应该实现基类的纯虚函数,如果派生类没有实现基类的纯虚函数,则该函数在派生类中仍然是纯虚函数。
抽象类只能作为基类派生新类,不能创建抽象类的对象,但可以定义抽象类的指针或引用,通过指针或引用操作派生类对象。
抽象类可以有多个纯虚函数,如果派生类需要实例化对象,则在派生类中需要全部实现基类的纯虚函数。如果派生类没有全部实现基类的纯虚函数,未实现的纯虚函数在派生类中仍然是纯虚函数,则派生类也是抽象类。
【示例1】下面通过案例演示纯虚函数和抽象类的应用,C++ 代码如下:
由运行结果可知,Cat 类和 Rabbit 类对象创建成功,并且通过基类指针 pC 和 pR 成功调用了各派生类的函数,实现了多态。在释放指针指向的空间时,先调用了派生类的析构函数,后调用了基类析构函数。
上述示例中 Animal 类是抽象类,如果创建 Animal 类对象,编译器会报错,例如在 main() 函数中添加如下代码:
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
例如,动物都有叫声,但不同的动物叫声不同,因此基类(动物类)并不需要实现描述动物叫声的函数,只需要声明即可,函数的具体实现在各派生类中完成。
在基类中,这样的函数可以声明为纯虚函数。
C++纯虚函数
纯虚函数也通过virtual
关键字声明,但是纯虚函数没有函数体。纯虚函数在声明时,需要在后面加上“=0”,格式如下所示:virtual 函数返回值类型 函数名(参数列表) = 0;
上述格式中,纯虚函数后面“=0”并不是函数的返回值为 0,它只是告诉编译器这是一个纯虚函数,在派生类中会完成具体的实现。纯虚函数的作用是在基类中为派生类保留一个接口,方便派生类根据需要完成定义,实现多态。
派生类都应该实现基类的纯虚函数,如果派生类没有实现基类的纯虚函数,则该函数在派生类中仍然是纯虚函数。
C++抽象类
如果一个类中包含纯虚函数,这样的类称为抽象类。抽象类的作用主要是通过它为一个类群建立一个公共接口(纯虚函数),使它们能够更有效地发挥多态性。抽象类声明了公共接口,而接口的完整实现由派生类定义。抽象类只能作为基类派生新类,不能创建抽象类的对象,但可以定义抽象类的指针或引用,通过指针或引用操作派生类对象。
抽象类可以有多个纯虚函数,如果派生类需要实例化对象,则在派生类中需要全部实现基类的纯虚函数。如果派生类没有全部实现基类的纯虚函数,未实现的纯虚函数在派生类中仍然是纯虚函数,则派生类也是抽象类。
【示例1】下面通过案例演示纯虚函数和抽象类的应用,C++ 代码如下:
#include<iostream> using namespace std; class Animal //动物类Animal { public: virtual void speak()=0; //纯虚函数speak() virtual void eat()=0; //纯虚函数eat() virtual ~Animal(); //虚析构函数 }; Animal::~Animal() { cout<<"调用Animal析构函数"<<endl; } class Cat:public Animal //猫类Cat,公有继承Animal类 { public: void speak(); //声明speak()函数 void eat(); //声明eat()函数 ~Cat(); //声明析构函数 }; void Cat::speak() //实现speak()函数 { cout<<"小猫喵喵叫"<<endl; } void Cat::eat() //实现eat()函数 { cout<<"小猫吃鱼"<<endl; } Cat::~Cat() //实现析构函数 { cout<<"调用Cat析构函数"<<endl; } class Rabbit:public Animal //兔子类Rabbit,公有继承Animal类 { public: void speak(); //声明speak()函数 void eat(); //声明eat()函数 ~Rabbit(); //声明析构函数 }; void Rabbit::speak() //实现speak()函数 { cout<<"小兔子咕咕叫"<<endl; } void Rabbit::eat() //实现eat()函数 { cout<<"小兔子吃白菜"<<endl; } Rabbit::~Rabbit() //实现析构函数 { cout<<"调用Rabbit析构函数"<<endl; } int main() { Animal* pC=new Cat; //定义基类指针pC指向Cat类对象 pC->speak(); //通过pC指针调用Cat类的speak()函数 pC->eat(); //通过pC指针调用Cat类的eat()函数 delete pC; //释放pC指针指向的空间 Animal* pR=new Rabbit; //定义基类指针pR指向Rabbit类对象 pR->speak(); //通过pR指针调用Rabbit类的speak()函数 pR->eat(); //通过pR指针调用Rabbit类的eat()函数 delete pR; //释放pR指针指向的空间 return 0; }运行结果:
小猫喵喵叫
小猫吃鱼
调用Cat析构函数
调用Animal析构函数
小兔子咕咕叫
小兔子吃白菜
调用Rabbit析构函数
调用Animal析构函数
- 第 3~9 行代码定义了动物类 Animal,该类提供了 speak() 和 eat() 两个纯虚函数;
- 第 14~20 行代码定义了猫类 Cat 公有继承 Animal 类,Cat 类实现了 Animal 类的全部纯虚函数;
- 第 33~39 行代码定义了兔子类 Rabbit 公有继承 Animal 类,Rabbit 类实现了 Animal 类的全部纯虚函数;
- 第 54~57 行代码,在 main() 函数中定义了 Animal 类指针 pC 指向一个 Cat 类对象,并通过 pC 指针调用 speak() 函数和 eat() 函数,之后使用 delete 运算符释放 pC 指针指向的空间;
- 第 58~61 行代码,定义 Animal 类指针 pR 指向一个 Rabbit 类对象,并通过 pR 指针调用 speak() 函数和 eat() 函数,之后使用 delete 运算符释放 pR 指针指向的空间。
由运行结果可知,Cat 类和 Rabbit 类对象创建成功,并且通过基类指针 pC 和 pR 成功调用了各派生类的函数,实现了多态。在释放指针指向的空间时,先调用了派生类的析构函数,后调用了基类析构函数。
上述示例中 Animal 类是抽象类,如果创建 Animal 类对象,编译器会报错,例如在 main() 函数中添加如下代码:
Animal animal;
再次运行程序,编译器会报错,如下所示:
“Animal”:不能实例化抽象类
不允许使用抽象类类型“Animal”的对象:
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。