第四章 继承与派生、多态性
description
Transcript of 第四章 继承与派生、多态性
第四章
继承与派生、多态性
第一节
派生类
继承是面向对象程序设计的一个重要特性,它允许在既有类的基础上创建新的类,新类可以从一个或多个既有类中继承函数和数据,而且可以重新定义或加进新的数据和函数,从而形成类的层次或等级。既有类称为基类或父类,在它基础上建立的新类称为派生类、导出类或子类。
汽车
运输汽车 专用汽车
客车 货车 消防车 洒水车
一、 继承
继承性也是程序设计中的一个非常有用的、有力的特性,它可以让程序员在既有类的基础上,通过增加少量代码或修改少量代码的方法得到新的类,从而较好地解决了代码重用的问题。
注意:一个派生类没有权利访问它的基类的私有数据,就像其他任何类一样。
二、 派生类的定义
1. 声明一个派生类的一般格式为: class 派生类名 : 派生方式 基类名
{ // 派生类新增的数据成员和成员函数 } ;
class person{char * name;int age;char sex;public:int getAge(){return age;} …};
2. 派生方式 :私有派生和公有派生 (1) 公有派生 class employee:public person{ //… } ;
(2) 私有派生 class employee:private person{ //… } ;
派生方式默认为 private ,即私有派生。
class employee: public person{char * department;float salary;public:float getSalary(){return salary;} …};
两种派生方式的特点如下:
(1) 无论哪种派生方式,基类中的私有成员既不允许外部函数访问,也不允许派生类中的成员函数访问,但是可以通过基类提供的公有成员函数访问。
(2) 公有派生与私有派生的不同点在于基类中的公有成员和保护成员在派生类中的访问属性。 · 公有派生时,基类中的所有公有成员在派生类中也都是公有的。基类中的所有保护成员在派生类中也都是保护的。 · 私有派生时,基类中的所有公有成员和保护成员只能成为派生类中的私有成员。
A
B
C
A
B
C
private
private 或public
public
public
C 类不能访问A 类中的任何成员
C 类可以访问 A 类中保护段和公有段的成员
例 1 : #include<iostream.h>class base{ int x ; public : void setx(int n) {x = n ; } void showx() {cout<<x<<endl;} } ;
class derived:private base{ int y ;public : void setxy(int n,int m);
{setx(n) ; y=m ; } void showxy() {cout<<x<<y<<endl;} // 非法 };
void main(){ derived obj ; base bobj; obj.showxy() ; // 合法 obj.setx(10) ; // 非法 bobj.setx(10); obj.setxy(3,20) ; // 合法 bobj.showx(); obj.showx() ; // 非法 bobj.showxy(); // 非法 }
例 2 :#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 ; } } ;main(){ derived obj ; obj.setx(10) ;
obj.sety(20) ; obj.showx() ;
obj.showy() ; return 0 ; }
说明: (1) 派生类从基类继承下来的成员可以当作是自己的成员一样使用。但不能访问基类的私有成员 。 (2) 派生类中继承来自基类的任何成员,其类范围仍属于原基类。
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 }};
main(){ X v1;Y v2;Z v3; v1.get_ij() ; v2.get_ij(); v2.get(); v3.get_ij(); v3.get(); v3.f();}
( 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(); //errorg(); //error
}静态成员的派生 static 成员受段约束符的限制,但不受访问描述符的限制。
4. 访问声明C++ 提供的一种调节机制,它是私有派生方法的一种补充。
希望类 C 中能访问到类 A 的某几个成员,不全部一刀切A
B
C
private
private 或public
class A{int a;
public:int b,c;int bf();
}; class B::private A{
int d;
public:A::c;int e;int df();
};
说明:( 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; //errorB::c; //error
B::a; };
( 3 )对重载函数名的访问声明将调整基类中具有该名的所有函数的访问域;具有不同访问域的重载函数名不能用于访问声明;如果基类的一个成员在派生类中也把同一名字定义为一个成员,则不可调整它的访问。
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 D:private B{ protected:
float c; public: void fun(int); int same(int); B::temp; B::print; //error B::same; //error};
三、保护成员的作用 class 类名 { private : 私有数据成员和成员函数 protected : 保护数据成员和成员函数 public : 公有数据成员和成员函数 } ;
保护成员可以被派生类的成员函数访问,但是对于外界是隐藏起来的,外部函数不能访问它。因此,为了便于派生类的访问,可以将基类私有成员中需要提供给派生类访问的成员定义为保护成员。
class base{protected: int i,j;public: void setdata(int m,int n) { i=m; j=n;}};
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;}};
void main() {derived d; d.setbasedata(3,5); d.sum();}
若基类的数据成员被说明成保护成员,对其派生类而言,这些数据成员就如同在基类中被说明成公有的一样,派生类可直接对它们进行访问,提高了程序运行效率。
四、派生类的构造函数和析构函数
声明:派生类无法继承基类的构造函数。
派生类的构造函数除了必须负责初始化自身所定义的数据成员外,还必须负责调用基类的构造函数以初始化基类的数据成员
1. 派生类构造函数的声明
派生类构造函数名(参数表):基类(参数表),对象成员名(参数表) {
// …} ;
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 2constructing B1 1constructing B3 *constructing B1 3constructing B2 4constructing B3 *destructing B3destructing B2destructing B1destructing B3destructing B1destructing 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 )通过构造函数将其它数据类型转换成用户定义的类类型
只具有一个参数的构造函数说明了一种从参数类型到该类类型的转换。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) 用类型转换函数将用户定义的类类型转换成其它的数据类型
将类型为 X 的对象转换为类型为 type 的对象转换函数的语法: X::operator type() { //…
return type 类型的对象 }
如: time p1=13521; long i=p1;
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. 类型转换函数可以被继承,可以是虚函数的,但不能被重载,因为它没有参数( 3 )基类指针和派生类指针与基类对象和派生类对象的混合匹配a. 直接用基类指针引用基类的对象b. 直接用派生类指针引用派生类的对象
1.a+i 可以使用 a.operator int()+i2. a+i 可以使用 a+X(i)
c. 用基类指针引用一个派生类对象,但是只能引用基类的成员。若要用基类指针访问其派生类的特定成员,必须将基类指针用显示类型转换为派生类指针。
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();}
返回
class B{ public: virtual void f();}; class D:public B{ public: void f();
void g();};
pb=&b_ob; pb->f(); pb=&d_ob; pb->f(); pb=&e_ob; pb->f(); ((D *)pb)->g();}
class E:public B{ public: void f();};main(){ B *pb; B b_ob; D d_ob; E e_ob;
解决办法:
d. 用派生类指针引用一个基类对象会导致语法错误,派生类指针必须先强制转换为基类指针。
3. 多 重 继承 单继承:每个类都只有一个直接基类的继承关系
多继承:派生类具有多个直接基类的继承关系A A B A B
B C C
C
( 1 )多继承的派生类构造函数必须负责所有基类中成员的初始化。 derived::derived(arguments):base1(args),…basen(args)
{ //…};
基类构造函数的调用顺序决定于定义派生类时所指明基类的顺序。 class A{ int a; public:
void fn_A();};
class B{ int b; public:
void fn_B();};
class C:public A,public B{ … };
C::C(int x1,int x2)::B(x1),A(x2){…}
例子
( 2 )注意多继承下的二义性:成员使用的二义性
class A{ public:
void show();};
class B{ public:
void show();};
class C:public A,public B{ };
main(){ C cc; cc.show(); // 二义性}
解决办法:
a. 利用范围运算符指明其所属类范围 cc.A::show();
b. 在派生类中重新定义一与基类同名的成员覆盖基类成员
L
A B
C
L
top_class
A B
C
top_class
top_class: i, inci(),display() A: j, inci(),display() i, inci(),display() B: k, inci(),display() i, inci(),display() C: m, inci(),display()
j, inci(),display() i, inci(),display() k, inci(),display() i, inci(),display()
第三节
多态性与重载
一、多态性
一个名字,多种语义;一个界面,多种实现
编译时的多态性:函数重载和运算符重载运行时多态性:虚函数
二、函数重载
对于普通成员函数的重载,可表达为下面两种方式:( 1 )在一个类说明中重载 例子( 2 )基类的成员函数在派生类中重载 例子
重载函数的访问是在编译时区分的,有以下两种区分方法:
( 1 )根据参数的特征加以区分
show(int,char);show(char*,float);( 2 )使用 :: 加以区分A::show();B::show();
三、运算符重载
1. 运算符 函数运算符函数是一种特殊的函数,用以完成对
运算符重载的功能。其一般语法形式为:type operator@(参数表) {…}
运算符函数只能定义为两种方式:类的成员函数和类的友元函数
2. 一元和二元 运算符一元运算符: 使用: aa@ 或 @aa
解释: aa.operator@();operator@(aa);
例如: class three_d{
int x,y,z; publlic:
three_d& operator++( );friend three_d operator++(three_d&);
};
three_d ob;++ ob; 返回
ob.operator++();operator++(ob);
二元运算符: 使用: aa@bb解释: aa.operator@(bb);
operator@(aa,bb);
class three_d{int x,y,z;
publlic:three_d operator+ ( three_d);friend three_d operator+ (three_d,three_d);
};
three_d ob1,ob2;ob1+ob2;
#include <iostream.h> class three_d{
int x,y,z; publlic:
three_d& operator= ( three_d t) { x=t.x;y=t.y;z=t.z;return *this; }friend three_d operator+ (three_d s,three_d t) { three_d temp;
temp.x=s.x+t.x;temp.y=s.y+t.y;temp.z=s.z+t.z;return temp; }
three_d& operator++ () { x++;y++;z++;return *this; }void show() { cout<<x<<“,”<<y<<“,”<<z<<endl; }void assign(int mx,int my,int mz) { x=mx;y=my;z=mz; }
}; main(){ three_d a,b,c;
a.assign(1,2,3); b.assign(10,10,10);a.show(); b.show(); c=a+b; c.show();c=a+b+c; c.show(); c=b=a; c.show();b.show(); c++; c.show(); }
注意:
( 1 )在 C++ 中几乎所有的运算符(除“ .”,“.*”,“::”,“?:”外)都可以被重载。
( 3 )当重载为类的成员函数时,运算符重载函数的形参个数要比运算符操作数个数少一个;若重载为友元函数,则参数个数与操作数个数相同。
( 2 )运算符的重载既不会改变原运算符的优先级和结合性,也不会改变使用运算符的语法和操作数个数。
( 5 )成员函数与友元函数的选用:若一运算符的操作需要修改类对象的状态,
则它应该是成员函数。需要左值操作数的运算符(如 = , *= , ++ )的重载最好用成员函数;相反,如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则该运算符重载必须用友元,而不是成员函数。
( 4 )当重载为友元函数时,“ =” 、“()”、“ []” 、“ ->” 等运算符不能重载。
class complex{int rem,imp;
public:complex(int a){rem=a;imp=0;}complex(int a,int b){rem=a;imp=b;}complex operator+(complex ob) { complex temp;
temp.rem=rem+ob.rem;temp.imp=imp+ob.imp;return temp; };
main(){ complex z(2,3),k(3,4);
z=z+27; z=27+z; }
相当于调用:z.operator+(complex(27));
27.operator+(z); 这里应使用友元函数。
friend complex operator+(complex a,complex b)
{ complex temp;temp.rem=a.rem+b.rem;temp.imp=a.imp+b.imp;return temp; }
相当于调用:operator+(complex(27),z)
3. 几种特殊运算符的重载
( 1 )重载 ++ 和— 例子 用成员函数重载 ++
three_d& three_d::operator++(){ x++;
y++;z++;return *this;
}
用友元函数对象参数重载 ++ three_d operator++(three_d opl){ opl.x++;
opl.y++;opl.z++;return opl;
} // 不能改变对象的值
用友元函数指针参数重载 ++ three_d operator++(three_d *opl){ opl->x++;
opl->y++;opl->z++;return *opl;
} //C++ 不知道如何激活它
用友元函数引用参数重载 ++ three_d operator++(three_d &opl){ opl.x++;
opl.y++;opl.z++;return opl;
}
( 2 ) ++ 、 -- 的前缀形式和后缀形式如何区别运算符“ ++” 和“ ”的两种不同形式呢?
在 C++ 中规定:•对于前缀形式的递增 /递减运算符,以和一般的 一元运算符同样的方式将它们重载为:
type& type::operator++() type& operator++(type&) 或 type& type::operator--() type& operator--(type&)
•对于后缀形式的递增 /递减运算符,将它们重载为: type type::operator++(int) type operator++(type&,int)
或 type type::operator--(int) type operator--(type&,int)
调用: ++a; // 隐式调用 a.operator++(); // 显示调用,成员函数 operator++(a); // 显示调用,友元函数
调用: a++; // 隐式调用 a.operator++(0); // 显示调用,成员函数 operator++(a,0); // 显示调用,友元函数
class A{ int value; public: A(int x):value(x){} A& operator++(); A operator++(int); void display(){
cout<<“the value is ”<<value<<endl;}};
A& A::operator++() { value++; return *this;}
A A::operator++(int) { A temp(*this); value++; return temp;} void main(){ A a(20); a.display(); (a++).display(); a.display(); (++a).display(); ++(++a); a.display(); }
( 2 )重载 =
重载格式: X& X::operator=(const X&){
//复制 X 的成员}
注意: i. 只能被成员函数重载,不能被友元函数重载
ii. 不能被继承
iii. 若用户没有为一个类重载赋值运算符,编译程序将生成一个缺省的赋值运算符,用于把源对象逐域拷贝到目的对象。(注意指针悬挂)
( 3 )重载()和 []
重载函数调用运算符():函数调用的一般形式为:
基本表达式(表达式表) ()可以看作一个二元运算符,运算对象为基本表达式和表达式表
重载下标运算符 [] :下标运算:
基本表达式 [表达式 ] [] 也是一个二元运算符,运算对象为基本表达式和表达式注意:()和 [] 只能用成员函数重载,不能用友元函数重载
例如:统计文件中每一单词的出现次数
数据结构: 用一结构描述单词 name及单词出现的次数 val
struct pair{char *name;int val;
}; 一个文件中的所有单词及出现的次数可用 pair结构的数组来描述。程序功能:查找 /填写功能和迭代功能
查找 /填写功能:到结构数组中查找单词 (char *p),若找到,将其 val域递增;否则将该单词填写到结构数组中。 重载运算符 [] ,功能为在结构数组 vec 中查找单词 (char *p), 并返回该单词出现的次数,则上述功能可用下列语句实现:
vec[p]++;
迭代功能:在数组 vec 中顺序提取每个 pair元素。重载运算符(),功能为: if (元素未取完) 返回一个元素并将指针指向下一个元素 else 返回 0值
#include “iostream.h”#include “string.h”struct pair{
char *name;int val;
}; class assoc_iterator; class assoc{
friend assoc_iterator;pair *vec;int max; // 数组最大值int free; //单词数
public:assoc(int);int& operator[](char *);
};
class assoc_iterator{ assoc *cs;
int i; public:
assoc_iterator(assoc &s) {cs=&s;i=0;}pair *operator()() {return ((i<cs->free)? (&cs->vec[i++]):0); }
}; assoc::assoc(int a) { max=(a>16)?a:16;
free=0;vec=new pair[max];
}
int& assoc::operator[](char *p){ pair *pp; for(pp=vec;pp<vec+free;pp++)
if (strcmp(p,pp->name)==0)return pp->val;
if (free==max) //vec溢出,增长数组{ pair *nvec=new pair[max*2]; for (int i=0;i<max;i++)
nvec[i]=vec[i]; delete vec; vec=nvec; max=2*max; }
pp=&vec[free++]; pp->name=new char[strlen(p)+1]; strcpy(pp->name,p); pp->val=0; return pp->val; }
main(){ const int MAX=256; char buf[MAX]; assoc vecc(512); while (cin>>buf) vecc[buf]++; assoc_iterator next(vecc); pair *p; while ((p=next())!=0)
{cout<<p->name<<“.”<<p->val<<endl;delete p->name;}
}
( 4 )重载 << 和 >> 运算符
重载输出运算符 << :cout<<a;
<< 是一个二元运算符,左边是 ostream 类的对象,右边是重载了 << 的类的对象
重载格式:ostream& operator<<(ostream&s,X obj){ // 相对于该类的输出操作
return s;}
#include “iostream.h” class three_d{
int x,y,z; public:
three_d(int a,int b,int c){ x=a;y=b;z=c;}friend ostream& operator<<(ostream&s,three_d obj)
{ s<<obj.x<<“,”; // 使用 << 的初始版本s<<obj.y<<“,”; // 使用 << 的初始版本s<<obj.z<<“\n”; // 使用 << 的初始版本return s; }
}; main(){ three_d a(1,2,3),b(3,4,5),c(6,7,8);
cout<<a<<b<<c; }
注意:
i. 该重载函数的返回类型是 ostream 类的对象的引用。返回类型使用引用的目的在于,当几个运算符 <<连在一起时,该重载函数能工作。
ii. 运算符函数必须被重载为友元。因为引起该重载函数的调用的运算对象是第二个参数而不是第一个参数。
重载输入运算符 >> :cin>>a;
>> 是一个二元运算符,左边是 istream 类的对象,右边是重载了 >> 的类的对象的引用重载格式:
istream& operator>>(istream&s,X& obj){ // 相对于该类的输入操作
return s;}
注意:第二个参数必须是一个引用
#include “iostream.h” class three_d{
int x,y,z; public:
three_d(int a,int b,int c) { x=a;y=b;z=c;}friend ostream& operator<<(ostream&s,three_d obj);
friend istream& operator>>(istream&s,three_d &obj); }; ostream& operator<<(ostream&s,three_d obj) { s<<obj.x<<“,”;
s<<obj.y<<“,”;s<<obj.z<<“\n”;return s;
}
istream& operator>>(istream&s,three_d &obj) { cout<<“Enter x,y,z Value:”;
s>>obj.x;s>>obj.y;s>>obj.z;return s;
} main(){ three_d a(1,2,3); cout<<a; cin>>a; cout<<a;}
四、虚函数
C++ 通过虚函数实现了多态性。一个虚函数就是一个在某基类中声明为 virtual并在一个或多个派生类中被重新定义的成员函数。声明虚函数的格式如下: virtual 类型 函数名(参数表);
当一个类的成员函数说明为虚函数后,就可以在该类的(直接或间接)派生类中定义与其基类虚函数原型相同的函数。这时,当用基类指针指向这些派生类对象时,系统会自动用派生类中的同名函数来代替基类中的虚函数。也就是说,当用基类指针指向不同派生类对象时,系统会在程序运行中根据所指向对象的不同,自动选择适当的函数,从而实现了运行时的多态性。
#include “iostream.h”class Base{ public: virtual void show() { cout<<”base class\n”; } };class Der1: public Base{ public: void show() { cout<<”derived class 1 \n”; } };class Der2: public Base{ public: void show() { cout<<”derived class 2 \n”; } };
void main(){ Base bobj, *p; p=&bobj; p->show(); Der1 dobj1; p->show(); p=&dobj2; Der2 dobj2; p=&dobj1; p->show();}
注意:
( 1 )虚函数可以在一个或多个派生类中被重新定义,但要求在派生类中重新定义时,必须与基类中的函数原型完全相同(包括函数名、返回类型、参数个数和参数类型的顺序)。这时无论在派生类的相应成员函数前是否加上关键字 virtual ,都将视其为虚函数,如果函数原型不同,只是函数名相同,C++将视其为一般的函数重载,而不是虚函数。
( 2 )只有类的成员函数才能说明为虚函数。因为虚函数仅适用于有继承关系的类对象,所以普通函数不能说明为虚函数。虚函数也不得是一个静态成员。
( 3 )内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时,仍将其看作非内联的。
( 4 )构造函数不能是虚函数,因为构造时,对象还是一片未定型的空间。只有在构造函数完成后,对象才能成为一个类的名副其实的实例。
( 5 )析构函数可以是虚函数,而且通常声明为虚函数。
( 6 )当一个函数在基类被声明为虚函数后,不管经历多少层派生,都将保持其虚拟性。因此,在派生类中重新定义该函数时,不再需要关键字 virtual。
五、纯虚函数与抽象类
纯虚函数是在基类中只声明虚函数而不给出具体的函数定义体,将它的具体定义放在各派生类中,称此虚函数为纯虚函数。通过该基类的指针或引用就可以调用所有派生类的虚函数,基类只是用于继承,仅作为一个接口,具体功能在派生类中实现。
声明纯虚函数的格式如下: virtual 类型 函数名(参数表) =0; (注:要放在基类的定义体中)
1.纯虚函数
class Shape{ protected: double x,y; public: void set(double i, double j) { x=i; y=j; } virtual void area()=0; // 声明纯虚函数 virtual void display()=0; };
class Triangle: public Shape { public: void display(){cout<< x<<“,/t"<<y<<endl; } void area() { cout<< "三角形面积: " <<0.5*x*y<<endl; } };
class Rectangle: public Shape { public: void area() { cout<<"矩形面积: " <<x*y<<endl; } virtual void display()=0; };
说明:( 1 )当声明一个基类时无法为虚拟函数定义其具体实现,可将它说明为纯虚拟函数,其具体实现留给派生类来定义。
( 2 )如果派生类中没有重新定义基类中的纯虚函数,则在派生类中必须再将该虚函数声明为纯虚函数。
2. 抽象类在类的层次结构中有一种特殊的类,它仅仅是为了被用作派生类的基类而定义出来的一种类,它不能创建具体的对象,即不能产生实例,这种类叫做抽象类。
在 C++ 中,通过在类中声明纯虚函数的方法来定义抽象类。
class Shape{ protected: double x,y; public: void set(double i, double j) { x=i; y=j; } virtual void area()=0; virtual void display()=0; };
class Totalangle: public Shape { public: void display() { cout<< "底边: " <<x; cout<<"高: "<<y<<endl; } virtual void area()=0; };
class Rectangle: public Totalangle { public: void area() { cout<<"矩形面积: " <<x*y<<endl; } };
class Triangle: public Totalangle { public: void area() { cout<< "三角形面积: " <<0.5*x*y<<endl; } };
void main() { Shape *p; Shape s; //error s.set(3,4); //error }
注意:
( 1 )抽象类包含有一个到多个纯虚函数,也可以定义其他非纯虚函数。
( 2 )不能声明抽象类的对象,但可以声明指向抽象类的指针变量和引用变量。
( 3 )从抽象类可以派生出具体或抽象类,但不能从具体类派生出抽象类。