继承

1. 继承和派生的含义

如果要提高程序开发的效率,需要对已有的代码进行重用。在面向过程的语言中,重用的单位是函数(过程)。而在面向对象的语言中,重用的单位是类。

重用一个已有的类来创建新的类,一种方法是组合,它体现部分和整体的关系。

比如说要构造一辆汽车,汽车是一种复杂的对象,它由很多小的部分组成。

figure4.png

再举一个更具体简单的例子:构造一个平面图形系统,需要用到点、线段、圆、矩形、三角形等。在线段、圆、矩形、三角形等图形中,都需要表示点这个类型,比如线段需要有两个端点来表示,我们可以通过添加点类型的成员,把它们组合起来。

figure2.png

或者可以把它们之间的关系更明确的表示为:

figure1.png

再比如说一个学生类。

figure3.png

继承派生)是类的重用的另一种方法,它体现类之间的is a的关系(一般和特殊的关系)。如果A is a B,即A是某一种特殊的B,则我们说A类是从B类继承(inherit)的,或者说B派生(derive)出A。比如说交通工具:

figure5.png

如果A类从B类继承,或B派生出A,则我们称A类是B类的派生类(Derived Class),B类是A类的基类(Base Class);或者说A类是B类的子类(Child Class),B类是A类的父类(Parent Class)。

比如说,学生Student是一类学校的成员SchoolMember,老师Teacher也是一类学校的成员SchoolMember,研究生GraduateStudent是一类学生Student。

figure6.png

还有各种图形之间的关系:

figure7.png

不恰当使用继承的例子:

figure9.png

figure10.png

2. 派生类的定义

我们用C++语言来描述学生老师之间的关系:

   1 struct Date {
   2     int year;
   3     int month;
   4     int day;
   5 };
   6 
   7 struct Course {
   8     string name;
   9     int credit;
  10 };
  11 
  12 class SchoolMember {
  13 private:
  14     string name;
  15     Date birth;
  16     bool gender;
  17 };
  18 
  19 //Student类继承了SchoolMember类,public指明继承方式是公有继承
  20 class Student : public SchoolMember 
  21 {
  22 private:
  23     string id;
  24     int department;
  25     int credit;
  26     Date enroll_day;
  27     Date graduate_day;
  28 };
  29 
  30 class Teacher; //前置声明
  31 
  32 //GraduateStudent类继承了Student类
  33 class GraduateStudent : public Student {
  34 private:
  35     Teacher *tutor;
  36 };
  37 
  38 //Teacher类继承了SchoolMember类
  39 class Teacher : public SchoolMember 
  40 {
  41 private:
  42     int title;
  43     int salary;
  44     GraduateStudent *supervise[8];
  45     int num_supervise;
  46 };

派生类对象的定义:

   1 int main() {
   2     SchoolMember p;
   3     Student s; 
   4     Teacher t;
   5     GraduateStudent g;
   6 }

对象的内存布局:

figure8.png

加上成员函数的实现:

   1 struct Date {
   2     int year;
   3     int month;
   4     int day;
   5 };
   6 
   7 struct Course {
   8     string name;
   9     int credit;
  10 };
  11 
  12 class SchoolMember {
  13 private:
  14     string name;
  15     Date birth;
  16     bool gender;
  17 public:
  18     Date get_bith() {
  19         return birth;
  20     }
  21     string get_name() {
  22         return name;
  23     }
  24     bool get_gender() {
  25         return gender;
  26     }
  27 };
  28 
  29 //Student类继承了SchoolMember类,public指明继承方式是公有继承
  30 class Student : public SchoolMember 
  31 {
  32 private:
  33     string id;
  34     int department;
  35     int credit;
  36     Date enroll_day;
  37     Date graduate_day;
  38 public:
  39     string get_id() {
  40         return id;
  41     }
  42     int get_department() {
  43         return department;
  44     }
  45     int get_credit() {
  46         return credit();
  47     }
  48     Date get_enroll_day() {
  49         return enroll_day;
  50     }
  51     Date get_graduate_day() {
  52         return graduate_day;
  53     }
  54     void enroll(Date day) {
  55         enroll_day = day;
  56     }
  57     void graduate(Date day) {
  58         graduate_day = day;
  59     }
  60 };
  61 //GraduateStudent类继承了Student类
  62 class GraduateStudent : public Student {
  63 private:
  64     Teacher *tutor;
  65 public:
  66     Teacher *get_tutor() {
  67         return tutor;
  68     }
  69     void set_tutor(Teacher *t) {
  70         if(tutor == t) return;
  71         tutor = t;
  72         t->add_supervise(this);
  73     }
  74 };
  75 
  76 //Teacher类继承了SchoolMember类
  77 class Teacher : public SchoolMember 
  78 {
  79 private:
  80     int title;
  81     int salary;
  82     GraduateStudent *supervise[8];
  83     int num_supervise;
  84 public:
  85     void add_supervise(GraduateStudent *g) {
  86         for(int i = 0; i < num_supervise; i++)
  87             if(supervise[i] == g)
  88                 return;
  89         supervise[num_supervise++] = g;
  90         g->set_tutor(this);
  91     }
  92 };

