当前位置:   article > 正文

【C++知识树】详述virtual的四种用法

【C++知识树】详述virtual的四种用法

virtual用法一:修饰父类中的普通函数

virtual用法二:修饰析构函数

virtual用法三:修饰继承性 

Virtual用法四:纯虚函数

virtual用法一:修饰父类中的普通函数

被修饰的函数称为虚函数, 是C++中多态的一种实现(多说一句,多态分编译时多态-通过重载实现和运行时多态-通过虚函数实现)。 也就是说用父类的指针或者引用指向其派生类的对象,当使用指针或引用调用函数的时候会根据具体的对象类型调用对应对象的函数(需要两个条件:父类的函数用virtual修饰和子类要重写父类的函数)。

使用virtual修饰的函数会根据实际对象的类型来调用,没有使用virtual修饰的根据指针的类型来调用。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。

下面用两个例子来说明:

例一: 

  1. #include <iostream>
  2. class father {
  3. public:
  4. void func1() {std::cout << "this is father func1" << std::endl;}
  5. virtual void func2() {std::cout << "this is father func2" << std::endl;
  6. }
  7. class son:public father {
  8. public:
  9. void func1() {std::cout << "this is son func1" << std::endl;}
  10. void func2() {std::cout << "this is son func2" << std::endl;
  11. }
  12. int main() {
  13. father *f1 = new son();
  14. f1.func1();
  15. f1.func2();
  16. return 0;
  17. }

output:

  1. this is father func1
  2. this is son func2

 例二:

下面这段代码打印出的结果为B,但是当把A类中的virtual去掉之后打印出的就为A。当基类中没有virtual的时候,编译器在编译的时候把p看做A类的对象,调用的自然就是A类的方法。但是加上virtual之后,将dispaly方法变成了虚方法,这样调用的时候编译器会看调用的究竟是谁的实例化对象,这样就实现了多态的效果。也就是说,当基类的派生类中有重写过基类的虚方法的时候,使用基类的指针指向派生类的对象,调用这个方法实际上调用的会是派生类最后实现的方法。

  1. #include<iostream>
  2. using namespace std;
  3. class A{
  4. public:
  5. virtual void display(){ cout<<"A"<<endl; }
  6. };
  7. class B : public A{
  8. public:
  9. void display(){ cout<<"B"<<endl; }
  10. };
  11. void doDisplay(A *p)
  12. {
  13. p->display();
  14. delete p;
  15. }
  16. int main(int argc,char* argv[])
  17. {
  18. doDisplay(new B());
  19. return 0;
  20. }

output: 

B

virtual用法二:修饰析构函数

修饰析构函数与上面讲到的使用方法和原理相同,虚析构函数在销毁时会调用对象的析构函数,这样就不会出现像有的数据成员没有销毁导致内存泄露的问题或者程序直接崩溃。

下面也用两个例子说明:

例一: 

  1. class GrandFather {
  2. public:
  3. GrandFather() {std::cout << "construct grandfather" << std::endl;}
  4. ~GrandFather() {std::cout << "destruct grandfather" << std::endl;}
  5. };
  6. class Fatherpublic GrandFather{
  7. public:
  8. Father() {std::cout << "construct father" << std::endl;}
  9. ~Father() {std::cout << "destruct father" << std::endl;}
  10. };
  11. class Sonpublic Father{
  12. public:
  13. Son() {std::cout << "construct son" << std::endl;}
  14. ~Son() {std::cout << "destruct son" << std::endl;}
  15. };
  16. int main() {
  17. Father *f = new Son();
  18. delete f;
  19. return 0;
  20. }

output:

  1. construct grandfather
  2. construct father
  3. construct son
  4. destruct father
  5. destruct grandfather

我们发现没有调用son的析构函数,当将Father或者GrandFather其中一个的析构函数修改为virtual后输出就变为了

  1. construct grandfather
  2. construct father
  3. construct son
  4. destruct son
  5. destruct father
  6. destruct grandfather

 例二:

  1. #include<iostream>
  2. using namespace std;
  3. class Person{
  4. public: Person() {name = new char[16];cout<<"Person构造"<<endl;}
  5. virtual ~Person() {delete []name;cout<<"Person析构"<<endl;}
  6. private:
  7. char *name;
  8. };
  9. class Teacher :virtual public Person{
  10. public: Teacher(){ cout<<"Teacher构造"<<endl; }
  11. ~Teacher(){ cout<<"Teacher析构"<<endl; }
  12. };
  13. class Student :virtual public Person{
  14. public: Student(){ cout<<"Student构造"<<endl; }
  15. ~Student(){ cout<<"Student析构"<<endl; }
  16. };
  17. class TS : public Teacher,public Student{
  18. public: TS(){ cout<<"TS构造"<<endl; }
  19. ~TS(){ cout<<"TS析构"<<ENDL; }
  20. };
  21. int main(int argc,char* argv[])
  22. {
  23. Person *p = new TS();
  24. delete p;
  25. return 0;
  26. }

 output:

  1. Person构造
  2. Teacher构造
  3. Student构造
  4. TS构造
  5. TS析构
  6. Student析构
  7. Teacher析构
  8. Person析构

但是当我们把Person类中析构前面的virtual去掉之后的运行结果为: 

  1. Person构造
  2. Teacher构造
  3. Student构造
  4. TS构造
  5. Person析构
  6. 程序崩溃

virtual用法三:修饰继承性 

假如有这种场景,一个类继承两个或者更多的父类,但是这些父类里又有一些有共同的父类,会出现什么情况呢?

下面给出两个例子:

例一: 

  1. #include<iostream>
  2. using namespace std;
  3. class Person{
  4. public: Person(){ cout<<"Person构造"<<endl; }
  5. ~Person(){ cout<<"Person析构"<<endl; }
  6. };
  7. class Teacher : virtual public Person{
  8. public: Teacher(){ cout<<"Teacher构造"<<endl; }
  9. ~Teacher(){ out<<"Teacher析构"<<endl; }
  10. };
  11. class Student : virtual public Person{
  12. public: Student(){ cout<<"Student构造"<<endl; }
  13. ~Student(){ cout<<"Student析构"<<endl; }
  14. };
  15. class TS : public Teacher, public Student{
  16. public: TS(){ cout<<"TS构造"<<endl; }
  17. ~TS(){ cout<<"TS析构"<<endl; }
  18. };
  19. int main(int argc,char* argv[])
  20. {
  21. TS ts;
  22. return 0;
  23. }

output:

  1. Person构造
  2. Teacher构造
  3. Student构造
  4. TS构造
  5. TS析构
  6. Student析构
  7. Teacher析构
  8. Person析构

当Teacher类和Student类没有虚继承Person类的时候,也就是把virtual去掉时候终端输出的结果为:

我们在构造TS的时候需要先构造他的基类,也就是Teacher类和Student类。而Teacher类和Student类由都继承于Person类。这样就导致了构造TS的时候实例化了两个Person类。

  1. Person构造
  2. Teacher构造
  3. Person构造
  4. Student构造
  5. TS构造
  6. TS析构
  7. Student析构
  8. Person析构
  9. Teacher析构
  10. Person析构

例二:

  1. class GrandFather {
  2. public:
  3. GrandFather() {std::cout << "construct grandfather" << std::endl;}
  4. ~GrandFather() {std::cout << "destruct grandfather" << std::endl;}
  5. };
  6. class Father1public GrandFather{
  7. public:
  8. Father1() {std::cout << "construct father1" << std::endl;}
  9. ~Father1() {std::cout << "destruct father1" << std::endl;}
  10. };
  11. class Father2public GrandFather{
  12. public:
  13. Father2() {std::cout << "construct father2" << std::endl;}
  14. ~Father2() {std::cout << "destruct father2" << std::endl;}
  15. };
  16. class Sonpublic Father1, Father2{
  17. public:
  18. Son() {std::cout << "construct son" << std::endl;}
  19. ~Son() {std::cout << "destruct son" << std::endl;}
  20. };
  21. int main() {
  22. Father *f = new Son();
  23. delete f;
  24. return 0;
  25. }

output:

  1. construct grandfather
  2. construct father1
  3. construct grandfather
  4. construct father2
  5. construct son
  6. destruct son
  7. destruct father2
  8. destruct grandfather
  9. destruct father1
  10. destruct grandfather

通过这个例子我们看到创建一个son会创建两个grandfather,不符合我们的预期啊,而且还可能会导致程序挂掉。这里就请virtual出场了,当把father1和father2继承grandfather修改为virtual继承(也就是在public前面加一个virtual)的时候输出会变成这样:
output:

  1. construct grandfather
  2. construct father1
  3. construct father2
  4. construct son
  5. destruct son
  6. destruct father2
  7. destruct father1
  8. destruct grandfather

Virtual用法四:纯虚函数

纯虚函数的定义是在虚函数的后面加一个=0。定义了纯虚函数的类是一个抽象类。


纯虚函数需要注意这几点:
1.定义了纯虚函数的类不能够实例化,也就是不能够创建对象
2.继承了含有纯虚函数的父类的子类如果没有实现纯虚函数也不能够实例化

virtual void func() = 0;

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/43454
推荐阅读
相关标签
  

闽ICP备14008679号