前言
简单记录一下关键字virtual的相关内容,涉及虚函数,虚析构函数,纯虚函数等,记录于此,方便自己查阅。
正文
在介绍虚函数前需要了解一下多态。
多态分为两类:
静态多态:静态多态在编译期就能确定要调用哪个函数,包含重载和模板两种方式。
动态多态:在程序运行阶段根据实际情况来确定调用哪个函数,主要通过虚函数来实现。
这里主要介绍虚函数,也就是动态多态。
如果基类与派生类中有同名成员函数,根据类型兼容规则,当使用基类指针或基类引用操作派生类对象时,只能调用基类的同名函数。如果想要使用基类指针或基类引用调用派生类中的成员函数,就需要虚函数解决,虚函数是实现多态的基础。
动态多态的实现需要满足3个条件:
基类声明虚函数。
派生类重写基类的虚函数。
将基类指针指向派生类对象,通过基类指针访问虚函数。
虚函数
虚函数的声明方式是在成员函数的返回值类型前添加virtual关键字,格式如下所示:
class 类名 { 权限控制符: virtual 返回类型 函数名(参数列表) ....//略 }
虚函数有如下注意事项:
只有类的成员函数才能成为虚函数。
静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
内联函数不能是虚函数,因为内联函数不能在运行中动态确定其位置。
构造函数不能是虚函数,析构函数通常是虚函数。
重载和覆盖(重写)的区别是:
重载是同一层次内多个函数名相同;
覆盖(重写)则是继承层次中多个成员函数的函数原型完全相同。
例子
下面例子有两个,一个有虚函数,一个没有,可以对比看效果。
不使用virtual
#include <iostream> class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } void printfInfo() { std::cout << "Book.printfInfo page : " << page << std::endl; std::cout << "Book.printfInfo value : " << value << std::endl; } }; class LuXunBook :public Book { public: LuXunBook(int page, float value) :Book(page, value) { std::cout << "LuXunBook(int page, float value)" << std::endl; this->page = page; this->value = value; } void printfInfo() { std::cout << "LuXunBook.printfInfo page : " << page << std::endl; std::cout << "LuXunBook.printfInfo value : " << value << std::endl; } };
没有使用virtual修饰printfInfo(),如果按照如下创建对象
int main() { Book* book_2 = new LuXunBook(300, 28.0f); book_2->printfInfo(); delete book_2; book_2 = NULL; return 0; }
会发现调用的是父类的方法。
Book(int page, float value) LuXunBook(int page, float value) Book.printfInfo page : 300 Book.printfInfo value : 28
按照我们设想,new了子类对象,应该调用子类的方法才对,但并不是!
使用virtual
#include <iostream> class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } virtual void printfInfo() { std::cout << "Book.printfInfo page : " << page << std::endl; std::cout << "Book.printfInfo value : " << value << std::endl; } }; class LuXunBook :public Book { public: LuXunBook(int page, float value) :Book(page, value) { std::cout << "LuXunBook(int page, float value)" << std::endl; this->page = page; this->value = value; } void printfInfo() { std::cout << "LuXunBook.printfInfo page : " << page << std::endl; std::cout << "LuXunBook.printfInfo value : " << value << std::endl; } }; int main() { Book* book_2 = new LuXunBook(300, 28.0f); book_2->printfInfo(); delete book_2; book_2 = NULL; return 0; }
使用virtual修饰printfInfo(),那么printfInfo就是虚函数了。
int main() { Book* book_2 = new LuXunBook(300, 28.0f); book_2->printfInfo(); delete book_2; book_2 = NULL; return 0; }
这次会发现,book_2调用的是子类的重写的方法。
Book(int page, float value) LuXunBook(int page, float value) LuXunBook.printfInfo page : 300 LuXunBook.printfInfo value : 28
小结
若类中声明了虚函数,并且派生类重新定义了虚函数,当使用基类指针或基类引用操作派生类对象调用函数时,系统会自动调用派生类中的虚函数代替基类虚函数。
虚析构函数
在C++中不能声明虚构造函数,因为构造函数执行时,对象还没有创建,不能按照虚函数方式调用。
但是,在C++中可以声明虚析构函数,格式如下:
virtual ~ 析构函数(){ //do something }
例子
下面还是根据上面是否使用virtual修饰对比查询。
不使用virtual
#include <iostream> class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } ~Book() { std::cout << "~Book()" << std::endl; } }; class LuXunBook :public Book { public: LuXunBook(int page, float value) :Book(page, value) { std::cout << "LuXunBook(int page, float value)" << std::endl; this->page = page; this->value = value; } ~LuXunBook() { std::cout << "~LuXunBook()" << std::endl; } }; int main() { Book* book_2 = new LuXunBook(300, 28.0f); delete book_2; book_2 = NULL; return 0; }
输出
Book(int page, float value) LuXunBook(int page, float value) ~Book()
从打印信息看,LuXunBook的构造函数和析构函数没有成对出现
使用virtual
#include <iostream> class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } virtual ~Book() { std::cout << "~Book()" << std::endl; } }; class LuXunBook :public Book { public: LuXunBook(int page, float value) :Book(page, value) { std::cout << "LuXunBook(int page, float value)" << std::endl; this->page = page; this->value = value; } ~LuXunBook() { std::cout << "~LuXunBook()" << std::endl; } }; int main() { Book* book_2 = new LuXunBook(300, 28.0f); delete book_2; book_2 = NULL; return 0; }
输出
Book(int page, float value) LuXunBook(int page, float value) ~LuXunBook() ~Book()
这里LuXunBook的构造函数和析构函数成对出现了。
小结
最好把基类的析构函数声明为虚析构函数,即使基类不需要析构函数,也要显式定义一个函数体为空的虚析构函数,这样所有派生类的析构函数都会自动成为虚析构函数。
如果程序中通过基类指针释放派生类对象,编译器能够调用派生类的析构函数完成派生类对象的释放。
纯虚函数
有些函数只需要子类实现,父类只需要声明即可。例如,动物都有叫声,但不同的动物叫声不同,因此基类(动物类)=只需要声明即可,函数的具体实现在各派生类中完成。
在基类中,这样的函数可以声明为纯虚函数。
格式
virtual 函数返回值类型 函数名(参数列表) = 0;
比如
virtual void printfInfo() = 0; //纯虚函数
纯虚函数也通过virtual关键字声明,但是纯虚函数没有函数体。纯虚函数在声明时,需要在后面加上“=0”。
纯虚函数的作用是在基类中为派生类保留一个接口,方便派生类根据需要完成定义,实现多态。派生类都应该实现基类的纯虚函数,如果派生类没有实现基类的纯虚函数,则该函数在派生类中仍然是纯虚函数。
抽象类
如果一个类中包含纯虚函数,这样的类称为抽象类。
抽象类的作用主要是通过它为一个类群建立一个公共接口(纯虚函数),而接口的完整实现由派生类定义,使它们能够更有效地发挥多态性。
注意
抽象类只能作为基类派生新类,不能创建抽象类的对象,但可以定义抽象类的指针或引用,通过指针或引用操作派生类对象。
class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } virtual void printfInfo() = 0; };
Book中的printfInfo()是纯虚函数,此时Book是抽象类。
int main() { //Book book; //错误,抽象类不允许创建对象。 return 0; }
提示:C++ 不允许使用抽象类类型Book的对象。
虽然不可以创建抽象类对象,但可以定义抽象类的指针或引用
#include <iostream> class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } virtual ~Book() { std::cout << "~Book()" << std::endl; } virtual void printfInfo() = 0; }; class LuXunBook :public Book { public: LuXunBook(int page, float value) :Book(page, value) { std::cout << "LuXunBook(int page, float value)" << std::endl; this->page = page; this->value = value; } ~LuXunBook() { std::cout << "~LuXunBook()" << std::endl; } void printfInfo() { std::cout << "LuXunBook.printfInfo page : " << page << std::endl; std::cout << "LuXunBook.printfInfo value : " << value << std::endl; } }; int main() { Book* book_1 = new LuXunBook(300, 28.0f); book_1->printfInfo(); delete book_1; book_1 = NULL; std::cout << std::endl; LuXunBook luXunBook(180, 18.0f); Book& book_2 = luXunBook; book_2.printfInfo(); return 0; }
输出结果
Book(int page, float value) LuXunBook(int page, float value) LuXunBook.printfInfo page : 300 LuXunBook.printfInfo value : 28 ~LuXunBook() ~Book() Book(int page, float value) LuXunBook(int page, float value) LuXunBook.printfInfo page : 180 LuXunBook.printfInfo value : 18 ~LuXunBook() ~Book()
抽象类可以有多个纯虚函数,如果派生类需要实例化对象,则在派生类中需要全部实现基类的纯虚函数。如果派生类没有全部实现基类的纯虚函数,未实现的纯虚函数在派生类中仍然是纯虚函数,则派生类也是抽象类。
#include <iostream> class Book { protected: int page; float value; public: Book(int page, float value) { std::cout << "Book(int page, float value)" << std::endl; this->page = page; this->value = value; } virtual ~Book() { std::cout << "~Book()" << std::endl; } virtual void printfInfo() = 0; //打印书籍信息 virtual bool reprint() = 0; //书籍是否重印 }; class NovalBook : public Book { public: NovalBook(int page, float value) :Book(page, value) { std::cout << "NovalBook(int page, float value)" << std::endl; this->page = page; this->value = value; } ~NovalBook() { std::cout << "~NovalBook()" << std::endl; } void printfInfo() { std::cout << "NovalBook.printfInfo page : " << page << std::endl; std::cout << "NovalBook.printfInfo value : " << value << std::endl; } }; int main() { //Book book; //错误,Book为抽象类 //NovalBook novalBook; // 错误,NovalBook也是抽象类 return 0; }
NovalBook只实现了一个纯虚函数,还有一个没有实现,所以依旧是抽象类。