继承
1. 继承和派生的含义
如果要提高程序开发的效率,需要对已有的代码进行重用。在面向过程的语言中,重用的单位是函数(过程)。而在面向对象的语言中,重用的单位是类。
重用一个已有的类来创建新的类,一种方法是组合,它体现部分和整体的关系。
比如说要构造一辆汽车,汽车是一种复杂的对象,它由很多小的部分组成。
再举一个更具体简单的例子:构造一个平面图形系统,需要用到点、线段、圆、矩形、三角形等。在线段、圆、矩形、三角形等图形中,都需要表示点这个类型,比如线段需要有两个端点来表示,我们可以通过添加点类型的成员,把它们组合起来。
或者可以把它们之间的关系更明确的表示为:
再比如说一个学生类。
继承(派生)是类的重用的另一种方法,它体现类之间的is a的关系(一般和特殊的关系)。如果A is a B,即A是某一种特殊的B,则我们说A类是从B类继承(inherit)的,或者说B派生(derive)出A。比如说交通工具:
如果A类从B类继承,或B派生出A,则我们称A类是B类的派生类(Derived Class),B类是A类的基类(Base Class);或者说A类是B类的子类(Child Class),B类是A类的父类(Parent Class)。
- 逻辑上派生类和基类是一种is a的关系;
- 派生类是在基类的基础上添加新的成员进行扩充而得到,它拥有基类的所有成员(所有属性和方法);
- 在用到基类对象的地方,可以用派生类对象来代替。
比如说,学生Student是一类学校的成员SchoolMember,老师Teacher也是一类学校的成员SchoolMember,研究生GraduateStudent是一类学生Student。
还有各种图形之间的关系:
不恰当使用继承的例子:
- Rectangle继承Point?
- Ellipse继承Circle还是Circle继承Ellipse?
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 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. 派生类的访问权限
- private成员:只在定义它的类的成员函数中可以访问,在派生类的成员函数中不可访问。基类的私有成员被派生类继承,但是不能被派生类访问
- protected成员:在本类及派生类中能访问的成员。派生类可以访问继承的保护成员,但是不能访问一个基类对象的保护成员
使用using声明改变基类成员权限
派生类的成员名字如果与基类相同,将隐藏基类成员
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 }
保护继承和私有继承
6. 多继承
在C++中允许一个派生类有多个基类,这种继承叫做多继承。相对的只有一个基类的继承被称为单继承。多继承表达的是“派生类既是一种基类A,又是一种基类B”的逻辑关系。
比如Assistent类继承了Student和Teacher两个类:
注意:不同基类中的同名的成员会在派生类中同时存在。比如这里的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();
虚基类
虚基类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. 多态与虚函数
相同的指令,作用在不同类型的对象上,产生不同动作。 比如:很多动物,有猫、狗、老虎等等。现在让所有的动物做一个“叫”的动作,结果…… 比如:有很多图形,有直线、方形、圆形、椭圆等等。现在让所有的图形把自己画在屏幕上,结果……
现在要求写一个函数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 }
仍不够简洁,理想的做法是:
但是,这时输出是
Jack 1984-1-1 false Marry 1969-5-5 true David 1983-3-3 false ……
虚函数
在派生类中覆盖(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 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 }
- 多态只能通过指针或者引用来实现。
- 非成员函数、类的构造函数、静态成员函数不能是虚函数
- virtual写在成员函数的声明中,而不是定义中
- 一般情况下,派生类要override基类的虚函数,要求函数声明形式完全一样。
例外:基类的虚函数返回基类的指针或者引用,那么派生类override它,可以返回此派生类的指针或引用
class Base { virtual Base*f(); }; class Derived:public Base { virtual Derived* f(); };
- 如果派生类没有override基类的虚函数,那么派生类会继承基类的虚函数
更多例子:
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。
一个函数调用如果在编译时候确定调用哪个函数,我们称它为静态绑定;如果编译时候不能确定,到运行时才能确定,那么我们称它为动态绑定。
一般虚函数使用动态绑定,普通函数使用静态绑定。动态绑定比静态绑定速度慢。
虚表(virtual table)——C++实现多态的方法
虚析构函数
虚析构函数作用:通过基类指针来释放派生类对象时,能够保证调用正确的析构函数
更多例子:动物的叫的例子
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. 纯虚函数和抽象类
问题:某些基类的虚函数没有实现的意义
纯虚函数(pure virtual function):函数体可以省略的虚函数
抽象类(abstract class):含有纯虚函数的类称为抽象类。类中只要有一个函数是纯虚函数,这个类就是抽象类。抽象类不能实例化,即不能定义对象。
与抽象类相对的是实体类(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为抽象类
纯虚函数一般省略函数体,但也可以有函数体
带函数体的纯虚函数可以在派生类中被调用
接口类:只有纯虚函数成员的类。 IUnknown:微软所有COM(Component Object Model)对象的基类
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>
typeid返回一个typeinfo类对象的引用
用法:
4. 面向对象设计方法
- 确定程序中需要的类及每个类需要的操作
- 将不同类的共同的操作提取到基类
- 通过基类接口来操纵各种不同的对象
- 可以通过增加新的派生类来扩展系统
例子:
- 复合文档(composite模式):包含文字、图形的文档
- 一组数据,有多种表现形式(observer模式)(比如表格、柱状图、柄图)
- 交互图
设计模式:
前人成功经验总结的可以重复利用的面向对象软件设计的范例。用处:套用前人范式以面向对象思想解决问题,设计出健壮、可复用、支持变化的软件。参考书:《设计模式——可复用面向对象软件的基础》——GoF(Erich Gamma等四人)