C++ 大学MOOC 北大课程(郭炜老师)听课整理 第六周(虚函数和多态)

基本概念

虚函数

1)在类定义里声明函数中,在函数前加 virtual 关键字,则成此函数为虚函数
例如:

class CBase{
public:
 virtual int func();
};
int CBase::func(){}

多态

1)通过基类指针调用基类和派生类中的同名虚函数时:当指针指向的是基类对象,则调用基类的虚函数;当指针指向派生类对象时,则调用派生类的虚函数
2)通过基类引用调用基类和派生类中的同名虚函数时:当引用的是基类对象,则调用基类的虚函数;当引用的是派生类对象时,则调用派生类的虚函数
例如:

#include <cstdlib>
#include <iostream>
using namespace std;
class A{
public:
 virtual void print(){
  cout << "A: printf" << endl;
 }
};
class B:public A{
public:
 virtual void print(){
  cout << "B: printf" << endl;
 }
};
class C:public A{
public:
 virtual void print(){
  cout << "C: printf" << endl;
 }
};
class D:public B{
public:
 virtual void print(){
  cout << "D: printf" << endl;
 }
};
int main(){
 A a;
 B b;
 C c;
 D d;
 A* pa = &a;
 B* pb = &b;
 C* pc = &c;
 D* pd = &d;
 pa->print(); //pa指向基类对象 a
 pa = pb;
 pa->print();  //pa指向派生类对象 b
 pa = pc;
 pa->print();  //pa指向派生类对象 c
 pa = pd;
 pa->print();  //pa指向间接派生类对象 d
 return 0;
}

多态示例:

class Cshape{
public:
 virtual double Area() = 0;
 virtual void PrintInfo() = 0;
};
class CRectangle :public Cshape{
public:
 int w, h;
 virtual double Area(){
  return w*h;
 }
 virtual void PrintInfo(){
  cout << "Rectangle: " << Area() << endl;
 }
};
class CCircle :public Cshape{
public:
 int r;
 virtual double Area(){
  return 3.14*r*r;
 }
 virtual void PrintInfo(){
  cout << "Circle: " << Area() << endl;
 }
};
class CTriangle :public Cshape{
public:
 int a, b, c;
 virtual double Area(){
  double p = (a + b + c) / 2.0;
  return sqrt(p*(p - a)*(p - b)*(p - c));
 }
 virtual void PrintInfo(){
  cout << "Triangle: " << Area() << endl;
 }
};
int Mycompare(const void* s1, const void* s2){
 double a1, a2;
 Cshape** p1;
 Cshape** p2;
 p1 = (Cshape**)s1;
 p2 = (Cshape**)s2;
 a1 = (*p1)->Area();
 a2 = (*p2)->Area();
 if (a1 < a2)
  return -1;
 else if (a1 > a2)
  return 1;
 else
  return 0;
}
int main(){
 int i, j;
 Cshape* p[100];
 CRectangle* p1;
 CCircle* p2;
 CTriangle* p3;
 int n;
 cin >> n;
 for (i = 0; i < n; ++i){
  char c;
  cin >> c;
  switch (c){
  case 'R':
   p1 = new CRectangle();
   cin >> p1->w >> p1->h;
   p[i] = p1;
   break;
  case 'C':
   p2 = new CCircle();
   cin >> p2->r;
   p[i] = p2;
   break;
  case 'T':
   p3 = new CTriangle();
   cin >> p3->a >> p3->b >> p3->c;
   p[i] = p3;
   break;
  }
 }
 qsort(p, n, sizeof(Cshape*), Mycompare);
 for (i = 0; i < n; ++i)
  p[i]->PrintInfo();
 return 0;
}

3)在非构造函数和非析构函数中调用虚函数,也是多态。
例如:

class Base{
public:
 void fun1(){ fun2(); } //基类函数中调用了虚函数 fun2
 virtual void fun2(){
  cout << "Base: fun2" << endl;
 }
};
class Derived :public Base{
public:
 virtual void fun2(){
  cout << "Derived: fun2" << endl;
 }
};
int main(){
 Derived p;
 Base* t = &p; //基类指针指向派生类的对象
 t->fun1();  //所以函数fun1中调用的是派生类中的fun2
 return 0;
}

输出:

Derived: fun2

4)构造函数和析构函数中调用虚函数不是多态,将调用的是自己类或基类中定义的函数
例如:

class myclass{
public:
 virtual void hello(){
  cout << "hello from myclass" << endl;
 }
 virtual void bye(){
  cout << "bye from myclass" << endl;
 }
};
class son :public myclass{
public:
 virtual void hello(){
  cout << "hello from son" << endl;
 }
 son(){ hello(); }   //构造函数中调用了虚函数 hello()
 ~son(){ bye();}    //析构函数中调用了虚函数 bye()
};
class grandson :public son{
public:
 virtual void bye(){
  cout << "bye from grandson" << endl;
 }
 virtual void hello(){
  cout << "hello from grandson" << endl;
 }
 grandson(){
  cout << "constructing grandson" << endl;
 }
 ~grandson(){
  cout << "destructing grandson" << endl;
 }
};
int main(){
 grandson s1;  //现调用基类的构造函数 在调用派生类的构造函数
 son * s = &s1;
 s->bye();  //多态
 return 0;  //析构函数
}

输出:

hello from son   //构造函数虽然调用了虚函数 但不是多态 调用了自己类定义的 hello函数
constructing grandson  //  派生类的构造函数
bye from grandson  //多态 调用了派生类中定义的 bye 函数
destructing grandson  //派生类的析构函数
bye from myclass  //基类son的析构函数中虽然调用了虚函数 但不是多态 调用了基类myclass的bye函数

多态的原理

1)在生成一个具有虚函数类的对象时,其空间中前4或8个字节用于存放指向该类虚函数表的地址
2)这样即使用基类指针存放派生类对象的地址,其指向空间的前4或8个字节依然说指向派生类虚函数表的地址
例如:

class A{
public:
 virtual void func(){
  cout << "A: func" << endl;
 }
};
class B:public A{
public:
 virtual void func(){
  cout << "B: func" << endl;
 }
};
int main(){
 B s1;
 A * p = &s1;
 A s2;
 p->func();  //一个正常的多态
 long long * p1 = (long long *)&s1;  
 long long * p2 = (long long *)&s2;
 *p1 = *p2;   //将A类对象s2前八个字节的内容覆盖B类对象s1前八个字节的内容
 p->func();  //一个看似不正常的多态
 return 0;
}

输出:

B: func  //第一个多态 基类指针指向派生类对象 调用派生类函数
A: func  //第二个多态 由于派生类对象用于存放派生类虚函数表的地址被替换为存放基类虚函数表的地址 所以虽然指向派生类的对象 但调用基类的虚函数

虚析构函数

1)当用基类指针指向一片new出来的派生类对象空间,delete这个指针时,我们希望先调用派生类的析构函数,但是事与愿违,此时只调用的是基类的析构函数
例如:

class son{
public:
 ~son(){
  cout << "bye from son" << endl;
 }
};
class grandson :public son{
public:
 ~grandson(){
  cout << "bye from grandson" << endl;
 }
};
int main(){
 son * p = new grandson();
 delete p;
 return 0;
}

只输出:

bye from son

2)为解决上诉问题,建议将基类的析构函数声明为虚函数
例如:

class son{
public:
 virtual ~son(){
  cout << "bye from son" << endl;
 }
};
class grandson :public son{
public:
 ~grandson(){
  cout << "bye from grandson" << endl;
 }
};
int main(){
 son * p = new grandson();
 delete p;
 return 0;
}

输出:

bye from grandson
bye from son

纯虚函数和抽象类

1)纯虚函数:没有函数体的虚函数
2)抽象类:包含纯虚函数的类
3)抽象类只能作为基类来派生新类,不能创建抽象类的对象
4)抽象类的指针和引用可以指向由抽象类派生出来的类的对象
5)抽象类的成员函数中可以调用纯虚函数,但构造函数和析构函数中不可以调用
6)从抽象类派生出来的类只有实现了基类中所有的纯虚函数,才能成为非抽象类
例如:

class A{
public:
 virtual void f() = 0;
 void g(){ f(); }
};
class B :public A{
public:
 void f(){
  cout << "B: fu" << endl;
 }
};
int main(){
 B b;
 b.g();
 return 0;
}

输出:

B: fu
发布了16 篇原创文章 · 获赞 17 · 访问量 752

猜你喜欢

转载自blog.csdn.net/weixin_45644911/article/details/104266166