练习:将各种图形类用C++描述出来。

3. 派生类的构造和析构

在创建对象时,我们需要对对象进行初始化。普通类型的对象的初始化是由构造函数完成的,对于派生类对象也是相同的。

创建派生类对象时,派生类的构造函数会被自动调用。

   1 struct Date {
   2     int year, month, day;
   3     Date(int y, int m, int d) {
   4         year = y;
   5         month = m;
   6         day = d;
   7     }
   8 };
   9 
  10 class SchoolMember {
  11 private:
  12     string name;
  13     Date birth
  14     bool gender;
  15 };
  16 
  17 class Student : public SchoolMember {
  18 private:
  19     string id;
  20     int department;
  21     int credit;
  22     Date enroll_day;
  23     Date graduate_day;
  24 public:
  25     Student(string n, Date b, bool g, string i, int d)
  26         : enroll_day(0,0,0), graduate_day(0,0,0) {
  27         id = i;
  28         department = d;
  29         credit = 0;
  30     }
  31 };
  32 int main() {
  33     Student d("Jack", Date(1985, 9, 13), true, "05011101", 11);
  34 }

这个例子里面,派生类Student扩展的部分已经初始化,但是基类的部分没有初始化。要初始化继承下来的基类部分的成员,可以在基类中定义构造函数。

   1 struct Date {
   2     int year, month, day;
   3     Date(int y, int m, int d) {
   4         year = y;
   5         month = m;
   6         day = d;
   7     }
   8 };
   9 
  10 class SchoolMember {
  11 private:
  12     string name;
  13     Date birth
  14     bool gender;
  15 public:
  16     SchoolMember(string n, Date b, bool g) : birth(b) {
  17         name = n;
  18         gender = g;
  19     }
  20 };
  21 
  22 class Student : public SchoolMember {
  23 private:
  24     string id;
  25     int department;
  26     int credit;
  27     Date enroll_day;
  28     Date graduate_day;
  29 public:
  30     Student(string n, Date b, bool g, string i, int d)
  31         : enroll_day(0,0,0), graduate_day(0,0,0), SchoolMember(n, b, g) {
  32         id = i;
  33         department = d;
  34         credit = 0;
  35     }
  36 };
  37 int main() {
  38     Student d("Jack", Date(1985, 9, 13), true, "05011101", 11);
  39 }

如果基类中有构造函数,派生类没有定义构造函数,或者有构造函数但是没有显示调用基类构造函数,那么基类中的默认构造函数会被自动调用。

   1 class SchoolMember {
   2     string name;
   3     Date birth;
   4     bool gender;
   5 public:
   6     SchoolMember(string n = "", Date b = Date(0,0,0), bool g = false) 
   7         : birth(b) {
   8         name = n;
   9         gender = g;
  10     }
  11 };
  12 class Student : public SchoolMember{
  13     string id;
  14     int department;
  15     int credit;
  16     Date enroll_day;
  17     Date graduate_day;
  18 };
  19 int main() {
  20     Student s;
  21 }

在一个多层的继承结构里面,创建一个派生类对象时,基类、基类的基类、基类的基类的基类等的构造函数都会被调用。

   1 class SchoolMember {
   2 private:
   3     string name;
   4     Date birth
   5     bool gender;
   6 public:
   7     SchoolMember(string n, Date b, bool g) : birth(b) {
   8         name = n;
   9         gender = g;
  10     }
  11 };
  12 
  13 class Student : public SchoolMember {
  14 private:
  15     string id;
  16     int department;
  17     int credit;
  18     Date enroll_day;
  19     Date graduate_day;
  20 public:
  21     Student(string n, Date b, bool g, string i, int d)
  22         : enroll_day(0,0,0), graduate_day(0,0,0), SchoolMember(n, b, g) {
  23         id = i;
  24         department = d;
  25         credit = 0;
  26     }
  27 };
  28 class GraduateStudent: public Student {
  29     Teacher *tutor;
  30 public:
  31     GraduateStudent(string n, Date b, bool g, string i, int d, Teacher *t)
  32         :Student(n, b, g, i, d) {
  33         tutor = t;
  34     }
  35 };
  36 int main() {
  37     Teacher teacher;
  38     GraduateStudent d("Rose", Date(1985, 9, 13), false, "05011101", 11, &teacher);
  39 }

