960 likes | 1.13k Views
第四章 继承与派生、多态性. 第一节 派生类. 汽车. 运输汽车. 专用汽车. 客车. 货车. 消防车. 洒水车. 一、 继承. 继承是面向对象程序设计的一个重要特性,它允许在既有类的基础上创建新的类,新类可以从一个或多个既有类中继承函数和数据,而且可以重新定义或加进新的数据和函数,从而形成类的层次或等级。既有类称为基类或父类,在它基础上建立的新类称为派生类、导出类或子类。. 继承性也是程序设计中的一个非常有用的、有力的特性,它可以让程序员在既有类的基础上,通过增加少量代码或修改少量代码的方法得到新的类,从而较好地解决了代码重用的问题。.
E N D
第四章 继承与派生、多态性
第一节 派生类
汽车 运输汽车 专用汽车 客车 货车 消防车 洒水车 一、 继承 继承是面向对象程序设计的一个重要特性,它允许在既有类的基础上创建新的类,新类可以从一个或多个既有类中继承函数和数据,而且可以重新定义或加进新的数据和函数,从而形成类的层次或等级。既有类称为基类或父类,在它基础上建立的新类称为派生类、导出类或子类。
继承性也是程序设计中的一个非常有用的、有力的特性,它可以让程序员在既有类的基础上,通过增加少量代码或修改少量代码的方法得到新的类,从而较好地解决了代码重用的问题。继承性也是程序设计中的一个非常有用的、有力的特性,它可以让程序员在既有类的基础上,通过增加少量代码或修改少量代码的方法得到新的类,从而较好地解决了代码重用的问题。 注意:一个派生类没有权利访问它的基类的私有数据,就像其他任何类一样。
二、 派生类的定义 1.声明一个派生类的一般格式为: class 派生类名:派生方式 基类名 { //派生类新增的数据成员和成员函数 }; class person{ char * name; int age; char sex; public: int getAge(){return age;} … };
class employee: public person{ char * department; float salary; public: float getSalary(){return salary;} … }; 2. 派生方式 :私有派生和公有派生 (1)公有派生 class employee:public person{ //… }; (2)私有派生 class employee:private person{ //… }; 派生方式默认为private,即私有派生。
两种派生方式的特点如下: (1)无论哪种派生方式,基类中的私有成员既不允许外部函数访问,也不允许派生类中的成员函数访问,但是可以通过基类提供的公有成员函数访问。 (2)公有派生与私有派生的不同点在于基类中的公有成员和保护成员在派生类中的访问属性。 ·公有派生时,基类中的所有公有成员在派生类中也都是公有的。基类中的所有保护成员在派生类中也都是保护的。 ·私有派生时,基类中的所有公有成员和保护成员只能成为派生类中的私有成员。
A A private public B B private或public public C C C类可以访问A类中 保护段和公有段的成员 C类不能访问 A类中的任何成员
void main() { derived obj;base bobj; obj.showxy(); //合法 obj.setx(10); //非法 bobj.setx(10); obj.setxy(3,20); //合法 bobj.showx(); obj.showx(); // 非法 bobj.showxy(); // 非法} class derived:private base{ int y; public: void setxy(int n,int m); {setx(n); y=m;} void showxy() {cout<<x<<y<<endl;} //非法 }; 例1: #include<iostream.h> class base{ int x; public: void setx(int n) {x=n;} void showx() {cout<<x<<endl;} };
class derived:public base{ int y; public: void sety(int n) {y=n;) void showy() {cout<<y<<endl;} }; 例2: #include<iostream.h> class base{ int x; public: void setx(int n) {x=n;} void showx() {cout<<x<<endl;} }; main() { derived obj; obj.setx(10); obj.sety(20); obj.showx(); obj.showy(); return 0;}
说明: (1)派生类从基类继承下来的成员可以当作是自己的成员一样使用。但不能访问基类的私有成员。 (2)派生类中继承来自基类的任何成员,其类范围仍属于原基类。 main(){ X v1;Y v2;Z v3; v1.get_ij(); v2.get_ij(); v2.get(); v3.get_ij(); v3.get(); v3.f();} class X{ protected: int i,j; public: get_ij(); }; class Y:private X { int k; public: void get() { i=1;j=2; k=3; get_ij(); }}; class Z:public Y {public: void f(){ i=1; //error j=2; //error k=3; //error }};
(3)在派生类中允许定义与基类中相同名称的数据成员或成员函数,此时基类中同名的成员被隐藏(或覆盖),使得编译器在派生类范围内看不见,但它们仍然被继承下来且是派生类的成员的一部分,如果要在派生类中使用基类的同名成员,必须指明类范围。(3)在派生类中允许定义与基类中相同名称的数据成员或成员函数,此时基类中同名的成员被隐藏(或覆盖),使得编译器在派生类范围内看不见,但它们仍然被继承下来且是派生类的成员的一部分,如果要在派生类中使用基类的同名成员,必须指明类范围。 例如: class X{ public: int f(); }; class Y:public X{ public: int f(); int g(); }; void Y::g() { f(); X::f(); } main() { Y obj; obj.f(); obj.X::f(); }
3. 派生类中的静态成员 不管是公有派生还是私有派生都不影响派生类对基类的静态成员的访问,但要求访问静态成员时,必须用“类名::成员”显示说明。 class B{ public: static void f(); void g(); }; class D:private B{}; class DD:public D{ void h(); }; void DD::h() { B::f(); f(); //error g(); //error } 静态成员的派生static成员受段约束符的限制,但不受访问描述符的限制。
4. 访问声明 C++提供的一种调节机制,它是私有派生方法的一种补充。 希望类C中能访问到类A的某几个成员,不全部一刀切 A class A{ int a; public: int b,c; int bf(); }; class B::private A{ int d; public: A::c; int e; int df(); }; private B private或public C
说明: (1)访问声明仅仅调整名字的访问,不可为它说明任何类型。 int A::c; //error (2)访问声明仅用于派生类中调整名字的访问权限,不允许在派生类中降低或提升基类成员的可访问性。基类的私有成员不能用于访问声明。 class B{ int b; protected: int c; public: int a; }; class D:private B{ protected: B::c; B::a; //error public: B::b; //error B::c; //error B::a; };
(3)对重载函数名的访问声明将调整基类中具有该名的所有函数的访问域;具有不同访问域的重载函数名不能用于访问声明;如果基类的一个成员在派生类中也把同一名字定义为一个成员,则不可调整它的访问。(3)对重载函数名的访问声明将调整基类中具有该名的所有函数的访问域;具有不同访问域的重载函数名不能用于访问声明;如果基类的一个成员在派生类中也把同一名字定义为一个成员,则不可调整它的访问。 class D:private B{ protected: float c; public: void fun(int); int same(int); B::temp; B::print; //error B::same; //error }; class B{ int b; void print(); public: int temp(int); int temp(int,float,char *); int temp(); void same(int,int); void print(int); };
三、保护成员的作用 class类名{ private: 私有数据成员和成员函数 protected: 保护数据成员和成员函数 public: 公有数据成员和成员函数 }; 保护成员可以被派生类的成员函数访问,但是对于外界是隐藏起来的,外部函数不能访问它。因此,为了便于派生类的访问,可以将基类私有成员中需要提供给派生类访问的成员定义为保护成员。
class derived:public base{ int k; public: void setk(int m) { k=m;} void setbasedata(int x1,int x2) {i=x1; j=x2;} int sum() {return i+j+k;} }; class base{ protected: int i,j; public: void setdata(int m,int n) { i=m; j=n;} }; void main() {derived d; d.setbasedata(3,5); d.sum(); } 若基类的数据成员被说明成保护成员,对其派生类而言,这些数据成员就如同在基类中被说明成公有的一样,派生类可直接对它们进行访问,提高了程序运行效率。
四、派生类的构造函数和析构函数 声明:派生类无法继承基类的构造函数。 派生类的构造函数除了必须负责初始化自身所定义的数据成员外,还必须负责调用基类的构造函数以初始化基类的数据成员 • 派生类构造函数的声明 派生类构造函数名(参数表):基类(参数表),对象成员名(参数表){ // … };
class base{ class mem{ int x1,x2; int y1,y2; public: public: base(int p1,int p2) mem(int p3,int p4) {x1=p1; {y1=p3; x2=p2; y2=p4; } } }; }; class derived:private base{ int x3; mem x4; public: derived(int p1,int p2,int p3,int p4,int p5): base(p1,p2),mem(p3,p4) {x3=p5;} }; 2. 构造函数的执行顺序 main() {derived d(17,18,1,2,-5); … } 首先基类,其次对象成员,最后派生类
class base{ int x; public: base(int i) {x=i;} }; class derived:private base{ int a; public: derived(int i):a(i*10),base(a){} }; 基类将得到一个未初始化的a 如:derived d(1);
说明: (1)若基类的构造函数不带参数,派生类不一定需要构造函数,然而,当基类的构造函数哪怕只带有一个参数,派生类都必须有构造函数。 (2)当派生类构造函数调用的是基类的default constructor,则初始值表可以省略。
class base{ int x; public: base() {x=3;} }; class derived:private base{ int a; public: void fn_d(int); … }; class base{ int x; public: base(int q) {x=q;} }; class derived:private base{ int a; public: derived(int i):base(i){ a=10; } };
class base1{ int x; public: void fn(); … }; class base2{ int y; public: base2(int q) {y=q;} }; class derived:private base1,public base2{ int a; public: derived(int i):base2(i){ a=10; } };
3. 析构函数 析构函数也不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用。执行顺序是先执行派生类的析构函数,再执行基类的析构函数,其顺序与执行构造函数时顺序正好相反。
#include <iostream> using namecpace std; class B1 //基类B1声明 { public: B1(int i) {cout<<"constructing B1 "<<i<<endl;} ~B1() {cout<<"destructing B1 "<<endl;} }; class B2 //基类B2声明 {public: B2(int j) {cout<<"constructing B2 "<<j<<endl;} ~B2() {cout<<"destructing B2 "<<endl;} }; class B3 //基类B3声明 {public: B3(){cout<<"constructing B3 *"<<endl;} ~B3() {cout<<"destructing B3 "<<endl;} };
class C: public B2, public B1, public B3 {public: C(int a, int b, int c, int d): B1(a),memberB2(d),memberB1(c),B2(b){} private: B1 x1; B2 x2; B3 x3; }; void main() { C obj(1,2,3,4); }
运行结果: constructing B2 2 constructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 constructing B3 * destructing B3 destructing B2 destructing B1 destructing B3 destructing B1 destructing B2
第二节 继承
1. 方法的继承 当利用继承派生出子类后,根据需要在派生类中只定义与它的基类不同的内容就可以了。 Point类——继承的一个例子 //point.h enum Boolean{false,true}; class Location{ protected: int x,y; public: Location(int InitX,int InitY); int GetX(); int GetY(); };
class Point:public Location{ protected: Boolean Visible; public: Point(int InitX,int InitY); void Show(); void Hide(); Boolean IsVisible(); void MoveTo(int NewX, int NewY); };
//point.cpp #include “point.h” #include “graphics.h” Location::Location(int InitX,int InitY) { x= InitX;Y=InitY;} int Location::GetX() { return x;} int Location::GetY() { return y;} Point::Point(int InitX,int InitY):Location(InitX, InitY) { Visible=false;} void Point::Show() { Visible=true;putpixel(x,y,getcolor());}
void Point::Hide() { Visible=false;putpixel(x,y,getbkcolor());} Boolean Point::IsVisible() { return Visible;} void Point::MoveTo(int NewX,int NewY) { Hide();x=NewX;y=NewY;Show();} //circle.cpp #include “graphics.h” #include “conio.h” #include “point.cpp” class Circle:Point{ int Radius;
public: Circle(int InitX,int InitY,int InitRadius); void Show(); void Hide(); void Expand(int ExpandBy); void Contract(int ContractBy); }; Circle::Circle(int InitX,int InitY,int InitR): Point(InitX,InitY) { Radius=InitR;} void Circle::Show() {Visible=true;circle(X,Y,Radius);}
void Circle::Hide() { unsigned int tempcolor; tempcolor=getcolor(); setcolor(getbkcolor()); Visible=false; circle(x,y,Radius); setcolor(tempcolor); } void Circle::Expand(int ExpandBy) { Hide(); Radius+=ExpandBy; if(Radius<0) Radius=0; Show(); }
void Circle::Contract(int ContractBy) { Expand(-ContracrtBy); } void Circle::MoveTo(int NewX,int NewY) { Hide();x=NewX;y=NewY;Show(); } //#include “circle.cpp” main() { int graphdriver=DETECT; int graphmode; initgraph(&graphdriver,&graphmode,”…\\bgi”); Circle Mycircle(100,200,500); Mycircle.Show(); getch();
Mycircle.MoveTo(200,500); getch(); Mycircle.Expand(50); getch(); Mycircle.Contract(75); getch(); closegraph(); } 练习:定义一个数组类,派生出队列和堆栈
#include “iostream.h” class Array{ protected: int *p; int size; public: Array(int a); ~Array(); int &operator[](int x); void expend(int offset); };
class Queue:public Array{ int first,last; public: Queue(int a):Array(a) { first=last=0;} void join_queue(int x) { if (last==size) expend(1); p[last++]=x; } int get() { if (first<last) return p[first++]; else {cout<<“空队”;return –1;} } };
class Stack:public Array{ int top; public: Stack(int a):Array(a) { top=0;} void push(int x) { if (top==size) expend(1); p[top++]=x; } int pop() { if (top>0) return p[--top]; else {cout<<“空堆栈”;return –1;} } };
2. 数据类型转换 将一种类型的值转换为另一种类型的值。 对于预定义类型,C++提供了两种类型转换: (1)标准类型转换(隐式类型转换) ——当char或short类型对象与int类型对象进行运算时,将char、shortint ——当两个操作对象不一致时,在算术运算前,级别低的自动转换为级别高的类型。 ——赋值表达式E1=E2,E2的值需转换为E1的类型。 ——实参转换为形参类型,返回表达式类型转换为函数返回值类型。
(2)显示类型转换 ——强制转换表示法 (类型名)表达式 ——函数法 类型名(表达式) 这里介绍用构造函数和转换函数实现类类型的转换
(1)通过构造函数将其它数据类型转换成用户定义的类类型(1)通过构造函数将其它数据类型转换成用户定义的类类型 只具有一个参数的构造函数说明了一种从参数类型到该类类型的转换。 class time{ long sec,min,hour; public: time(long s); time(long m,long s); time(long h,long m,long s); };
time::time(long s) {hour=s/3600; min=(s-hour*3600)/60; sec=s-(hour*3600+min*60); } time::time(long m,long s) {hour=m/60; min=m-hour*60; sec=s;} time::time(long s) {hour=h; min=m; sec=s;} void main() { time p1(13521); time p2(3,5,1); p1=22030; //… } 当程序中提供了单参数的构造函数后,若编译器发现后面的程序中需要使用类型转换时(如赋值语句,函数参数传递等),便会自动调用该构造函数。
(2)用类型转换函数将用户定义的类类型转换成其它的数据类型(2)用类型转换函数将用户定义的类类型转换成其它的数据类型 如: time p1=13521; long i=p1; 将类型为X的对象转换为类型为type的对象 转换函数的语法: X::operator type() { //… return type类型的对象 } time::operator long() { long temp; temp=hour*60+min*60+sec*60; return temp; }
说明: a. 转换函数必须是类的成员函数(不能是友元函数) b. 转换函数不可以指定其返回值类型 c. 转换函数参数行不可以有任何参数 d. 编译器也会自动调用转换函数以完成类型转换工作 class X{ public: operator int(); }; void f(X a) { int i=int(a); i=(int)a; i=a; } 相当于调用: a.operator int();
e. 用户定义的类型转换函数只有在它们无二义性时才能隐式地使用 设有: x(int); operator int(); 则: a=a+i; 产生二义性 f. 类型转换函数可以被继承,可以是虚函数的,但不能被重载,因为它没有参数 1.a+i可以使用a.operator int()+i 2. a+i可以使用a+X(i) (3)基类指针和派生类指针与基类对象和派生类对象的混合匹配 a. 直接用基类指针引用基类的对象 b. 直接用派生类指针引用派生类的对象
class B{ public: void f(){cout<<“in class B!”} }; class D:public B{ public: void f() {cout<<“in class D!”} void g() {cout<<“in function G!”} }; class E:public B{ public: void f() {cout<<“in class E!”} }; main() { B *pb, D *pd, E *pe; B b_ob; D d_ob; E e_ob; pb=&b_ob; pb->f(); pd=&d_ob; pd->f(); pe=&e_ob; pe->f(); // ((D *)pb)->g(); } c. 用基类指针引用一个派生类对象,但是只能引用基类的成员。若要用基类指针访问其派生类的特定成员,必须将基类指针用显示类型转换为派生类指针。 结论:通过指针引起的普通成员函数调用,仅仅与指针的类型有关,而与此刻正在指向什么对象无关。 返回
解决办法: class E:public B{ public: void f(); }; main() { B *pb; B b_ob; D d_ob; E e_ob; pb=&b_ob; pb->f(); pb=&d_ob; pb->f(); pb=&e_ob; pb->f(); ((D *)pb)->g(); } class B{ public: virtual void f(); }; class D:public B{ public: void f(); void g(); };
d. 用派生类指针引用一个基类对象会导致语法错误,派生类指针必须先强制转换为基类指针。 3. 多重继承 单继承:每个类都只有一个直接基类的继承关系 多继承:派生类具有多个直接基类的继承关系 A A B A B B C C C