本章导读
多态是面向对象程序设计的重要特征之一,多态(polymorphism)一词从词面理解,其含义是具有多种形式或形态,如日常生活中最常见的物质水就具有多态性,在一定条件下呈现固态、液态和气态。在面向对象程序设计中,多态是指发出同样的消息在被不同类型的对象接收时,将导致完全不同的行为。
多态可以分为两类:编译时的多态和运行时的多态。编译时的多态是指编译器在编译时确定同名操作的具体操作对象,而运行时的多态是指程序运行时确定具体操作对象。把确定具体操作对象的过程称绑定(或称联编)。绑定也可以分为两种:编译时的多态其绑定过程称静态绑定,运行时的多态其绑定过程称动态绑定。
静态绑定是编译器在编译时,就可以确定同名操作的具体操作对象,所以也称早期绑定。这种多态主要通过函数重载和运算符重载实现。
动态绑定是编译器在编译时无法确定具体操作对象,而需要在程序运行过程中,才可以确定具体操作对象,所以也称晚期绑定。这种多态主要通过类的继承关系和虚函数实现。
在面向对象程序设计中,封装用以隐藏细节,实现代码模块化;继承用以扩展已存在的功能,实现代码重用;多态则是为了实现接口重用。
10.2 虚函数
虚函数存在于继承与派生过程中,是允许被其派生类重新定义的成员函数,离开了继承与派生,就没有虚函数。虚函数必须是类的成员函数。
10.2.1 虚函数的定义及实现过程
在继承与派生过程中,定义一个虚函数应在基类中说明,其一般格式为:
virtual <类型说明符> <成员函数名>(参数表)
{<函数体>}
其中:关键字virtual说明该成员函数是虚函数。
虚函数是通过类的继承与派生关系来实现的。当基类中把一个成员函数说明为虚函数,则由该基类所派生的所有派生类中,该函数一直保持虚函数的特性。在派生类中如果重新定义一个与虚函数同名的成员函数,且参数的个数、类型以及返回值类型全部相同,则不管有无关键字virtual说明,该成员函数都将成为一个虚函数。
【例10.1】在继承与派生关系中定义虚函数。
class A{
public:
virtual void fun1()
{
cout<<"Class A\n";
}
};
class B:public A{
public:
virtual void fun1()
{
cout<<"Class B\n";
}
};
class C:public A{
public:
void fun1(){
cout<<"Class C\n";
}
};
【例10.2】在继承与派生关系中虚函数的判断。
class A{
public:
virtual void fun1()
{
cout<<"Class A\n";
}
};
class B:public A{
public:
void fun1(int i)
{
cout<<"Class B\n";
}
};
class C:public A{
public:
void fun1(){
cout<<"Class C\n";
}
};
注意:覆盖(Override)和重载(Overload)的概念。在派生类中重新定义基类中的虚函数,不仅函数名相同,并且要求参数的个数、类型以及返回值类型全部相同,这称为“覆盖”(或称为“重写”)。重载是指允许存在多个同名函数,但这些函数的参数表不同(可以是参数个数不同,或参数类型不同,或参数个数、类型都不同)。
10.2.2 虚函数实现过程
在继承和派生过程中,定义了虚函数,以实现动态多态。
实现动态多态,需要通过基类的指针对象(或引用),使该指针指向(或引用)不同派生类的对象,并通过指针对象(或引用)调用虚函数,这样才能体现虚函数的特性。
【例10.3】在继承与派生关系中通过虚函数实现多态。
#include<iostream>
using namespace std;
class A{
public:
virtual void fun1()
{
cout<<"Class A\n";
}
};
class B:public A{
public:
virtual void fun1()
{
cout<<"Class B\n";
}
};
class C:public A{
public:
void fun1(){
cout<<"Class C\n";
}
};
int main(){
B b;
C c;
A *pa=&b;
pa->fun1();
pa=&c;
pa->fun1();
}
【例10.4】在继承与派生关系中,是否为虚函数对结果的影响。
#include<iostream>
using namespace std;
class A{
public:
virtual void fun1()
{
cout<<"Class A\n";
}
};
class B:public A{
public:
void fun1(int i)
{
cout<<"Class B\n";
}
};
class C:public A{
public:
void fun1(){
cout<<"Class C\n";
}
};
int main(){
B b;
C c;
A *pa=&b;
pa->fun1();
pa=&c;
pa->fun1();
}
当构造函数调用类中的虚函数时,只调用该类中定义的虚函数,而如果是一般成员函数调用类中的虚函数时,也遵循虚函数的特性。
【例10.5】分别在构造函数和成员函数中调用虚函数。
#include<iostream>
using namespace std;
class A{
int i;
public:
A(int a){
i=a;fun1();
}
virtual void fun1()
{
cout<<i<<" Class A\n";
}
void fun2(){
fun1();
}
};
class B:public A{
int j;
public:
B(int a,int b):A(a){
j=b;
fun1();
}
void fun1()
{
cout<<j <<" Class B\n";
}
};
int main(){
B b(10,20);
b.fun2();
}
特别提示:
①虚函数是在程序运行过程中,确定调用哪一个函数,与一般成员函数比较,会降低程序运行效率,但提高了程序的通用性。
②C++中不可以定义构造函数为虚函数,但是可以定义析构函数为虚函数。定义析构函数为虚函数,是为了实现撤消对象的多态性。
③虚函数只能是类的成员函数,但静态成员函数不能声明为虚函数。
10.2.3 纯虚函数和抽象类
在某些情况下,基类中有些虚函数无法定义具体的实现部分,要求各派生类根据各自需要分别定义,此时可以在基类中定义纯虚函数。定义纯虚函数的一般格式为:
virtual <类型说明符> <成员函数名>(参数表)=0;
定义纯虚函数和虚函数的不同之处是在参数表后直接加了“=0”。定义为纯虚函数,
基类中该函数就不再给出具体实现部分(即没有函数体),其函数体由派生类定义。
【例10.6】说明图形类和其派生圆、矩形和三角形的简单实现。
#include<iostream>
#include<math.h>
using namespace std;
#define PI 3.1415926
class Shape{
public:
virtual void Print()=0;
virtual float Area()=0;
};
class Circle:public Shape{
float R;
public:
Circle(float r=0){ R=r;}
void Print(){ cout<<"圆半径:"<<R<<'\t';}
float Area(){ return PI*R*R;}
};
class Rectangle:public Shape
{
float L,W;
public:
Rectangle(float l,float w)
{ L=l;W=w;}
void Print()
{ cout<<"长:"<<L<<"\t宽:"<<W<<'\t';}
float Area(){return L*W;}
};
class Triangle:public Shape
{
float A,B,C;
public:
Triangle(float a,float b,float c)
{ A=a; B=b; C=c;}
void Print()
{ cout<<“三角形边:”;
cout<<A<<','<<B<<','<<C<<'\t';
}
float Area()
{ float s=(A+B+C)/2;
return sqrt(s*(s-A)*(s-B)*(s-C));
}
};
int main()
{ Shape *S;
S=new Circle(10);
S->Print();
cout<<"圆面积为:"<<S->Area()<<endl;
S=new Rectangle(4,5);
S->Print();
cout<<“矩形面积为:”;
cout<<S->Area()<<endl;
S=new Triangle(3,4,5);
S->Print();
cout<<“三角形面积为:”;
cout<<S->Area()<<endl;
}
特别提示:便于理解,可以这样来理解抽象类型。图形类是一个抽象类,因为不能确定图形的形状,是一个抽象的概念。而其派生的圆、矩形、三角形等,可以确定其形状,是具体存在的,所以非抽象类。同样动物类是抽象类,但猫、兔等是非抽象类。
10.3 运算符重载
在面向对象程序设计中,运算符重载可以实现两个对象或对象与其他数据类型中的复杂运算。运算符重载的原理是:对于C++给定的运算符,通过运算符重载函数,完成一个特定的操作。所以运算符重载的关键是通过特定的函数重新定义运算符的功能,使它能够完成作用于类的对象的相关运算。由此可见运算符重载实际上是函数的重载。
关于运算符重载,强调以下几个方面:
(1)必须是C++预定义的运算符,不能是自己臆造的运算符;
(2)重新定义运算符新的功能是通过函数实现的;
(3)运算符重载必须作用于类的对象,在实现运算符重载时,至少有一个操作数是类的对象。
(4)C++中允许重载的运算符如表10.1所示。
(5)下列运算符不可以重载:“·”、“*”、“::”、“?:”、“sizeof()”。
提示:
①运算符重载不能改变运算符的优先级和结合性。也不可以改变运算符的语法结构。
②实现运算符重载可以有两种实现形式:类的成员函数和友元函数。用类的友元函数实现时,称友元运算符。
③除new和delete之外,任何运算符作为成员函数重载时,不得重载为static成员函数。
④C++编译器对运算符重载的选择,遵循函数重载的选择原则。实现运算符重载时,C++编译器将去寻找与参数相匹配的运算符重载函数。
10.3.1 成员函数实现运算符重载及方法
- 定义二元运算符重载函数的格式为:
<返回值类型> operator<运算符>(<参数>)
{……}
格式中,operator是关键字,与后面的<运算符>一起构成运算符重载函数的函数名。使用特殊的函数名是方便C++编译器区别其他成员函数。
若在程序中出现如下表达式:c1<运算符>c2
则C++编译器将该表达式解释为:
**c1.operator<运算符>(c2)**
【例10.7】说明一个复数类,通过运算符重载函数,直接完成两个复数间的运算。
#include<iostream>
using namespace std;
class Complex{
float Real,Image;
public:
Complex(float r=0.0,float i=0.0)
{ Real=r;Image=i;} //带缺省的构造函数
void Print()
{ cout<<Real;
if(Image>=0)
cout<<'+'<<Image<<"i\n";
else
cout<<Image<<"i\n";
}
Complex operator +(Complex &c)
{ Complex temp;
temp.Real=Real+c.Real;
temp.Image=Image+c.Image;
return temp;
}
Complex operator +(float x)
{ return Complex(Real+x,Image);}
Complex & operator +=(Complex &c)
{ Real+=c.Real;Image+=c.Image;
return *this;
}
Complex & operator =(Complex &c)
{ Real=c.Real; Image=c.Image;
return *this;
}
bool operator ==(Complex);
};
bool Complex::operator ==(Complex c)
{
return (Real==c.Real&&Image==c.Image);
}
void main(void)
{ Complex c1(10,20),c2(100,200);
Complex c3,c4;
c3=c1+c2; c1.Print(); c2.Print();
c3.Print();
c4=c1+5; c4.Print();
c4+=c2; c4.Print();
if(c3==c4) cout<<"c3,c4两对象相等!\n";
else cout<<"c3,c4两对象不相等!\n";
}
提示:由于使用引用类型作为运算符重载函数的形参,调用时不再重新为形参分配空间、也不再通过拷贝构造函数复制对象,所以可以提高程序运行效率。但也须注意,使用引用类型作为运算符重载函数的形参,如果形参被改变了,函数调用完成后,则对应的操作数也将被改变。
- 定义一元运算符重载函数的格式为:
<返回值类型> operator<运算符>(){……}
提示:二元运算符重载函数有一个参数,是运算符的右操作数,而一元运算符重载函数没有参数。所以通过成员函数实现运算符重载,其函数的形参的个数比该运算符的操作数个数少一个。
若在程序中出现如下表达式:<运算符>c1
则C++编译器将该表达式解释为:
c1.operator<运算符>()
提示:其中c1必须是该类的对象。
对于自增“++”和自减“- -”运算符,存在前置和后置情况,为使C++编译器能区分前置和后置情况,定义这两个运算符重载函数时,也必须有所区别。以自增“++”为例:
前置“++” 运算符重载函数的格式为:
<返回值类型> operator++(){……}
后置“++” 运算符重载函数的格式为:
<返回值类型> operator++(int){……}
提示:后置运算符重载函数中的int没有实际意义,只作为区分后置运算符的标识。
【例10.8】说明一个时间类,通过运算符重载函数,实现“++”(加1秒)前置和后置运算。
#include<iostream>
using namespace std;
class Time{
int HH,MM,SS;
public:
Time(int h=0,int m=0,int s=0)
{ HH=h;MM=m;SS=s;}
void Print()
{
cout<<HH<<':'<<MM<<':'<<SS<<endl;
}
Time operator++();
Time operator++(int);
};
Time Time::operator++()
{ SS++;
if(SS==60){MM++;SS=0;}
if(MM==60){HH++;MM=0;}
if(HH==24){HH=0;}
return *this;
}
Time Time::operator++(int)
{ Time t=*this;
++(*this);
return t;
}
int main()
{
Time t1(8,20,36),t2,t3;
t2=++t1;
t1.Print();
t2.Print();
t3=t1++;
t1.Print();
t3.Print();
}
10.3.3 友元函数实现运算符重载及方法
实现运算符重载时,很多运算符既可以通过成员函数实现,也可以通过友元函数实现。通过友元函数实现时,称友元运算符。
- 定义二元运算符重载函数的格式为:
friend <返回值类型> operator<运算符>(<参数1>,<参数2>)
{……}
通过友元函数实现二元运算符重载时,参数个数比成员函数实现时多一个,其中<参数1>是运算符的左操作数,<参数2>是运算符的右操作数。
通过友元函数实现二元运算符重载时,若在程序中出现如下表达式:c1<运算符>c2
则C++编译器将该表达式解释为:
operator<运算符>(c1,c2)
提示:从C++编译器对该表达式的解释可知, c1和c2之中须有一个是类的对象,且该运算符重载函数必须是该类的友元函数。
【例10.9】把例10.7说明的复数类,通过友元函数实现运算符重载,直接完成两个复数间的运算。
#include
using namespace std;
class Complex{
float Real,Image;
public:
Complex(float r=0.0,float i=0.0)
{ Real=r;Image=i;}
void Print()
{ cout<<Real;
if(Image>0) cout<<‘+’<<Image<<“i\n”;
else if(Image<0) cout<<Image<<“i\n”;
}
friend Complex operator +(Complex,Complex);
friend Complex operator +(Complex,float);
friend Complex & operator += (Complex &,Complex);
friend bool operator (Complex,Complex);
};
Complex operator +(Complex c1,Complex c2)
{ Complex temp;
temp.Real=c1.Real+c2.Real;
temp.Image=c1.Image+c2.Image;
return temp;
}
Complex operator +(Complex c1,float x)
{ return Complex(c1.Real+x,c1.Image);}
Complex&operator+=(Complex&c1,Complex c2)
{ c1.Real+=c2.Real;
c1.Image+=c2.Image;
return c1;
}
bool operator (Complex c1,Complex c2)
{return (c1.Realc2.Real&&c1.Imagec2.Image);}
int main()
{
Complex c1(10,20),c2(100,200),c3,c4;
c3=c1+c2;
c1.Print();
c2.Print();
c3.Print();
c4=c1+5;
c4.Print();
c4+=c2;
c4.Print();
if(c3==c4) cout<<“c3,c4两对象相等!\n”;
else cout<<“c3,c4两对象不相等!\n”;
}
2. 定义一元运算符重载函数的格式为:
<返回值类型> operator<运算符>(<参数>)
{……}
当通过友元函数实现一元运算符重载时,需要有一个参数,该参数用来表示唯一的操作数。
通过友元函数实现一元运算符重载时,若在程序中出现如下表达式:<运算符>c1
则C++编译器将该表达式解释为:
operator<运算符>(c1)
此时c1必须是类的对象。
与成员函数实现一元运算符重载时类同,友元函数在定义自增“++”和自减“- -”运算符重载函数时,也必须在后置运算符重载函数中增加一个int标识,以区分前置和后置。以自增“++”为例:
前置“++” 运算符重载函数的格式为:
friend <返回值类型> operator++(<参数>)
{……}
后置“++” 运算符重载函数的格式为:
friend <返回值类型> operator++(<参数>,int){……}
与成员函数一样,后置运算符重载函数中的int没有实际意义,只作为区分后置运算符的标识。
【例10.10】把例10.8说明的时间类,通过友元函数实现运算符重载,实现“++”(加1秒)前置和后置运算。
#include
using namespace std;
class Time{
int HH,MM,SS;
public:
Time(int h=0,int m=0,int s=0)
{ HH=h;MM=m;SS=s;}
void Print()
{ cout<<HH<<‘:’<<MM<<‘:’<<SS<<endl;}
friend Time operator++(Time &);
friend Time operator++(Time &,int);
};
Time operator++(Time &t)
{
t.SS++;
if(t.SS60){t.MM++;t.SS=0;}
if(t.MM60){t.HH++;t.MM=0;}
if(t.HH==24){t.HH=0;}
return t;
}
//提示:友元函数没有this指针,访问对象成员需
//要通过对象名和成员运算符实现。
Time operator++(Time &t,int)
{
Time temp=t;
++t;
return temp;
}
int main()
{
Time t1(8,20,36),t2,t3;
t2=++t1;
t1.Print();
t2.Print();
t3=t1++;
t1.Print();
t3.Print();
}
3. 用成员函数实现和友元函数实现运算符重载比较
①成员函数和友元函数实现运算符重载时,其函数名都必须以关键字operator开始,后面跟合适的运算符,但参数个数友元函数实现比成员函数实现时多一个。
②成员函数实现时,第一操作数必须是类的对象,但友元函数实现时,只要其中一个操作数是类的对象,所以通过友元函数实现时,两个操作数可以交换,满足交换率。
③有些运算符只允许成员函数实现(如赋值运算符),有些只允许友元函数实现(如提取、插入运算符)等。
④成员函数隐含this指针,而友元函数没有this指针。
★使用运算符重载特别说明:
尽管实现运算符重载时,把加法(+)运算重载为相减,把乘法(*)运算重载为相加是非常有趣的。但事实上程序设计更重要的是要求程序易于理解和保证程序运行安全,因此实现运算符重载应当以使程序结构和功能清晰,便于理解和使用为目的。
10.3.4 类型转换函数
类型转换函数是类的成员函数,其功能是将类对象中的数据成员,转换成另一种已定义的数据类型,可以是基本数据类型,也可以是其它已定义的构造数据类型。类型转换函数的格式为:
operator<数据类型>(){……}
注意:类型转换函数不允许带参数,也不可以指定返回值类型这一项,其函数的返回值类型就是函数名中的<数据类型>。该函数的作用是将所在类对象相关的数据成员转换为指定的<数据类型>。
【例10.11】说明一个长方体类,分别通过类型转换函数和类的成员函数计算其体积。
#include<iostream.h>
class Rect{
float L,W,H;
public:
Rect(float l,float w,float h)
{ L=l;W=w;H=h;}
void Print(){cout<<“长:”<<L;
cout<<“\t宽:”<<W<<“\t高:”<<H<<endl;
}
operator float()
{ return LWH;}
float Volume()
{ return LWH;}
};
int main()
{
Rect r1(10,5,20),r2(5,4,6);
float v1,v2,v3,v4;
r1.Print();
r2.Print();
v1=r1;
v2=r2.Volume();
cout<<“r1的体积为:“<<v1<<endl;
cout<<“r2的体积为:”<<v2<<endl;
v3=float(r1);
v4=(float)r2;
cout<<“r1的体积为:“<<v3<<endl;
cout<<“r2的体积为:”<<v4<<endl;
}
10.3.5 提取和插入运算符重载
在C++中允许用户重载运算符“<<”和“>>”,以实现对象的直接输入/输出。但必须通过友元函数实现“<<”和“>>”运算符重载,不可以通过成员函数实现。其原因是这两个运算符的左操作数不可能是该类的对象,而是流类的对象cin、cout等。
(1)重载提取运算符“>>”的一般格式为:
friend istream & operator >> (istream & ,类名 &);
重载提取运算符“>>”时,函数返回值必须是类istream的引用。非引用时,cin后面只能有一个提取运算符,如果要满足cin中可以连续使用“>>”运算符,则必须是引用。格式中,运算符重载函数的第一个参数也必须是类istream的引用,作为运算符的左操作数,第二个参数为自定义类的对象引用,作为运算符的右操作数。须注意,在重载提取运算符“>>”时,第二个参数必须是引用,否则在运算符重载函数中对对象的输入,无法返回到对应的实参对象。
(2)重载插入运算符“<<”的一般格式为:
friend ostream & operator <<(ostream & ,类名 &);
和提取运算符“>>”类同,重载插入运算符“<<”时,函数返回值必须是类ostream的引用,这也是为了在cout中可以连续使用“<<”运算符。函数的第一个参数是类ostream的引用,第二个参数为自定义类的对象或对象引用。
【例10.12】重载提取和插入运算符,实现时间类的输入和输出。
#include<iostream>
using namespace std;
class Time{
int Hour,Minute,Second;
public:
Time(int h=0.0,int m=0.0,int s=0)
{Hour=h;Minute=m;Second=s;}
friend ostream&
operator<<(ostream &os,Time &t);
friend istream&
operator>>(istream &is,Time &t);
};
ostream&operator<<(ostream &os,Time &t)
{ return os<<t.Hour<<':'<<t.Minute
<<':'<<t.Second<<'\n';
}
istream& operator>>(istream &is,Time &t)
{ cout<<"请输入时间(时、分、秒):" ;
is>>t.Hour>>t.Minute>>t.Second;
return is;
}
int main()
{ Time t1(10,20,30),t2;
cin>>t2; //A
cout<<t1<<t2; //B
}
10.3.5 一些特殊运算符的重载
1. 赋值运算符重载
如果用户没有定义赋值运算符重载函数,C++编译器会自动生成一个赋值运算符重载函数,其函数功能是将“=”左边对象的每个数据成员分别复制给右边对象的每个数据成员。大多数情况下相同类型的对象之间可以直接赋值,并且得到一个正确的结果。但当类的数据成员包含指针成员,其构造函数将为指针成员动态分配所需要的空间,此时相同类型对象间就不能直接赋值,直接赋值将导致两个对象的指针成员同时指向同一个动态分配的空间而出现运行错误。所以当类的数据成员包含指针成员时,为确保每个对象的指针成员都有自己独立的存储空间,需要自定义赋值运算符重载函数。
赋值运算符重载函数的常用格式为:
<类名> & operator = (<类名>&<对象名>)
{ ……
return *this;
}
格式中,函数返回值是对象的引用,其目的是使赋值运算符可实现连续赋值。
提示:赋值运算符重载函数必须是类的成员函数。
【例10.12】当类的数据成员包含指针成员时,对象间直接赋值所带来的问题。
#include<iostream>
#include<string.h>
using namespace std;
class Person{
protected:
char *Name,*Sex;
int Age;
public:
Person(char *n,char *s,int a)
{ Name=new char[strlen(n)+1];
strcpy(Name,n);
Sex=new char[strlen(s)+1];
strcpy(Sex,s);
Age=a;
}
~Person()
{ if(Name)delete []Name;
if(Sex)delete []Sex;
}
void Show()
{ cout<<“姓名:”<<Name<<“\t性别:”;
cout<<Sex<<"\t年龄:"<<Age<<'\n';
}
};
int main()
{ Person s1(“小明”,“男”,20);
Person s2("小丽","女",22);
s1.Show();
s2.Show();
s2=s1;
s2.Show();
}
类中如果没有自定义“=”运算符重载函数,C++编译器将自动产生一个“=”运算符重载函数,其格式为:
Person & operator=(Person &s)
{Name=s.Name;Sex=s.Sex;Age=s.Age;}
解决此问题的方法,是自定义如下形式的赋值运算符重载函数。
Person & Person::operator =(Person &s)
//赋值运算符重载函数
{ if(Name)delete []Name;
if(Sex)delete []Sex;
if(s.Name){
Name=new char[strlen(s.Name)+1];
strcpy(Name,s.Name);}
else Name=0;
if(s.Sex){
Sex=new char[strlen(s.Sex)+1];
strcpy(Sex,s.Sex);}
else Sex=0;
Age=s.Age;
return *this;
}
2. 逗号运算符重载
逗号运算符是一个二元运算符,既可以通过成员函数,也可以通过友元函数实现重载。
【例10.13】实现逗号运算符重载示例。
#include
using namespace std;
class Complex{
private:
float Real,Image;
public:
Complex(float r=0,float i=0)
{ Real=r;Image=i;}
Complex operator=(Complex c)
{ Real=c.Real;
Image=c.Image;
return *this;
}
Complex operator,(Complex c)
{ return c;} //逗号运算符重载函数
void Print()
{ cout<<Real;
if(Image>0)
cout<<‘+’<<Image<<“!\n”;
else
if(Image<0)cout<<Image<<“!\n”;
}
};
int main()
{ Complex c1(3.4,5.6),c2(10.5,-12.3),c3;
c1.Print();
c2.Print();
c3=(c1,c2);
c3.Print();
}
3. 下标运算符重载
逗号下标运算符重载只能针对对象数组,其一般格式为:
<返回值类型> operator [] (<参数>)
{ …… }
格式中,<参数>是用来表示数组的下标,所以应该是整型类型。
提示:下标运算符重载有且仅有一个参数,所以只能对一维数组下标进行处理,对于多维数组可以将其看作一维数组来处理。
提示:下标运算符重载函数也必须是类的成员函数,其数组也必须是对象数组。
【例10.14】使用下标运算符重载实现对数组下标的越界检查。
#include<iostream>
#include<stdlib.h>
using namespace std;
class Array{
int Len, *Arr;
public:
Array(int *a=0,int n=0)
{ if(n>0){
Len=n;Arr=new int[n];
for(int i=0;i<n;i++)Arr[i]=a[i];
}
else {Len=0; Arr=0; }
}
~Array(){if(Arr)delete []Arr;}
int &operator[](int i)//下标运算符重载函数
{ if(i>=Len||i<0){
cout<<"出错!!!下标["<<i<<"]越界!程序执行终止!\n";
abort();
}
return Arr[i];
}
};
int main()
{ int a[10]={0,1,2,3,4,5,6,7,8,9};
Array a1(a,10); int i;
for(i=0;i<10;i++)cout<<a1[i]<<" ";
cout<<endl;
for(i=0;i<10;i++)a1[i]=i*3;
for(i=0;i<10;i++)cout<<a1[i]<<" ";
cout<<endl;}
10.3.6 实现字符串类的运算符重载
在C++中,对字符串的处理可以通过标准库提供的字符串类和相关的字符串处理函数实现;在面向对象程序设计中,还可以通过运算符重载实现字符串的操作,如字符串的加法、减法、赋值等。
相同的运算符对于字符串的操作与数值运算有很大差异,可以通过重载运算符“+”实现字符串的拼接,重载运算符“-”实现字符串中删除子字符串的操作,重载运算符“=”,实现字符串的直接赋值等等。
【例10.15】下面定义一个字符串的类,通过构造函数实现对象的初始化,重载运算符”+”实现两个对象的字符串的拼接运算,重载运算符”-”实现一个对象的字符串中删除包含的另一个对象的字符串(子字符串)的运算,重载运算符”-” 实现一个对象的字符串中删除指定字符的运算,重载运算符“>”实现两个字符串的比较运算。
#include<iostream>
#include<string.h>
using namespace std;
class String{
protected:
char *Sp;
public:
String(){Sp=0;}//缺省构造函数
String(String &); //拷贝构造函数
String(char *s) //带参数的构造函数
{ Sp=new char[strlen(s)+1];
strcpy(Sp,s);
}
~String(){if(Sp)delete []Sp;}//析构函数
void Show(){cout<<Sp<<'\n';}//输出成员函数
String&operator=(String &);//赋值运算符重载函数
friend String operator+(String &,String &);
//友元运算符“+”实现两个对象相加
String operator-(String &);
//成员运算符“-”实现两个对象相减
String operator-(char);
//成员运算符“-”实现对象中减去所有指定字符
int operator >(String &);
//成员运算符“>”比较两个对象大于关系
};
String::String(String &s)
//深拷贝的构造函数,保证参数和返回值正确传递
{ if(s.Sp){Sp=new char [strlen(s.Sp)+1];
strcpy(Sp,s.Sp); }
else Sp=0;
}
String operator +(String &s1,String &s2)
{ String t;
t.Sp=new char[strlen(s1.Sp)+strlen(s2.Sp)+1];
strcpy(t.Sp,s1.Sp);
strcat(t.Sp,s2.Sp);//两个对象相加,实现字符串拼接
return t;
}
String String::operator -(String &s)
{ String t1=*this; char *p;
while(1) { if(p=strstr(t1.Sp,s.Sp))
{if(strlen(t1.Sp)==strlen(s.Sp))
{ delete[]t1.Sp;t1.Sp=0;
break;; }
String t2;
t2.Sp=new char[strlen(t1.Sp)-strlen(s.Sp)+1];
char *p1=t1.Sp,*p2=t2.Sp;
int i=strlen(s.Sp);
while(p1<p)*p2++=*p1++;
while(i){p1++;i--;}//跳过子串
while(*p2++=*p1++); t1=t2;//为删除后面子串做准备}
else break;
}
return t1;//返回删除子串后的对象
}
String String::operator -(char s)
{ String t1;int i=0,flag=0;
t1.Sp=new char[strlen(Sp)+1];
char *p1=Sp,*p2=t1.Sp;
while(*p1)
if(*p1!=s){*p2++=*p1++;i++;flag=1;}
else p1++;//跳过需删除的字符
*p2='\0';//为复制的新字符串增加结束符号\0
if(flag){//如果字符串中包含有需删除的字符
String t2;
if(t1.Sp){
t2.Sp=new char[strlen(t1.Sp)+1];
strcpy(t2.Sp,t1.Sp);}
else t2.Sp=0; return t2;
}else return *this;
}
int String::operator >(String &s)
{ if(strcmp(Sp,s.Sp)>0)return 1;
else return 0;}
String & String::operator =(String &s)
{ if(Sp)delete []Sp; //删除已分配的空间
if(s.Sp){ //根据右操作数重新分配空间
Sp=new char[strlen(s.Sp)+1];
strcpy(Sp,s.Sp); }
else Sp=0;
return *this;
}
int main()
{ String s1("SouthEast ");
String s2("University"),s3,s4,s5;
s1.Show();
s2.Show();
s3=s1+s2;
s3.Show();
if(s1>s2) cout<<"s1>s2 成立!\n";
else cout<<"s1>s2 不成立!\n";
s4=s3-s2;
s4.Show();
s5=s3-'t';
s5.Show();
}