类的完整实现实例:

   1 class Date {
   2 public:
   3     int year, month, day;
   4     Date(int y, int m, int d) 
   5         : year(y), month(m), day(d) {
   6     }
   7 };
   8 struct Course{
   9     string name;
  10     int credit;
  11 };
  12 
  13 class SchoolMember {
  14     string name;
  15     Date birth;
  16     bool gender;
  17 public:
  18     SchoolMember(string n, Date b, bool g)
  19         : name(n), birth(b), gender(g) {
  20     }
  21 };
  22 class GraduateStudent;
  23 class Teacher : public SchoolMember {
  24     int salary;
  25     int title;
  26     int num_supervise;
  27     GraduateStudent *supervise[8];
  28 public:
  29     Teacher(string n, Date b, bool g, int t, int s) 
  30         : SchoolMember(n, b, g), salary(s), title(t) {
  31     }
  32     void add_supervise(GraduateStudent *g) {
  33         for(int i = 0; i < num_supervise; i++)
  34             if(supervise[i] == g)
  35                 return;
  36         supervise[num_supervise++] = g;
  37         g->set_tutor(this);
  38     }
  39 };
  40 class Student: public SchoolMember {
  41     string id;
  42     int department;
  43     int credit;
  44     Date enroll_day;
  45     Date graduate_day;
  46 public:
  47     SchoolMember(string n, Date b, bool g, string i, int d) 
  48         : SchoolMember(n, b, g), id(i), department(d), credit(0), 
  49         enroll_day(0,0,0), graduate_day(0,0,0) {
  50     }
  51     void take_course(Course *c) {
  52         credit += c->credit;
  53     }
  54     void enroll(Date day) {
  55         enroll_day = day;
  56     }
  57     void graduate(Date day) {
  58         graduate_day = day;
  59     }
  60 };
  61 class GraduateStudent: public Student {
  62     Teacher *tutor;
  63 public:
  64     GraduateStudent(string n, Date b, bool g, string i, int d, Teacher *t)
  65         : Student(n, b, g, i, d), tutor(t) {
  66         t->add_supervise(this);
  67     }
  68     void set_tutor(Teacher *t) {
  69         if(tutor == t) return;
  70         tutor = t;
  71         tutor->add_supervise(this);
  72     }
  73 };

派生类对象被销毁时,基类的析构函数会被自动调用。如果派生类和基类都有析构函数,那么他们都会被调用。

   1 class SchoolMember {
   2 public: 
   3     SchoolMember() {
   4         cout << "construct SchoolMember" << endl;
   5     }
   6     ~SchoolMember() { 
   7         cout <<"destruct SchoolMember" <<endl;
   8     }
   9 };
  10 class Student : public SchoolMember{
  11 public:
  12     Student() {
  13         cout << "construct Student" << endl;
  14     }
  15     ~Student() {
  16         cout << "destruct Student" << endl;
  17     }
  18 };
  19 int main() {
  20     Student s;
  21     SchoolMember p;
  22 }

派生类析构函数只负责派生类增加部分的分配资源的析构。

   1 int MAX_SUPERVISE[] = {0, 0, 4, 10};
   2 
   3 class Teacher : public SchoolMember {
   4     int salary;
   5     int title;
   6     int num_supervise;
   7     int capacity_supervise;
   8     GraduateStudent **supervise;    
   9 public:
  10     Teacher(string n, Date b, bool g, int t, int s) 
  11         : SchoolMember(n, b, g), salary(s), title(t) {
  12     }
  13     Teacher(string n, Date b, bool g, int t, int s)
  14         :SchoolMember(n,b,g), salary(s), title(t) {
  15         capacity_supervise = MAX_SUPERVISE[t];
  16         supervise = new GraduateStudent*[capacity_supervise]
  17     }
  18     ~Teacher(){
  19         delete[] supervise;
  20     }
  21     void add_supervise(GraduateStudent *g) {
  22         for(int i = 0; i < num_supervise; i++)
  23             if(supervise[i] == g)
  24                 return;
  25         if(num_supervise >= capacity_supervise)
  26             return;
  27         supervise[num_supervise++] = g;
  28         g->set_tutor(this);
  29     }
  30 };
  31 int main() {
  32     Teacher t("czk", Date(1979,8,27), true, 1, 1500);
  33 }

4. 类型兼容性

