340 likes | 479 Views
继承与派生. 继承 (inheritance) : 继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。 这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。 体现了由简单到复杂的认识过程 。.
E N D
继承与派生 继承(inheritance): 继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。 通过继承(inheritance)可对类(class)分层, C++通过类的派生(class derivation)的机制来支持继承。被继承的类称为基类(base class)或超类(superclass),新的类为派生类(derived class)或子类(subclass)。 派生反映了事物之间的联系,事物的共性与个性之间的关系。 派生与独立设计若干相关的类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程实现代码复用。
继承与派生 继承与派生的概念 虚基类 派生类的构造函数与析构函数 派生类应用讨论 多重继承与派生类成员标识
继承与派生的概念 一、类的派生与继承 二、公有派生与私有派生
一、类的派生与继承 1、派生类的定义格式: class 派生类名:访问限定符 基类名1,访问限定符 基类名2,……,访问限定符 基类名n { private: 成员表1;//派生类增加或替代的私有成员 public: 成员表2;//派生类增加或替代的公有成员 protected: 成员表3;//派生类增加或替代的保护成员 };//分号不可少 其中基类1,基类2,……是已声明的类。 在派生类定义的类体中给出的成员称为派生类成员,它们是新增加成员,它们给派生类添加了不同于基类的新的属性和功能。派生类成员也包括取代基类成员的更新成员。
2、多重继承: 如果一个派生类可以同时有多个基类,称为多重继承(multiple-inheritance)。 3、单继承: 派生类只有一个直接基类称为单继承(single-inheritance)。 基类1 基类2 …… 基类n 基类 派生类1 派生类2 派生类1 派生类2 一个基类可以直接派生出多个派生类 派生类可以由多个基类共同派生出来,称多重继承。 (a)多重继承 (b)单继承 多重继承与单继承
一、类的派生与继承 4、多层次继承: 在派生过程中,派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。直接参与派生出某类称为直接基类,而基类的基类,以及更深层的基类称为间接基类。 类族:同时一个基类可以直接派生出多个派生类。这样形成了一个相互关联的类族。如MFC就是这样的族类,它由一个CObject类派生出200个MFC类中的绝大多数。
1)、吸收基类的成员 不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收 2)、改造基类成员 声明一个和某基类成员同名的新成员,派生类中的新成员就屏蔽了基类同名成员称为同名覆盖(override) 3)、发展新成员 派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。 4)、重写构造函数与析构函数 5、派生编程步骤: 编制派生类时可分四步
注意: • 在第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。 • 第三步中,独有的新成员才是继承与派生的核心特征。 • 第四步是重写构造函数与析构函数,派生类不继承这两种函数。不管原来的函数是否可用一律重写可免出错。
6、访问控制: 亦称为继承方式,是对基类成员进一步的限制,访问控制有三种: 公有(public)方式,亦称公有继承 保护(protected)方式,亦称保护继承 私有(private)方式, 亦称私有继承。 派生方式 基类中的访问限定 在派生类中对基类成员的访问限定 在派生类对象外访问派生类对象的基类成员 公有派生 public public 可直接访问 protected protected 不可直接访问 private 不可直接访问 不可直接访问 私有派生 public private 不可直接访问 protected private 不可直接访问 private 不可直接访问 不可直接访问
例题:分析下列程序的访问权限,并回答所提出的问题例题:分析下列程序的访问权限,并回答所提出的问题 #include<iostream.h> class A { public :void f1(); protected:int j1; private:int i1; }; class B:public A { public: void f2(); protected: int j2; private: int i2; }; class C:public B { public: void f3(); }; 回答下列问题: 1、派生类B中成员函数f2()能否访问基类A 中的成员:f1(),i1,j1? 2、派生类B的对象能否访问基类A 中的成员:f1(),i1,,j1? 3、派生类C中的成员函数能否访问基类B 中的成员:f2(),i2,,j2?
派生类的构造函数与析构函数 1、派生类构造函数的定义: 派生类名::派生类名(参数总表):基类名1(参数名表1),基类名2(参数名表2),……,基类名n(参数名表n),成员对象名1(成员对象参数名表1),……,成员对象名m(成员对象参数名表m) { ……//派生类新增成员的初始化; } //所列出的成员对象名全部为新增成员对象的名字 注意: 1)在构造函数的声明中,冒号及冒号以后部分必须略去。 2)基类名仅指直接基类,写了底层基类,编译器认为出错。 3)冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。
2、派生类构造函数各部分执行次序: 1)调用基类构造函数,按它们在派生类定义的先后顺序,顺序调用。 2)调用成员对象的构造函数,按它们在类定义中声明的先后顺序,顺序调用。 3)派生类的构造函数体中的操作。 注意: 1)在派生类构造函数中,只要基类不是使用无参的默认构造函数都要显式给出基类名和参数表。 2)如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的默认构造函数。 3)如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数。
3、析构函数: 析构函数的功能是作善后处理工作。 派生类的析构函数只需把派生类新增一般成员处理好就可以了,而对新增的成员对象和基类的善后工作,系统会自己调用成员对象和基类的析构函数来完成。 析构函数各部分执行次序与构造函数相反,首先对派生类新增一般成员析构,然后对新增对象成员析构,最后对基类成员析构。
例题:分析下列程序 的输出结果 代码一: #include<iostream.h> class A { public: A(){a=0;cout<<“A’s default constructor called.\n”; A(int i){a=i,;cout<<“A’s constructor called.\n”); ~A(){cout<<“ A’s destructor called.\n”;} void Print() const {cout<<a<<“,”} int GetA(){return a;} private: int a; }
class B:public A { public: B () {b=0; cout<<“A’s default constructor called.\n”;} B(int I,int j,int k); ~B(){cout<<“B’s destructor called.\n”;} void Print(); private: int b; A aa; }; B::B(int I,int j,int k):A(i),aa(j) { b=k; cout<<“B’s constructor called”; } void B:;Print() {A;:Print(); cout<<b<<“,”<<aa.Geta()<<endl; } void main() { B bb[2]; bb[0]=B(1,2,5); bb[1]={3,4,7}; for(int i=0;i<2;i++) bb[i].Print();}
代码二: #include<iostream.h> class M {public: M(){m1=m2=0;} M(int I,int j){m1=I;m2=j;} void print(){cout<<m1<<“,”<<m2<<“,”; ~M(){cout<<“M’s destructor called.\n”;} private: int m1,m2; }; class N:public M {public: N(){n=0;} N(int I,int j,int k); void print() {M::print(); cout<<n<<endl; } ~N() {cout<<“N’s destructor called\n”;} private: int n;} N::N(int I,int j,int k):M(I,j),n(k){} void mian() { N n1(5,6,7); n2(-2,-3,-4); n1.print(),n2.print();}
例题:由“人”类派生出“学生”类。我们希望基类和派生类共享相同的公有方法,因此,只能采用公有派生来实现。例题:由“人”类派生出“学生”类。我们希望基类和派生类共享相同的公有方法,因此,只能采用公有派生来实现。 人类: enum Tsex{mid,man,woman}; class Person{ string IdPerson; //身份证号,18位数字 string Name; //姓名 Tsex Sex; //性别 int Birthday; //生日,格式1986年8月18日写作19860818 string HomeAddress; //家庭地址 public: Person(string, string,Tsex,int, string);//构造函数 Person(); //默认的构造函数 ~Person(); //析构函数
//接口方法: void SetName(string); //修改名字 string GetName(){return Name;}//提取名字 void SetSex(Tsex sex){Sex=sex;} //修改性别 Tsex GetSex(){return Sex;} //提取性别 void SetId(string id){IdPerson=id;}//修改身份证号 string GetId(){return IdPerson;}//提取身份证号 void SetBirth(int birthday) {Birthday=birthday;} //修改生日 int GetBirth(){return Birthday;} //提取生日 void SetHomeAdd(string ); //修改住址 string GetHomeAdd(){return HomeAddress;}//提取住址 void PrintPersonInfo(); //输出个人信息 };
派生的学生类: struct course{ string coursename; int grade;}; class Student:public Person{ //定义派生的学生类 string NoStudent; //学号 course cs[30]; //30门课程与成绩 public: Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud); //注意派生类构造函数声明方式 Student(); //默认派生类构造函数 ~Student(); //派生类析构函数 SetCourse(string ,int); //课程设置 int GetCourse(string ); //查找成绩 void PrintStudentInfo(); //打印学生情况 };
多重继承与派生类成员标识 人 教职工(单继承) 学生(单继承) 兼职教师(单继承) 教师(单继承) 工人(单继承) 行政人员(单继承) 研究生(单继承) 在职研究生 (多重继承) 行政人员兼教师 (多重继承) 研究生助教 (多重继承) 多重继承实例: 派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。 学校人员与人继承关系
多重继承与派生类成员标识 二义性问题: 在上图中,比如行政人员兼教师,在其基类教师中有一个“教职工编号”,另一基类行政人员中也有一个“教职工编号”,如果只讲教职工编号到底那是哪一个基类中的呢?或者这两者是一回事? 解决的方法: 1)通常采用作用域分辨符“::”,进行区别: 基类名::成员名; //数据成员 基类名::成员名(参数表); //函数成员 2)引入”虚基类”
例题:分析下列程序的输出结果 #include<iostream.h> class A { public: A(int i){a=I;cout<<“con A\n”; void print(){cout<<a<<endl; ~A(){cout<<“des A\n”;} private: int a; }; class B1:public A { public : B1(int I,int j):A(i){b1=j;cout<<“con B1\n”; void print() {A::print(); cout<<b1<<ednl;} ~B1(){cout<<des B1\n”}; private: int b1; }; class B2:public A { public: B2(int I,int j):A(i) {b2=j;cout<<“con B2\n”; }
private:int c; }; void main() { C c1(1,2,3,4,5); c1.print(); } result: con A1 con B1 con A con B2 con c 1 2 3 4 5 des C des B2 des A des B1 des A void print() {A::print(); cout<<b2<<ednl;} ~B2(){cout<<des B2\n”}; private: int b2; }; class C:public B1,B2 { public: C(int I,int j,int k,int l,int m):B1(I,j),B2(k,l),c(m) { cout<<“con C\n”;} void print() { B1::print(); B2::print(); cout<<c<<endl; } ~C(){cout<<“des C\n”;}
虚基类 一、虚基类(virtual base class)定义: class派生类名:virtual 访问限定符 基类类名{...}; 或 class派生类名:访问限定符virtual 基类类名{...}; 注意: virtual 关键字只对紧随其后的基类名起作用: 例如: class Student:virtualpublic Person{...}; class Employee:virtual public Person{...};
由于使用了虚基类,使得类A,类B,类C和类D之间的关系用DAG图示法表示如下:由于使用了虚基类,使得类A,类B,类C和类D之间的关系用DAG图示法表示如下: A{f(),a} B{b} C{c} D{g(),d} 例如: class A { public: void f(); protected: int a; }; class B:virtual public A { protected: int b;}; class C:virtual public A { protected: int c;}; class D:public B,public C { public: int g(); private: int d;};
说明:引进虚基类后,派生类的对象中只有一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针被称为虚基类指针。上例中,各类的存储结构如下图所示:说明:引进虚基类后,派生类的对象中只有一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针被称为虚基类指针。上例中,各类的存储结构如下图所示: B C D A 虚基类指针
二、虚基类的构造函数 如果一个派生类有一个直接或者间接的虚基类,那么派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果未列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。 从虚基类直接或者间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数的调用。但是,只有最后的派生类对象调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数的调用在执行中被忽略。 在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。
class C:virsual public A { public: C(const char *s1,const char *s2):A(s1) {cout<<s2<<endl; } }; class D:public B,public C { public: D(const char *s1,const char *s2,const *s3,const *s4):B(s1,s2),C(s1,s3),A(s1) { cout<<s4<<endl; }}; 例题:分析下列程序的输出结果 #include<iostream.h> class A { public: A(const char *s) { cout<<s<<endl;} ~A(){} }; class B:virtual public A { public: B(const char *s1,const char *s2):A(s1) {cout<<s2<<endl;}};
void main() { D *ptr=new D(“class A”,”class B”,”class C”,”class D”); delete ptr; } 运行结果: class A class B class C class D
例题:分析下列程序的输出结果 #include<iostream.h> class A { public: int n; }; class B:public A{}; class C:public A{}; class D:public B,public C { int getn(){return B::n;} }; void main() { D d; d.B::n=10; d.C::n=20; cout<<d.B::n<<","<<d.C::n<<endl; } D类是从类B和类C派生的而类B和类C又都是从类A派生的,但各有自己的副本。所以对于对象d,d.B::n与d.C::n是两个不同的数据成员它们互无联系。 所以输出为: 10,20
例题:假设图书馆的图书包含书名、编号、作者属性,读者包含姓名和借书证属性,每位读者最多可借5本书,编写程序列出某读者的借书情况。例题:假设图书馆的图书包含书名、编号、作者属性,读者包含姓名和借书证属性,每位读者最多可借5本书,编写程序列出某读者的借书情况。 分析:设计一个类,从它派生出图书类book和读者类reader,在reader类中有一个rentbook()成员函数用于借阅图书。 程序代码如下:#include<iostream.h> #include<string.h> class object { char name[20]; int no; public: object(){} object(char na[],int n) { strcpy(name,na);no=n; } void show() { cout<<name<<“(”<<no<<“)”; } };
class book:public object { char author[10]; public: book(){} book(char na[],int n,char auth[]):object(na,n) { strcpy(author,auth); } void showbook() { show(); cout<<"作者:"<<author; } }; void main() { book b1("C语言",100,"谭浩强"),b2("数据结构",110,"严蔚敏"); reader r1("王华",1234); r1.rentbook(b1); r1.rentbook(b2); r1.showreader(); }
class reader:public object { book rent[5]; int top; public: reader(char na[],int n):object(na,n){top=0;} void rentbook(book &b) { rent[top]=b; top++; } void showreader() { cout<<"读者:";show(); cout<<endl<<"所借图书:"<<endl; for(int i=0;i<top;i++) { cout<<" "<<i+1<<":"; // 5个空格 rent[i].show(); cout<<endl; } } }; void main() { book b1("C++语言",101,"谭浩强"),b2("数据结构",110,"严蔚敏"); reader r1("王华",1234); r1.rentbook(b1); r1.rentbook(b2); r1.showreader(); }
总结: 1. 派生类具有基类的所有成员。 2. 派生类的构造函数自动执行基类的构造函数,且基类的构造函数先执行。基类构造函数的参数由派生类传递。 3. 派生类中可对已有的成员进行重新定义。 4.可定义多重派生类。且可利用虚基类使派生类只保持一个基类的成员拷贝。