6932
备注:
|
25056
|
删除的内容标记成这样。 | 加入的内容标记成这样。 |
行号 1: | 行号 1: |
[[TableOfContents]] |
|
行号 3: | 行号 5: |
== 继承和派生的含义 == 如果要提高程序开发的效率,需要对已有的代码进行'''重用'''。在面向过程的语言中,重用的单位是函数(过程)。而在面向对象的语言中,重用的单位是类。 重用一个已有的类来创建新的类,一种方法是'''组合'''。比如说构造一个平面图形系统,需要用到点、线段、圆、矩形、三角形等。在线段、圆、矩形、三角形等图形中,都需要表示点这个类型,我们可以通过添加点类型的成员,把它们组合起来。 attachment:figure2.png 或者可以把它们之间的关系更明确的表示为: attachment:figure1.png 再比如说有汽车的例子,汽车是一种复杂的对象,它由很多小的部分组成。 attachment:figure4.png 再比如说一个学生类。 attachment:figure3.png 总之,组合体现的是对象之间'''部分与整体'''的关系。'''继承'''('''派生''')是类的重用的另一种方法,它体现类之间的'''is a'''的关系。如果A is a B,即A是某一种特殊的B,则我们说A类是从B类'''继承'''(inherit)的,或者说B'''派生'''(derive)出A。比如说交通工具: attachment: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,研究生Graduate{{{}}}Student是一类学生Student。 attachment:figure6.png 还有图形之间的关系: attachment:figure7.png 错误的例子: Rectangle继承Point Ellipse继承Circle |
|
行号 5: | 行号 49: |
利用已有的类,进行扩充构成新类的机制 基类/父类 派生类/子类 基类和派生类是一种is a的关系 {{{#!cplusplus class Person { |
我们用C++语言来描述类之间派生和继承的关系: {{{#!cplusplus struct Date { int year; int month; int day; }; struct Course { string name; int credit; }; class SchoolMember { |
行号 17: | 行号 68: |
class Teacher : public Person | //Teacher类继承了SchoolMember类 class Teacher : public SchoolMember |
行号 20: | 行号 73: |
int title; | |
行号 21: | 行号 75: |
int level; }; class Student : public Person |
}; //Student类继承了Person类,public指明继承方式是公有继承 class Student : public SchoolMember |
行号 27: | 行号 80: |
string ID; int department; }; class GradStudent : public Student { private: Teacher *tutor; }; }}} 成员函数实现 {{{#!cplusplus class Person { |
string id; int department; int credit; Date enroll_day; Date graduate_day; }; //GraduateStudent类继承了Student类 class GraduateStudent : public Student { private: Teacher *tutor; }; }}} 派生类对象的定义: {{{#!cplusplus int main() { SchoolMember p; Student s; //派生类对象s包括了基类的全部成员,还有在派生类中新增的成员 Teacher t; GraduateStudent g; } }}} 对象的内存布局: attachment:figure8.png 加上成员函数的实现: {{{#!cplusplus class SchoolMember { |
行号 47: | 行号 118: |
class Teacher : public Person { | class Teacher : public SchoolMember { int title; |
行号 49: | 行号 121: |
int level; | |
行号 53: | 行号 124: |
}; class Student: public Person { string ID; |
void add_pupil(GraduateStudent *); void remove_pupil(GraduateStudent *); }; class Student: public SchoolMember { string id; |
行号 57: | 行号 130: |
int credit; Date enroll_day; Date graduate_day; |
|
行号 60: | 行号 136: |
void take_course(Course *); void enroll(); void graduate(); |
|
行号 65: | 行号 144: |
}; }}} 派生类的访问权限 * private成员:只在本类中可以访问,在派生类中不可访问的成员。基类的私有成员被派生类继承,但是不能被派生类访问 |
void set_tutor(Teacher *); }; }}} == 派生类的访问权限 == * private成员:只在定义它的类的成员函数中可以访问,在派生类的成员函数中不可访问。基类的私有成员被派生类继承,但是不能被派生类访问 |
行号 75: | 行号 156: |
public: int a; protected: int b; private: int c; |
public: int a; protected: int b; private: int c; |
行号 81: | 行号 165: |
cout << a << b << c; | cout << a << b << c;//访问c是错误的 |
行号 85: | 行号 169: |
cout << d.a<< d.b<< d.c; | cout << d.a<< d.b<< d.c; //访问b,c都是错的 |
行号 98: | 行号 182: |
Base b; cout << b.x; cout << x; |
Base b; cout << b.x; //访问基类对象的保护成员,错误 cout << x; //发访问继承的基类保护成员,正确 |
行号 138: | 行号 222: |
继承和组合是软件复用的两种方式。 |
|
行号 140: | 行号 226: |
创建派生类对象时,基类的构造函数会被自动调用。如果派生类没有构造函数,或者没有显示调用,那么基类中的默认构造函数会被自动调用。 | 创建派生类对象时,基类的构造函数会被自动调用,用于初始化继承下来的基类部分的成员。如果派生类没有构造函数,或者没有显示调用,那么基类中的默认构造函数会被自动调用。 |
行号 155: | 行号 241: |
string id; | |
行号 168: | 行号 255: |
cout << "Person initialized" << endl; | |
行号 174: | 行号 262: |
public: Student(int dep) { department = dep; } private: |
|
行号 180: | 行号 263: |
}; }}} |
string id; public: Student() :department(11), id("0509342") { cout << "Student initialized" << endl; } }; int main() { } }}} 在这个派生类的构造函数中,没有给基类的构造函数任何参数,基类中的默认构造函数会被调用。 |
行号 206: | 行号 297: |
创建一个派生类对象时,必须显式或者隐式的调用基类的构造函数。在一个多层的继承结构里面,创建一个派生类对象时,所有的基类的构造函数都会被调用。 | 在一个多层的继承结构里面,创建一个派生类对象时,所有的基类的构造函数都会被调用。 |
行号 226: | 行号 317: |
类的完整实现实例: {{{#!cplusplus class Person { string name; bool gender; public: Person(string n, bool g) : name(n), gender(g) { } }; class Teacher : public Person { int salary; int level; public: Teacher(string n, bool g, int s, int l) : Person(n, g), salary(s), level(l) { } }; class Student: public Person { string id; int department; public: Student(string n, bool g, string i, int d) : Person(n, g), id(i), department(d) { } }; class GradStudent: public Student { Teacher *tutor; public: GradStudent(string n, bool g, string i, int d, Teacher *t) : Student(n, g, i, d), tutor(t) { } }; }}} |
|
行号 241: | 行号 363: |
int main() { Student s; Person p; } |
|
行号 248: | 行号 374: |
~Person() { cout << "destruct Person" <<endl; } | ~Person() { cout << "destruct Person" << endl; } |
行号 257: | 行号 383: |
} }}} 派生类的构造析构实例。派生类析构函数只负责派生类增加部分的分配资源的析构。 |
Person p; } }}} 派生类析构函数只负责派生类增加部分的分配资源的析构。 |
行号 302: | 行号 429: |
Student &rd = s; Person &rp = p; Student &rd2 = p; //error Person &rp2 = s; // ok |
|
行号 317: | 行号 448: |
在C++中允许一个派生类有多个基类,这种继承叫做多继承。相对的只有一个基类的继承被称为单继承。多继承表达的是“派生类既是一种基类A,又是一种基类B”的逻辑关系。 比如Assistent类继承了Student和Teacher两个类: {{{#!cplusplus class Assistant : public Student, public Teacher { public: Assistant(string n, string i, string l) : Teacher(n, l), Student(n, i) { cout << "a"; } void print() const { cout<< name << id << level; // ambiguous error cout<< Student::name << Teacher::name << id << level; } }; }}} 注意:不同基类中的同名的成员会在派生类中同时存在。比如这里的name,id,level等。 多继承时对象的复制:派生类对象的指针可以赋给任何一个基类类型的指针 {{{#!cplusplus Assistant assist("jack", "12456", "assistant"); Student s = assist; //OK, slice Teacher t = assist; //OK, slice Student *ps = &assist; //OK Teacher *pt= &assist; //OK ps->print(); pt->print(); Person *p = &assist; //ambigius Person *p = (Student *)&assist; Person *p = (Teacher *)&assist; p->print(); }}} |
|
行号 319: | 行号 484: |
{{{#!cplusplus class Person { }; class Student : virtual public Person { }; class Teacher : virtual public Person { }; class Assistant : public Student, public Teacher { }; }}} 虚基类Person在派生类Assistant的对象中只有一份 {{{#!cplusplus class Person { string name; public: Person(string n):name(n){ cout << "P"; } }; class Student : virtual public Person { string id; public: Student(string n, string i):id(i), Person(n){ cout<<"S";} }; class Teacher : virtual public Person { string level; public: Teacher(string n, string l):level(l), Person(n){cout<<"T";} }; class Assistant : public Student, public Teacher { public: Assistant(string n, string i, string l) : Student(n, i), Teacher(n, l), Person(n) {cout<<"A";} }; }}} |
|
行号 321: | 行号 519: |
{{{#!cplusplus class Derived : private Base { //… }; class Derived: protected Base { //… }; }}} {{{#!cplusplus class Base { public: int a; protected: int b; private: int c; }; class Derived : private Base{ void f() { cout << a << b << c; } }; int main() { Derived d; cout << d.a << d.b << d.c; } }}} |
|
行号 323: | 行号 547: |
相同的指令,作用在不同类型的对象上,产生不同动作。 比如:很多动物,有猫、狗、老虎等等。现在让所有的动物做一个“叫”的动作,结果…… 比如:有很多图形,有直线、方形、圆形、椭圆等等。现在让所有的图形把自己画在屏幕上,结果…… {{{#!cplusplus int main() { Person *p[100]; p[0] = new Student(“Jack”, Date(1984, 1,1), false, “20111374”, 11); p[1] = new Teacher(“Marry”, Date(1969, 5,5), true, “lecturer”, 1000); p[2] = new Student(“David”, Date(1983, 11,11), false, “20112343”, 11); //... } }}} 现在要求写一个函数printall显示所有人的详细信息,应该怎么做? {{{#!cplusplus class Person { enum PersonType{ P, S,T, G } type; //用来区分学生还是教师 }; void printall( Person *p[100] ) { for( int i = 0; i < 100; i++) { cout << p[i]->name << p[i]->birth << p[i]->gender; switch(p[i]->type) { case S: { Student *s = (Student*)(p[i]); cout << s->id << s->department; } break; case T: { Teacher *t = (Teacher*)(p[i]); cout << t->level << t->salary; } break; } } } }}} 在每个类上增加一个print函数: {{{#!cplusplus class Person { public: void print(){ cout << name << birth <<gender; } }; class Student : public Person{ public: void print() { cout << name << birth << gender << ID << department; } }; class Teacher : public Person { public: void print() { cout << name << birth << gender << level << salary; } }; void printall( Person* p[100] ) { for(int i = 0; i < 100; i++) switch (p[i]->type) { case S: { Student *s = (Student*)(p[i]); s->print(); } break; case T: { Teacher *t = (Teacher*)(p[i]); t->print(); } break; } } }}} 不够简洁,理想的做法是: {{{#!cplusplus void printall(Person *p[100]) { for ( int i =0; i < 100; i++) p[i]->print( ); } }}} 但是,这时输出是 {{{ Jack 1984-1-1 false Marry 1969-5-5 true David 1983-3-3 false …… }}} 虚函数 {{{#!cplusplus class Person { protected: string name; Date birth; bool gender; public: virtual void print( ) { //虚函数 cout << name << birth << gender; } }; }}} override虚函数 {{{#!cplusplus class Student : public Person { protected: string ID; int department; public: virtual void print( ) { // override Base::print cout << name << birth <<gender; cout << ID << department; } }; class Teacher : public Person{ protected: int level; double salary; public: virtual void print() { //override Base::print cout << name << birth << gender; cout << level << salary; } }; void printall(Person *p[100]) { for ( int i =0; i < 100; i++) p[i]->print( ); } }}} 这时候的结果是: {{{ Jack 1984-1-1 false 20111374 11 Marry 1969-5-5 true lecturer 1000 David 1983-11-11 false 2012343 11 …… }}} 调用被override的函数 {{{#!cplusplus class Teacher : public Person { protected: string level; double salary; public: virtual void print() { Person::print(); cout << level << salary; } }; }}} 多态的限制 {{{#!cplusplus int main() { Person p(“Jack”, Date(1984,1,1), false); p.print(); // Person::print Student s(“David”, Date(1983,11,11), false, 403315,10); s.print(); // Student::print Person p2 = s; p2.print(); // Person::print Person *p3 = &s; p3->print(); // Student::print 这里是多态 Person &p4 = s; p4.print(); } }}} * 多态只能通过指针或者引用来实现。 * 非成员函数、类的构造函数、静态成员函数不能是虚函数 * virtual写在成员函数的声明中,而不是定义中 * 一般情况下,派生类要override基类的虚函数,要求函数声明形式完全一样。 * 例外:基类的虚函数返回基类的指针或者引用,那么派生类override它,可以返回此派生类的指针或引用{{{ class Base { virtual Base*f(); }; class Derived:public Base { virtual Derived* f(); }; }}} * 如果派生类没有override基类的虚函数,那么派生类会继承基类的虚函数 更多例子: {{{#!cplusplus class Shape { public: virtual void draw(); }; class Rectangle : public Shape{ public: virtual void draw(); }; class Circle :public Shape { public: virtual void draw(); }; int main() { Rectangle r; Circle c; Shape &s = r; s.draw(); Shape &t = c; t.draw(); } }}} 问题: {{{#!cplusplus class Shape { public: virtual void draw(); //no virtual function in base }; class Rectangle : public Shape{ public: void draw(); }; class Circle :public Shape { public: void draw(); }; void f(Shape* s) { s->draw(); } int main() { Shape *s = new Rectangle; f( s ); } }}} 基类中没有虚函数draw,则s->draw()出错。 问题: {{{#!cplusplus class Shape { public: virtual void draw(); }; class Rectangle : public Shape{ public: virtual void draw(int a); }; class Circle :public Shape { public: virtual void draw(); }; void f(Shape* s) { s->draw(); } int main() { Shape *s = new Rectangle; f( s ); Shape *c = new Circle; f( c ); } }}} Rectangle中定义的draw与基类中的虚函数draw不同,则基类中的draw没有被override。 一个函数调用如果在编译时候确定调用哪个函数,我们称它为静态绑定;如果编译时候不能确定,到运行时才能确定,那么我们称它为动态绑定。 {{{#!cplusplus void f(Person *p) { p->print( ); //static or dynamic? } }}} 一般虚函数使用动态绑定,普通函数使用静态绑定。动态绑定比静态绑定速度慢。 虚表(virtual table)——C++多态的实现方法 {{{#!cplusplus int main() { Person *p = new Student(...); delete p; // which destructor is called? } }}} 虚析构函数作用:通过基类指针来释放派生类对象时,能够保证调用正确的析构函数 {{{#!cplusplus class Person { public: virtual ~Person() { }; }; }}} 更多例子:动物的叫的例子 {{{#!cplusplus class Animal { public: virtual void shout() { cout << "animal cannot shout"; } }; class Dog : public Animal { public: void shout() { cout << “Dog barking!”; } }; class Ox : public Animal { public: void shout() { cout << “Ox Moo”; } }; class Fish: public Animal { //shout not overridden here, Animal::shout is inherited }; void f(Animal *a) { a->shout(); } int main () { int i; cin >> i; Animal *a; switch(i) { case 1: a = new Dog; break; case 2: a = new Ox; break; case 3: a = new Fish; break; } f(a); delete a; } }}} |
|
行号 326: | 行号 856: |
问题:某些基类的虚函数没有实现的意义 {{{#!cplusplus class Animal { public: virtual void shout() { cout << "animal cannot shout"; } }; }}} 纯虚函数(pure virtual function):函数体可以省略的虚函数 {{{#!cplusplus class Animal { public: virtual void shout() = 0; //Pure virtual function }; }}} 抽象类(abstract class):含有纯虚函数的类称为抽象类。类中只要有一个函数是纯虚函数,这个类就是抽象类。抽象类不能实例化,即不能定义对象。 {{{#!cplusplus void f() { Animal a; // error Animal *pa1 = new Animal; // error Animal *pa2 = new Ox; pa2->shout(); Animal *pa3 = new Fish; pa3->shout(); } }}} 与抽象类相对的是实体类(concrete class),即不包含纯虚函数的类 抽象类虽然不能够实例化,但可以派生。在派生类中,纯虚函数会被继承。如果在派生类中,给出了所有纯虚函数的实现,那么派生类将成为实体类 {{{#!cplusplus class Shape { //abstract class public: virtual void draw() =0; virtual double getarea() = 0; }; class Rectangle :public Shape { public: virtual void draw() {/*...*/} virtual double getarea() { /*...*/} }; class Circle : public Shape{ public: virtual void draw() { /*...*/} }; }}} Rectangle为实体类,Circle为抽象类 纯虚函数一般省略函数体,但也可以有函数体 {{{#!cplusplus class Person { public: virtual void print() = 0 { cout << name << gender << birth; } }; }}} 带函数体的纯虚函数可以在派生类中被调用 {{{#!cplusplus class Student : public Person{ public: virtual void print() { Person::print(); //base pure virtual function is called cout << id << department; } }; }}} 接口类:只有纯虚函数成员的类。 IUnknown:微软所有COM(Component Object Model)对象的基类 {{{#!cplusplus class IUnknown { public: virtual long QueryInterface( REFIID riid, void **ppvObject) = 0; virtual unsigned long AddRef( void) = 0; virtual unsigned long Release( void) = 0; }; }}} |
|
行号 328: | 行号 936: |
问题:某些情况下,光靠多态性不够用,必须得到对象准确的类型信息 {{{#!cplusplus void f(Shape *s) { // what on earth is s? } }}} 两种RTTI机制:dynamic_cast和typeid。先决条件:类中必须有虚函数。在使用RTTI之前,需要先审查在面向对象设计上是否出了问题。 {{{#!cplusplus dynamic_cast void f(Person *p) { if( dynamic_cast<Student *>(p) ) { cout<<“p is a Student object or its derivation”; Student *s = dynamic_cast<Student *>(p); } else if( dynamic_cast<Teacher *>(p) ) { cout<<“p is a Teacher object or its derivation”; Teacher * t = dynamic_cast<Teacher *>(p); } } }}} typeid能够确定表达式的确切类型,使用时要包含<typeinfo> {{{#!cplusplus void f( Shape *s) { cout << typeid(double).name(); cout << typeid(5280L).name(); cout << typeid(s).name(); //runtime class name cout << typeid(*s).name(); //static or dynamic? Shape &r = *s; cout << typeid(r).name(); Shape a; cout << typeid(a).name(); } }}} typeid返回一个typeinfo类对象的引用 {{{#!cplusplus class type_info{ public: virtual ~type_info(); const char *name() const; bool operator==(const type_info&) const; bool operator!=(const type_info&) const; bool before(const type_info&) const; private: type_info( const type_info&); type_info& operator=(const type_info&); }; }}} 用法: {{{#!cplusplus void f(Shape *s) { if( typeid( *s ) == typeid( Circle ) ) cout << “s is a Circle”; } }}} |
|
行号 329: | 行号 994: |
* 确定程序中需要的类及每个类需要的操作 * 将不同类的共同的操作提取到基类 * 通过基类接口来操纵各种不同的对象 * 可以通过增加新的派生类来扩展系统 例子: * 复合文档(composite模式):包含文字、图形的文档 * 一组数据,有多种表现形式(observer模式)(比如表格、柱状图、柄图) 设计模式: 前人成功经验总结的可以重复利用的面向对象软件设计的范例。用处:套用前人范式以面向对象思想解决问题,设计出健壮、可复用、支持变化的软件。参考书:《设计模式——可复用面向对象软件的基础》——GoF(Erich Gamma等四人) |
继承
1. 继承和派生的含义
如果要提高程序开发的效率,需要对已有的代码进行重用。在面向过程的语言中,重用的单位是函数(过程)。而在面向对象的语言中,重用的单位是类。
重用一个已有的类来创建新的类,一种方法是组合。比如说构造一个平面图形系统,需要用到点、线段、圆、矩形、三角形等。在线段、圆、矩形、三角形等图形中,都需要表示点这个类型,我们可以通过添加点类型的成员,把它们组合起来。
attachment:figure2.png
或者可以把它们之间的关系更明确的表示为:
attachment:figure1.png
再比如说有汽车的例子,汽车是一种复杂的对象,它由很多小的部分组成。
attachment:figure4.png
再比如说一个学生类。
attachment:figure3.png
总之,组合体现的是对象之间部分与整体的关系。继承(派生)是类的重用的另一种方法,它体现类之间的is a的关系。如果A is a B,即A是某一种特殊的B,则我们说A类是从B类继承(inherit)的,或者说B派生(derive)出A。比如说交通工具:
attachment: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。
attachment:figure6.png
还有图形之间的关系:
attachment:figure7.png
错误的例子:
Rectangle继承Point
Ellipse继承Circle
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 //Teacher类继承了SchoolMember类
20 class Teacher : public SchoolMember
21 {
22 private:
23 int title;
24 int salary;
25 };
26 //Student类继承了Person类,public指明继承方式是公有继承
27 class Student : public SchoolMember
28 {
29 private:
30 string id;
31 int department;
32 int credit;
33 Date enroll_day;
34 Date graduate_day;
35 };
36 //GraduateStudent类继承了Student类
37 class GraduateStudent : public Student {
38 private:
39 Teacher *tutor;
40 };
派生类对象的定义:
对象的内存布局:
attachment:figure8.png
加上成员函数的实现:
1 class SchoolMember {
2 string name;
3 Date birth;
4 bool gender;
5 public:
6 string get_name();
7 Date get_date();
8 bool get_gender();
9 };
10 class Teacher : public SchoolMember {
11 int title;
12 int salary;
13 public:
14 int get_salary();
15 int get_level();
16 void add_pupil(GraduateStudent *);
17 void remove_pupil(GraduateStudent *);
18 };
19 class Student: public SchoolMember {
20 string id;
21 int department;
22 int credit;
23 Date enroll_day;
24 Date graduate_day;
25 public:
26 string get_id();
27 int get_department();
28 void take_course(Course *);
29 void enroll();
30 void graduate();
31 };
32 class GradStudent: public Student {
33 Teacher *tutor;
34 public:
35 Teacher *get_tutor();
36 void set_tutor(Teacher *);
37 };
3. 派生类的访问权限
- 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 }
继承和组合是软件复用的两种方式。
4. 派生类的构造和析构
创建派生类对象时,基类的构造函数会被自动调用,用于初始化继承下来的基类部分的成员。如果派生类没有构造函数,或者没有显示调用,那么基类中的默认构造函数会被自动调用。
1 class Person {
2 public:
3 Person(string aname = "", bool agender = false) {
4 name = aname; gender = agender;
5 }
6 private:
7 string name;
8 bool gender;
9 };
10 class Student : public Person {
11 private:
12 int department;
13 string id;
14 };
15 int main() {
16 Student d; // default constructor of Person is called
17 }
派生类可以定义自己的构造函数,用于初始化派生类扩展的部分
1 class Person {
2 public:
3 Person(string aname = "", bool agender = false) {
4 name = aname;
5 gender = agender;
6 cout << "Person initialized" << endl;
7 }
8 private:
9 string name;
10 bool gender;
11 };
12 class Student : public Person{
13 int department;
14 string id;
15 public:
16 Student() :department(11), id("0509342") {
17 cout << "Student initialized" << endl;
18 }
19 };
20 int main() {
21 }
在这个派生类的构造函数中,没有给基类的构造函数任何参数,基类中的默认构造函数会被调用。
如果基类的构造函数需要参数,那么需要使用初始化列表为基类构造提供参数。
1 class Person {
2 public:
3 Person(string aname, bool agender) {
4 name = aname;
5 gender = agender;
6 }
7 private:
8 string name;
9 bool gender;
10 };
11 class Student : public Person {
12 public:
13 Student(string name, bool gender, int dep)
14 : department(dep) , Person ( name, gender )
15 {
16 }
17 private:
18 int department;
19 };
在一个多层的继承结构里面,创建一个派生类对象时,所有的基类的构造函数都会被调用。
1 class Person {
2 public:
3 Person( ) { cout << "construct Person"; }
4 };
5 class Student : public Person {
6 public:
7 Student( ) { cout << "construct Student"; }
8 };
9 class GradStudent : public Student {
10 public:
11 GradStudent( ) { cout << "construct GradStudent"; }
12 };
13 int main( ) {
14 GradStudent gs;
15 }
类的完整实现实例:
1 class Person {
2 string name;
3 bool gender;
4 public:
5 Person(string n, bool g) : name(n), gender(g) {
6 }
7 };
8 class Teacher : public Person {
9 int salary;
10 int level;
11 public:
12 Teacher(string n, bool g, int s, int l) : Person(n, g), salary(s), level(l) {
13 }
14 };
15 class Student: public Person {
16 string id;
17 int department;
18 public:
19 Student(string n, bool g, string i, int d) : Person(n, g), id(i), department(d) {
20 }
21 };
22 class GradStudent: public Student {
23 Teacher *tutor;
24 public:
25 GradStudent(string n, bool g, string i, int d, Teacher *t)
26 : Student(n, g, i, d), tutor(t) {
27 }
28 };
派生类对象被销毁时,基类的析构函数会被自动调用。如果派生类和基类都有析构函数,那么他们都会被调用。
派生类构造和析构顺序
1 class Person {
2 public:
3 Person() { cout << "construct Person" << endl; }
4 ~Person() { cout << "destruct Person" << endl; }
5 };
6 class Student : public Person{
7 public:
8 Student() { cout << "construct Student" <<endl; }
9 ~Student() { cout << "destruct Student" <<endl; }
10 };
11 int main() {
12 Student s;
13 Person p;
14 }
派生类析构函数只负责派生类增加部分的分配资源的析构。
1 class Base {
2 public:
3 Base() {
4 s = new char[3];
5 }
6 ~Base() {
7 delete[ ] s;
8 }
9 private:
10 char *s;
11 };
12 class Derived : public Base {
13 public:
14 Derived() {
15 sd = new char[5];
16 }
17 ~Derived () {
18 delete[ ] sd;
19 }
20 private:
21 char *sd;
22 };
23 int main() {
24 Base b;
25 Derived d;
26 }
5. 派生类对象和对象指针的复制
有继承关系的对象复制
1 Student s("jack", Date(1980, 8, 8), true, 970101, 10);
2 Person p = s; //slice
3 Student s = p; //error
4
5 Student *pD = new Student("jack", Date(1980, 8, 8), true, 970101, 10);
6 Person *pB = pD; //correct 基类指针指向派生类对象
7 Person *pB2 = new Student("jack", Date(1980, 8, 8), true, 970101, 10);
8 pB = new Teacher; //多态性在这里体现
9 Student &rd = s;
10 Person &rp = p;
11 Student &rd2 = p; //error
12 Person &rp2 = s; // ok
13
指针类型的转换
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();
7. 虚基类
虚基类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 };
8. 保护继承和私有继承
9. 多态与虚函数
相同的指令,作用在不同类型的对象上,产生不同动作。 比如:很多动物,有猫、狗、老虎等等。现在让所有的动物做一个“叫”的动作,结果…… 比如:有很多图形,有直线、方形、圆形、椭圆等等。现在让所有的图形把自己画在屏幕上,结果……
现在要求写一个函数printall显示所有人的详细信息,应该怎么做?
1 class Person {
2 enum PersonType{ P, S,T, G } type; //用来区分学生还是教师
3 };
4 void printall( Person *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 Person {
2 public:
3 void print(){ cout << name << birth <<gender; }
4 };
5 class Student : public Person{
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: virtual void draw();
3 //no virtual function in base
4 };
5 class Rectangle : public Shape{
6 public:
7 void draw();
8 };
9 class Circle :public Shape {
10 public:
11 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: virtual void draw();
3 };
4 class Rectangle : public Shape{
5 public:
6 virtual void draw(int a);
7 };
8 class Circle :public Shape {
9 public:
10 virtual void draw();
11 };
12 void f(Shape* s) {
13 s->draw();
14 }
15 int main() {
16 Shape *s = new Rectangle;
17 f( s );
18 Shape *c = new Circle;
19 f( c );
20 }
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 }
10. 纯虚函数和抽象类
问题:某些基类的虚函数没有实现的意义
纯虚函数(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)对象的基类
11. 运行时类型信息
问题:某些情况下,光靠多态性不够用,必须得到对象准确的类型信息
两种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类对象的引用
用法:
12. 面向对象设计方法
- 确定程序中需要的类及每个类需要的操作
- 将不同类的共同的操作提取到基类
- 通过基类接口来操纵各种不同的对象
- 可以通过增加新的派生类来扩展系统
例子:
- 复合文档(composite模式):包含文字、图形的文档
- 一组数据,有多种表现形式(observer模式)(比如表格、柱状图、柄图)
设计模式:
前人成功经验总结的可以重复利用的面向对象软件设计的范例。用处:套用前人范式以面向对象思想解决问题,设计出健壮、可复用、支持变化的软件。参考书:《设计模式——可复用面向对象软件的基础》——GoF(Erich Gamma等四人)