在需要使用基类对象的情况下,可以用派生类对象来代替。分为三种情况:

   1 int main() {
   2     Student s("jack", Date(1980, 8, 8), true, "970101", 10);
   3     SchoolMember  p = s; //slice
   4     Student s2 = p; //error
   5 
   6     Student  *pD = new Student("jack", Date(1980, 8, 8), true, "970101", 10);
   7     SchoolMember *pB = pD;  //correct 基类指针指向派生类对象
   8     SchoolMember *pB2 = new Student("jack", Date(1980, 8, 8), true, "970101", 10);
   9     pB = new Teacher("jack", Date(1980, 8, 8), true, 4, 1500); //多态性在这里体现
  10 
  11     Student &rd = s;
  12     SchoolMember &rp = p;
  13     Student &rd2 = p; //error
  14     SchoolMember &rp2 = s; // ok
  15 }

指针类型的强制转换

   1 int main() {
   2     SchoolMember  *pB = new Student;
   3     Student *pD2 = pB; //error
   4     Student *pD3 = dynamic_cast<Student *>(pB);
   5 
   6     SchoolMember *pB = new Teacher;
   7     Student *pD = dynamic_cast<Student *>(pB); //转换失败,得到空指针
   8     Student *pD2 = static_cast<Student *>(pB); //static_cast转换时不做检查,访问pD2的后果不堪设想
   9     pD2->enroll(); //不可设想的后果
  10 }

5. 派生类的访问权限

   1 class Base {
   2 public:
   3     int a;
   4 protected:
   5     int b;
   6 private:
   7     int c;
   8 };
   9 class Derived :public Base {
  10     void f() {
  11         cout << a << b << c;//访问c是错误的
  12     }
  13 };
  14 void f(Derived &d) {
  15     cout << d.a<< d.b<< d.c; //访问b,c都是错的
  16 }

   1 class Base {
   2 protected:
   3     int x;
   4 };
   5 class Derived  : public Base{
   6 public:
   7     void f( ) {
   8         Base b; 
   9         cout << b.x; //访问基类对象的保护成员,错误
  10         cout << x;  //发访问继承的基类保护成员,正确
  11     }
  12 };

使用using声明改变基类成员权限

   1 class Person {
   2 public:
   3     string get_name();
   4     Date get_date();
   5     bool get_gender();
   6 };
   7 class Student : public Person {
   8 private:
   9     using Person::get_name;
  10 };

派生类的成员名字如果与基类相同,将隐藏基类成员

   1 class Person {
   2 public:
   3     void set(string name);
   4     void set(bool gender);
   5 };
   6 class Student : public Person{
   7 public:
   8     void set(int department); // hide base member functions
   9 };
  10 void f(Student &s) {
  11     s.set("12345"); 
  12     s.set( true );
  13     s.Person::set("12345"); //ok
  14     s.Person::set(true); //ok
  15 }

保护继承和私有继承

   1 class Derived : private Base {
   2 //…
   3 };
   4 
   5 class Derived: protected Base {
   6 //…
   7 };

   1 class Base {
   2 public: int a;
   3 protected: int b;
   4 private: int c;
   5 };
   6 class Derived : private Base{
   7     void f() {
   8         cout << a << b << c;
   9     }
  10 };
  11 int main() {
  12     Derived d;
  13     cout << d.a << d.b << d.c;
  14 }

6. 多继承

在C++中允许一个派生类有多个基类,这种继承叫做多继承。相对的只有一个基类的继承被称为单继承。多继承表达的是“派生类既是一种基类A,又是一种基类B”的逻辑关系。

比如Assistent类继承了Student和Teacher两个类:

   1 class Assistant : public Student, public Teacher {
   2 public:
   3   Assistant(string n, string i, string l) 
   4         :  Teacher(n, l), Student(n, i) {
   5     cout << "a";
   6   }
   7   void print() const {
   8     cout<< name << id << level; // ambiguous error
   9     cout<< Student::name << Teacher::name << id << level;
  10   }
  11 };

注意:不同基类中的同名的成员会在派生类中同时存在。比如这里的name,id,level等。

多继承时对象的复制:派生类对象的指针可以赋给任何一个基类类型的指针

   1 Assistant  assist("jack", "12456", "assistant");
   2 Student  s = assist; //OK, slice
   3 Teacher  t = assist; //OK, slice
   4 Student *ps = &assist; //OK
   5 Teacher *pt= &assist; //OK
   6 ps->print();
   7 pt->print();
   8 Person *p = &assist; //ambigius
   9 Person *p = (Student *)&assist;
  10 Person *p = (Teacher *)&assist;
  11 p->print();

虚基类

   1 class Person {
   2 };
   3 class Student : virtual public Person {
   4 };
   5 class Teacher : virtual public Person {
   6 };
   7 class Assistant : public Student, public Teacher {
   8 };

虚基类Person在派生类Assistant的对象中只有一份

   1 class Person {
   2     string name;
   3 public:
   4     Person(string n):name(n){ cout << "P"; }
   5 };
   6 class Student : virtual public Person {
   7     string id;
   8 public:
   9     Student(string n, string i):id(i), Person(n){ cout<<"S";}
  10 };
  11 class Teacher : virtual public Person {
  12     string level;
  13 public:
  14     Teacher(string n, string l):level(l), Person(n){cout<<"T";}
  15 };
  16 class Assistant : public Student, public Teacher {
  17 public:
  18     Assistant(string n, string i, string l)
  19     : Student(n, i), Teacher(n, l), Person(n) {cout<<"A";}
  20 };

完整例子:

   1 #include <iostream>
   2 #include <string>
   3 using namespace std;
   4 
   5 struct Date {
   6     int year;
   7     int month;
   8     int day;
   9 public:
  10     Date(int y, int m, int d) {
  11         year = y;
  12         month = m;
  13         day = d;
  14     }
  15     void print() {
  16         cout << year<<"-" <<month<<"-" <<day;
  17     }
  18 };
  19 
  20 const bool MALE = false;
  21 const bool FEMALE = true;
  22 
  23 enum {SCHOOLMEMBER, TEACHER, STUDENT, GRADUATESTUDENT};
  24 
  25 class SchoolMember {
  26     string name;
  27     Date birth;
  28     bool gender;
  29 public:
  30     int type;
  31 
  32     SchoolMember(string n, Date b, bool g)
  33      : birth(b) {
  34         name = n;
  35         gender = g;
  36         type = SCHOOLMEMBER;
  37     }
  38     string get_name() {
  39         return name;
  40     }
  41     Date get_birth() {
  42         return birth;
  43     }
  44     bool get_gender() {
  45         return gender;
  46     }
  47     virtual void print() {
  48         cout << name;
  49         birth.print();
  50         cout << (gender==MALE?"MALE":"FEMALE") <<endl;        
  51     }
  52     virtual ~SchoolMember() {
  53         cout << "destruct SchoolMember " << get_name() << endl;
  54     }
  55 };
  56 
  57 struct Course {
  58     string name;
  59     int credit;
  60 };
  61 
  62 enum { MATHS = 8, PHYSICS =9, CS = 10};
  63 
  64 class Student : virtual public SchoolMember {
  65     string id;
  66     int department;
  67     int credit; 
  68 public:
  69     Student(string n, Date b, bool g, string i, int d) 
  70     : SchoolMember(n,b,g)
  71     {
  72         id = i;
  73         department = d;
  74         credit = 0;
  75         type = STUDENT;
  76     }
  77     void take_course(Course *c){
  78         credit += c->credit;
  79     }
  80     bool can_graduate() {
  81         return credit >= 160;
  82     }
  83     string get_id() {
  84         return id;
  85     }
  86     int get_credit() {
  87         return credit;
  88     }
  89     int get_department() {
  90         return department;
  91     }
  92     
  93     virtual void print() {
  94         cout << get_name();
  95         get_birth().print();
  96         cout << (get_gender()==MALE?"MALE":"FEMALE") << id << credit <<department << endl;
  97     }
  98     ~Student() {
  99         cout << "destruct Student:" << get_name() << endl;
 100     }
 101 
 102 };
 103 
 104 class Graduate;
 105 
 106 enum {PROFESSOR =4, VICEPROFESSOR = 3, ASSISTANT = 1};
 107 class Teacher :virtual public SchoolMember {
 108     int title;
 109     int salary;
 110     Graduate* supervised[10];
 111     int num_supervised;
 112 public:
 113     Teacher(string n, Date b, bool g, int t, int s)
 114     : SchoolMember(n, b, g) {
 115         title = t;
 116         salary = s;
 117         num_supervised = 0;
 118         type = TEACHER;
 119     }
 120     void add_supervised(Graduate *g) {
 121         supervised[num_supervised] = g;
 122         num_supervised++;
 123     }
 124     virtual void print() {
 125         cout << get_name();
 126         get_birth().print();
 127         cout << (get_gender()==MALE?"MALE":"FEMALE") << title << salary << endl;
 128     }
 129     ~Teacher() {
 130         cout << "destruct Teacher:" << get_name() << endl;
 131     }
 132     
 133 };
 134 
 135 class Graduate :public Student {
 136     Teacher *tutor;
 137 public:
 138     Graduate(string n, Date b, bool g, string i, int d, Teacher *t) 
 139     : Student(n, b, g, i, d), SchoolMember(n, b,g)
 140     {
 141         tutor = t;
 142         type = GRADUATESTUDENT;
 143     }
 144     void set_tutor(Teacher *t){
 145         tutor = t;
 146     }
 147     virtual void print() {
 148         cout << get_name();
 149         get_birth().print();
 150         cout << (get_gender()==MALE?"MALE":"FEMALE") << get_id() << get_credit()<< get_department() << tutor->get_name() << endl;
 151     }
 152     ~Graduate() {
 153         cout << "destruct Graduate:" << get_name() << endl;
 154     }
 155     
 156 };
 157 
 158 class Assistant : public Student, public Teacher {
 159 public:
 160     Assistant(string n, Date b, bool g, string i, int d, int t, int s)
 161     :Student(n, b, g, i, d), Teacher(n, b, g, t, s), 
 162     SchoolMember(n, b, g) {
 163                 
 164     }
 165     void print() {
 166     }
 167 };
 168 
 169 void printall(SchoolMember *p[], int n) {
 170     for(int i = 0; i < n; i++) {
 171         p[i]->print();
 172     }
 173 }
 174 
 175 int main() {
 176     SchoolMember *members[100];
 177     
 178     members[0] = new SchoolMember("Jack", Date(1980, 1, 1), MALE);
 179     members[1] = new Student("Mike", Date(1985, 5, 5), MALE, "12345", CS);
 180     members[2] = new Teacher("Rose", Date(1970, 3, 3), FEMALE, PROFESSOR, 2000);
 181     members[3] = new Graduate("Tom", Date(1982, 2, 2), MALE, "54321", 
 182                         CS, dynamic_cast<Teacher*>(members[2]));
 183     
 184     printall(members, 4);
 185     for(int i = 0;i < 4; i++)
 186         delete members[i];
 187     Assistant a("Jeff", Date(1978, 5, 5), MALE, "11100", CS, ASSISTANT, 800);
 188     SchoolMember *s = &a;
 189     
 190 }

多态

1. 多态与虚函数

相同的指令,作用在不同类型的对象上,产生不同动作。 比如:很多动物,有猫、狗、老虎等等。现在让所有的动物做一个“叫”的动作,结果…… 比如:有很多图形,有直线、方形、圆形、椭圆等等。现在让所有的图形把自己画在屏幕上,结果……

   1 int main() {
   2     SchoolMember *p[100];
   3     p[0] = new Student(“Jack”, Date(1984, 1,1), false, “20111374”, 11);
   4     p[1] = new Teacher(“Marry”, Date(1969, 5,5), true, “lecturer”, 1000);
   5     p[2] = new Student(“David”, Date(1983, 11,11), false, “20112343”, 11);
   6     //...
   7 }

现在要求写一个函数printall显示所有人的详细信息,应该怎么做?

   1 class SchoolMember {
   2     enum MemberType{ P, S, T, G } type; //用来区分学生还是教师
   3 };
   4 void printall( SchoolMember *p[100] ) {
   5     for( int i = 0; i < 100; i++) {
   6         cout << p[i]->name << p[i]->birth << p[i]->gender;
   7         switch(p[i]->type) { 
   8         case S: {
   9                 Student *s = (Student*)(p[i]);
  10                 cout << s->id << s->department;
  11             }  break;
  12         case T: {
  13                 Teacher *t = (Teacher*)(p[i]);
  14                 cout << t->level << t->salary;
  15             }  break;
  16         }
  17     }
  18 }

在每个类上增加一个print函数:

   1 class SchoolMember {
   2 public:
   3         void print(){ cout << name << birth <<gender; }
   4 };
   5 class Student : public SchoolMember{
   6 public:
   7     void print() { 
   8         cout << name << birth << gender << ID << department; 
   9     }
  10 };
  11 class Teacher : public Person {
  12 public:
  13     void print() { 
  14         cout << name << birth << gender << level << salary; 
  15     }
  16 };
  17 void printall( Person* p[100] ) {
  18     for(int i = 0; i < 100; i++) 
  19     switch (p[i]->type) {
  20         case S:
  21         {
  22             Student *s = (Student*)(p[i]);
  23             s->print();
  24         } break;        
  25         case T:
  26         {
  27             Teacher *t = (Teacher*)(p[i]);
  28             t->print();
  29         } break;
  30     }
  31 }

仍不够简洁,理想的做法是:

   1 void printall(Person *p[100]) {
   2     for ( int i =0; i < 100; i++)
   3         p[i]->print( );
   4 }

但是,这时输出是

Jack 1984-1-1 false
Marry 1969-5-5 true
David 1983-3-3 false
……

虚函数

   1 class Person {
   2 protected:
   3     string name;
   4     Date birth;
   5     bool gender;
   6 public:
   7     virtual void print( ) { //虚函数
   8         cout << name << birth << gender;
   9     }
  10 };

在派生类中覆盖(override)虚函数

   1 class Student : public Person {
   2 protected:
   3     string ID;
   4     int department;
   5 public:
   6     virtual void print( ) {  // override Base::print
   7         cout << name << birth <<gender;
   8         cout << ID << department;
   9     }
  10 };
  11 class Teacher : public Person{
  12 protected:
  13     int level;
  14     double salary;
  15 public:
  16     virtual void print() { //override Base::print
  17         cout << name << birth << gender;
  18         cout << level << salary;
  19     }
  20 };
  21 void printall(Person *p[100]) {
  22     for ( int i =0; i < 100; i++)
  23         p[i]->print( );
  24 }

这时候的结果是:

Jack 1984-1-1 false 20111374 11
Marry 1969-5-5 true lecturer 1000
David 1983-11-11 false 2012343 11
……

调用被覆盖(override)的函数

   1 class Teacher : public Person {
   2 protected:
   3     string level;
   4     double salary;
   5 public:
   6     virtual void print() {
   7         Person::print();
   8         cout << level <<  salary;
   9     }
  10 };

多态的限制

   1 int main() {
   2     Person p(“Jack”, Date(1984,1,1), false);
   3     p.print(); // 这里调用Person::print
   4     Student s(“David”, Date(1983,11,11), false, 403315,10);
   5     s.print();  // 这里调用 Student::print
   6     Person p2 = s;
   7     p2.print(); // 这里调用 Person::print
   8     Person *p3 = &s;
   9     p3->print();  // 这里调用 Student::print 这里才是多态
  10     Person &p4 = s;
  11     p4.print();  
  12 }

更多例子:

   1 class Shape {
   2 public: 
   3     virtual void draw();
   4 };
   5 class Rectangle : public Shape{
   6 public: 
   7     virtual void draw();
   8 };
   9 class Circle :public Shape {
  10 public: 
  11     virtual void draw();
  12 };
  13 int main() {
  14     Rectangle r;
  15     Circle c;
  16     Shape &s = r;
  17     s.draw();
  18     Shape &t = c;
  19     t.draw(); 
  20 }

问题:

   1 class Shape {
   2 public: 
   3     //no virtual function in base
   4 };
   5 class Rectangle : public Shape{
   6 public: 
   7     virtual void draw() { /*....*/ }
   8 };
   9 class Circle :public Shape {
  10 public: 
  11     virtual void draw() { /*....*/ }
  12 }; 
  13 void f(Shape* s) {
  14     s->draw();
  15 }
  16 int main() {
  17     Shape *s = new Rectangle;
  18     f( s );
  19 }

基类中没有虚函数draw,则s->draw()出错。

问题:

   1 class Shape {
   2 public: 
   3     virtual void draw() {/*....*/}
   4 };
   5 class Rectangle : public Shape{
   6 public: 
   7     virtual void draw(int a){/*....*/}
   8 };
   9 class Circle :public Shape {
  10 public: 
  11     virtual void draw() {/*....*/}
  12 }; 
  13 void f(Shape* s) {
  14     s->draw();
  15 }
  16 int main() {
  17     Shape *s = new Rectangle;
  18     f( s );
  19     Shape *c = new Circle;
  20     f( c );
  21 }

Rectangle中定义的draw与基类中的虚函数draw不同,则基类中的draw没有被override。

一个函数调用如果在编译时候确定调用哪个函数,我们称它为静态绑定;如果编译时候不能确定,到运行时才能确定,那么我们称它为动态绑定。

   1 void f(Person *p) {
   2     p->print( ); //static or dynamic?
   3 }

一般虚函数使用动态绑定,普通函数使用静态绑定。动态绑定比静态绑定速度慢。

虚表(virtual table)——C++实现多态的方法

虚析构函数

   1 int main() {
   2     Person *p = new Student(...);
   3     delete p;  // which destructor is called?
   4 }

虚析构函数作用:通过基类指针来释放派生类对象时,能够保证调用正确的析构函数

   1 class Person {
   2 public:
   3     virtual ~Person() { };
   4 };

更多例子:动物的叫的例子

   1 class Animal {
   2 public:
   3     virtual void shout() { cout << "animal cannot shout"; }
   4 };
   5 class Dog : public Animal {
   6 public: 
   7     void shout() { cout << "Dog barking!"; }
   8 };
   9 class Ox : public Animal {
  10 public: 
  11     void shout() { cout << "Ox Moo"; }
  12 };
  13 
  14 class Fish: public Animal {
  15 //shout not overridden here, Animal::shout is inherited
  16 };
  17 void f(Animal *a) {
  18      a->shout();
  19 }
  20 int main () {
  21     int i; cin >> i;
  22     Animal *a;
  23     switch(i) {
  24     case 1: a = new Dog; break;
  25     case 2: a = new Ox; break;
  26     case 3: a = new Fish; break;
  27     }
  28     f(a);
  29     delete a;
  30 }

2. 纯虚函数和抽象类

问题:某些基类的虚函数没有实现的意义

   1 class Animal {
   2 public: 
   3     virtual void shout() { cout << "animal cannot shout"; } 
   4 };

纯虚函数(pure virtual function):函数体可以省略的虚函数

   1 class Animal {
   2 public:         
   3     virtual void shout() = 0;  //Pure virtual function
   4 };

抽象类(abstract class):含有纯虚函数的类称为抽象类。类中只要有一个函数是纯虚函数,这个类就是抽象类。抽象类不能实例化,即不能定义对象。

   1 void f() {
   2     Animal a; // error
   3     Animal *pa1 = new Animal; // error
   4     Animal *pa2 = new Ox;
   5     pa2->shout();
   6     Animal *pa3 = new Fish; 
   7     pa3->shout();
   8 }

与抽象类相对的是实体类(concrete class),即不包含纯虚函数的类

抽象类虽然不能够实例化,但可以派生。在派生类中,纯虚函数会被继承。如果在派生类中,给出了所有纯虚函数的实现,那么派生类将成为实体类

   1 class Shape { //abstract class
   2 public: 
   3     virtual void draw() =0;
   4     virtual double getarea() = 0;
   5 };
   6 class Rectangle :public Shape {
   7 public: 
   8     virtual void draw() {/*...*/}
   9     virtual double getarea() { /*...*/}
  10 }; 
  11 class Circle : public Shape{
  12 public: 
  13     virtual void draw() { /*...*/}
  14 }; 

Rectangle为实体类,Circle为抽象类

纯虚函数一般省略函数体,但也可以有函数体

   1 class Person {
   2 public:
   3     virtual void print() = 0 {
   4         cout << name << gender << birth;
   5     }
   6 };

带函数体的纯虚函数可以在派生类中被调用

   1 class Student : public Person{
   2 public:
   3     virtual void print() {
   4         Person::print();  //base pure virtual function is called
   5         cout << id << department;
   6     }
   7 };

接口类:只有纯虚函数成员的类。 IUnknown:微软所有COM(Component Object Model)对象的基类

   1 class IUnknown {
   2 public:
   3     virtual long QueryInterface( 
   4                 REFIID riid, void **ppvObject) = 0;
   5     virtual unsigned long AddRef( void) = 0;
   6     virtual unsigned long Release( void) = 0;
   7 };

3. 运行时类型信息

问题:某些情况下,光靠多态性不够用,必须得到对象准确的类型信息

   1 void f(Shape *s) {
   2     // what on earth is s?
   3 }

两种RTTI机制:dynamic_cast和typeid。先决条件:类中必须有虚函数。在使用RTTI之前,需要先审查在面向对象设计上是否出了问题。

   1 dynamic_cast
   2 void f(Person *p) {
   3     if( dynamic_cast<Student *>(p) ) {
   4        cout<<"p is a Student object or its derivation";
   5        Student *s = dynamic_cast<Student *>(p);
   6     } else if( dynamic_cast<Teacher *>(p) ) {
   7        cout<<"p is a Teacher object or its derivation";
   8        Teacher * t = dynamic_cast<Teacher *>(p);
   9     }
  10 }

typeid能够确定表达式的确切类型,使用时要包含<typeinfo>

   1 void f( Shape *s) {
   2     cout << typeid(double).name();
   3     cout << typeid(5280L).name();
   4     cout << typeid(s).name(); //runtime class name
   5     cout << typeid(*s).name(); //static or dynamic?
   6     Shape &r = *s;
   7     cout << typeid(r).name();
   8     Shape  a;
   9     cout << typeid(a).name();
  10 }

typeid返回一个typeinfo类对象的引用

   1 class type_info{
   2 public:
   3     virtual ~type_info();
   4     const char *name() const;
   5     bool operator==(const type_info&) const;
   6     bool operator!=(const type_info&) const;
   7     bool before(const type_info&) const;
   8 private:
   9     type_info( const type_info&);
  10     type_info& operator=(const type_info&);
  11 };

用法:

   1 void f(Shape *s) {
   2     if( typeid( *s ) == typeid( Circle ) )
   3         cout << "s is a Circle";
   4 }

4. 面向对象设计方法

例子:

设计模式:

前人成功经验总结的可以重复利用的面向对象软件设计的范例。用处:套用前人范式以面向对象思想解决问题,设计出健壮、可复用、支持变化的软件。参考书:《设计模式——可复用面向对象软件的基础》——GoF(Erich Gamma等四人)

The End

C++继承与多态 (2008-02-23 15:35:15由localhost编辑)

ch3n2k.com | Copyright (c) 2004-2020 czk.