return0;
}
aarea:
2*barea:
12*carea:
2d[0]area:
30d[1]area:
56
以下是怎样读前面例子中出现的一些指针和类操作符(*,&,.,->,[]):
*x读作:
pointedbyx(由x指向的)
&x读作:
addressofx(x的地址)
x.y读作:
memberyofobjectx(对象x的成员y)
(*x).y读作:
memberyofobjectpointedbyx(由x指向的对象的成员y)
x->y读作:
memberyofobjectpointedbyx(同上一个等价)
x[0]读作:
firstobjectpointedbyx(由x指向的第一个对象)
x[1]读作:
secondobjectpointedbyx(由x指向的第二个对象)
x[n]读作:
(n+1)thobjectpointedbyx(由x指向的第n+1个对象)
在继续向下阅读之前,一定要确定你明白所有这些的逻辑含义。
如果你还有疑问,再读一遍这一笑节,或者同时参考小节"3.3,指针(Pointers)"和"3.5,数据结构(Structures)".
4.2操作符重载(Overloadingoperators)
C++实现了在类(class)之间使用语言标准操作符,而不只是在基本数据类型之间使用。
例如:
inta,b,c;a=b+c;
是有效操作,因为加号两边的变量都是基本数据类型。
然而,我们是否可以进行下面的操作就不是那么显而易见了(它实际上是正确的):
struct{charproduct[50];floatprice;}a,b,c;a=b+c;
将一个类class(或结构struct)的对象赋给另一个同种类型的对象是允许的(通过使用默认的复制构造函数copyconstructor)。
但相加操作就有可能产生错误,理论上讲它在非基本数据类型之间是无效的。
但归功于C++的操作符重载(overload)能力,我们可以完成这个操作。
像以上例子中这样的组合类型的对象在C++中可以接受如果没有操作符重载则不能被接受的操作,我们甚至可以修改这些操作符的效果。
以下是所有可以被重载的操作符的列表:
+-*/=<>+=-=*=/=<<>><<=>>===!
=<=>=++--%&^!
|~&=^=|=&&||%=[]()newdelete
要想重载一个操作符,我们只需要编写一个成员函数,名为operator,后面跟我们要重载的操作符,遵循以下原型定义:
typeoperatorsign(parameters);
这里是一个操作符+的例子。
我们要计算二维向量(bidimensionalvector)a(3,1)与b(1,2)的和。
两个二维向量相加的操作很简单,就是将两个x轴的值相加获得结果的x轴值,将两个y轴值相加获得结果的y值。
在这个例子里,结果是(3+1,1+2)=(4,3)。
//vectors:
overloadingoperatorsexample
#include
classCVector{
public:
intx,y;
CVector(){};
CVector(int,int);
CVectoroperator+(CVector);
};
CVector:
:
CVector(inta,intb){
x=a;
y=b;
}
CVectorCVector:
:
operator+(CVectorparam){
CVectortemp;
temp.x=x+param.x;
temp.y=y+param.y;
return(temp);
}
intmain(){
CVectora(3,1);
CVectorb(1,2);
CVectorc;
c=a+b;
cout<return0;
}
4,3
如果你迷惑为什么看到这么多遍的CVector,那是因为其中有些是指class名称CVector,而另一些是以它命名的函数名称,不要把它们搞混了:
CVector(int,int);//函数名称CVector(constructor)
CVectoroperator+(CVector);//函数operator+返回CVector类型的值
ClassCVector的函数operator+是对数学操作符+进行重载的函数。
这个函数可以用以下两种方法进行调用:
c=a+b;c=a.operator+(b);
注意:
我们在这个例子中包括了一个空构造函数(无参数),而且我们将它定义为无任何操作:
CVector(){};
这是很必要的,因为例子中已经有另一个构造函数,
CVector(int,int);
因此,如果我们不像上面这样明确定义一个的话,CVector的两个默认构造函数都不存在。
这样的话,main()中包含的语句
CVectorc;
将为不合法的。
尽管如此,我已经警告过一个空语句块(no-opblock)并不是一种值得推荐的构造函数的实现方式,因为它不能实现一个构造函数至少应该完成的基本功能,也就是初始化class中的所有变量。
在我们的例子中,这个构造函数没有完成对变量x和y的定义。
因此一个更值得推荐的构造函数定义应该像下面这样:
CVector(){x=0;y=0;};
就像一个class默认包含一个空构造函数和一个复制构造函数一样,它同时包含一个对赋值操作符assignationoperator(=)的默认定义,该操作符用于两个同类对象之间。
这个操作符将其参数对象(符号右边的对象)的所有非静态(non-static)数据成员复制给其左边的对象。
当然,你也可以将它重新定义为你想要的任何功能,例如,只拷贝某些特定class成员。
重载一个操作符并不要求保持其常规的数学含义,虽然这是推荐的。
例如,虽然我们可以将操作符+定义为取两个对象的差值,或用==操作符将一个对象赋为0,但这样做是没有什么逻辑意义的。
虽然函数operator+的原型定义看起来很明显,因为它取操作符右边的对象为其左边对象的函数operator+的参数,其它的操作符就不一定这么明显了。
以下列表总结了不同的操作符函数是怎样定义声明的(用操作符替换每个@):
ExpressionOperator(@)FunctionmemberGlobalfunction
@a+-*&!
~++--A:
:
operator@()operator@(A)
a@++--A:
:
operator@(int)operator@(A,int)
a@b+-*/%^&|<>==!
=<=>=<<>>&&||,A:
:
operator@(B)operator@(A,B)
a@b=+=-=*=/=%=^=&=|=<<=>>=[]A:
:
operator@(B)-
a(b,c...)()A:
:
operator()(B,C...)-
a->b->A:
:
operator->()-
*这里a是classA的一个对象,b是B的一个对象,c是classC的一个对象。
从上表可以看出有两种方法重载一些class操作符:
作为成员函数(memberfunction)或作为全域函数(globalfunction)。
它们的用法没有区别,但是我要提醒你,如果不是class的成员函数,则不能访问该class的private或protected成员,除非这个全域函数是该class的friend(friend的含义将在后面的章节解释)。
关键字this
关键字this通常被用在一个class内部,指正在被执行的该class的对象(object)在内存中的地址。
它是一个指针,其值永远是自身object的地址。
它可以被用来检查传入一个对象的成员函数的参数是否是该对象本身。
例如:
//this
#include
classCDummy{
public:
intisitme(CDummy¶m);
};
intCDummy:
:
isitme(CDummy¶m){
if(¶m==this)return1;
elsereturn0;
}
intmain(){
CDummya;
CDummy*b=&a;
if(b->isitme(a))
cout<<"yes,&aisb";
return0;
}
yes,&aisb
它还经常被用在成员函数operator=中,用来返回对象的指针(避免使用临时对象)。
以下用前面看到的向量(vector)的例子来看一下函数operator=是怎样实现的:
CVector&CVector:
:
operator=(constCVector¶m){x=param.x;y=param.y;return*this;}
实际上,如果我们没有定义成员函数operator=,编译器自动为该class生成的默认代码有可能就是这个样子的。
静态成员(Staticmembers)
一个class可以包含静态成员(staticmembers),可以是数据,也可以是函数。
一个class的静态数据成员也被称作类变量"classvariables",因为它们的内容不依赖于某个对象,对同一个class的所有object具有相同的值。
例如,它可以被用作计算一个class声明的objects的个数,见以下代码程序:
//staticmembersinclasses
#include
classCDummy{
public:
staticintn;
CDummy(){n++;};
~CDummy(){n--;};
};
intCDummy:
:
n=0;
intmain(){
CDummya;
CDummyb[5];
CDummy*c=newCDummy;
cout<deletec;
cout<:
n<return0;
}
76
实际上,静态成员与全域变量(globalvariable)具有相同的属性,但它享有类(class)的范围。
因此,根据ANSI-C++标准,为了避免它们被多次重复声明,在class的声明中只能够包括staticmember的原型(声明),而不能够包括其定义(初始化操作)。
为了初始化一个静态数据成员,我们必须在class之外(在全域范围内),包括一个正式的定义,就像上面例子中做法一样。
因为它对同一个class的所有object是同一个值,所以它可以被作为该class的任何object的成员所引用,或者直接被作为class的成员引用(当然这只适用于static成员):
cout<:
n;
以上两个调用都指同一个变量:
classCDummy里的static变量n。
在提醒一次,它其实是一个全域变量。
唯一的不同是它的名字跟在class的后面。
就像我们会在class中包含static数据一样,我们也可以使它包含static函数。
它们表示相同的含义:
static函数是全域函数(globalfunctions),但是像一个指定class的对象成员一样被调用。
它们只能够引用static数据,永远不能引用class的非静态(nonstatic)成员。
它们也不能够使用关键字this,因为this实际引用了一个对象指针,但这些static函数却不是任何object的成员,而是class的直接成员。
4.3类之间的关系(Relationshipsbetweenclasses)
友元函数(Friendfunctions)
在前面的章节中我们已经看到了对class的不同成员存在3个层次的内部保护:
public,protected和private。
在成员为protected和private的情况下,它们不能够被从所在的class以外的部分引用。
然而,这个规则可以通过在一个class中使用关键字friend来绕过,这样我们可以允许一个外部函数获得访问class的protected和private成员的能力。
为了实现允许一个外部函数访问class的private和protected成员,我们必须在class内部用关键字friend来声明该外部函数的原型,以指定允许该函数共享class的成员。
在下面的例子中我们声明了一个friend函数duplicate:
//friendfunctions
#include
classCRectangle{
intwidth,height;
public:
voidset_values(int,int);
intarea(void){return(width*height);}
friendCRectangleduplicate(CRectangle);
};
voidCRectangle:
:
set_values(inta,intb){
width=a;
height=b;
}
CRectangleduplicate(CRectanglerectparam){
CRectanglerectres;
rectres.width=rectparam.width*2;
rectres.height=rectparam.height*2;
return(rectres);
}
intmain(){
CRectanglerect,rectb;
rect.set_values(2,3);
rectb=duplicate(rect);
cout<}
24
函数duplicate是CRectangle的friend,因此在该函数之内,我们可以访问CRectangle类型的各个object的成员width和height。
注意,在duplicate()的声明中,及其在后面main()里被调用的时候,我们并没有把duplicate当作classCRectangle的成员,它不是。
friend函数可以被用来实现两个不同class之间的操作。
广义来说,使用friend函数是面向对象编程之外的方法,因此,如果可能,应尽量使用class的成员函数来完成这些操作。
比如在以上的例子中,将函数duplicate()集成在classCRectangle可以使程序更短。
友元类(Friendclasses)
就像我们可以定义一个friend函数,我们也可以定义一个class是另一个的friend,以便允许第二个class访问第一个class的protected和private成员。
//friendclass
#include
classCSquare;
classCRectangle{
intwidth,height;
public:
intarea(void){return(width*height);}
voidconvert(CSquarea);
};
ClassCSquare{
private:
intside;
public:
voidset_side(inta){side=a;}
friendclassCRectangle;
};
voidCRectangle:
:
convert(CSquarea){
width=a.side;
height=a.side;
}
intmain(){
CSquaresqr;
CRectanglerect;
sqr.set_side(4);
rect.convert(sqr);
cout<return0;
}
16
在这个例子中,我们声明了CRectangle是CSquare的friend,因此CRectangle可以访问CSquare的protected和private成员,更具体地说,可以访问CSquare:
:
side,它定义了正方形的边长。
在上面程序的第一个语句里你可能也看到了一些新的东西,就是classCSquare空原型。
这是必需的,因为在CRectangle的声明中我们引用了CSquare(作为convert()的参数)。
CSquare的定义在CRectangle的后面,因此如果我们没有在这个class之前包含一个CSquare的声明,它在CRectangle中就是不可见的。
这里要考虑到,如果没有特别指明,友元关系(friendships)并不是相互的。
在我们的CSquare例子中,CRectangle是一个friend类,但因为CRectangle并没有对CSquare作相应的声明,因此CRectangle可以访问CSquare的protected和private成员,但反过来并不行,除非我们将CSquare也定义为CRectangle的friend。
类之间的继承(Inheritancebetweenclasses)
类的一个重要特征是继承,这使得我们可以基于一个类生成另一个类的对象,以便使后者拥有前者的某些成员,再加上它自己的一些成员。
例如,假设我们要声明一系列类型的多边形,比如长方形CRectangle或三角形CTriangle。
它们有一些共同的特征,比如都可以只用两条边来描述:
高(height)和底(base)。
这个特点可以用一个类CPolygon来表示,基于这个类我们可以引申出上面提到的两个类CRectangle和CTriangle。
类CPolygon包含所有多边形共有的成员。
在我们的例子里就是:
width和height。
而CRectangle和CTriangle将为它的子类(derivedclasses)。
由其它类引申而来的子类继承基类的所有可视成员,意思是说,如果一个基类包含成员A,而我们将它引申为另一个包含成员B的类,则这个子类将同时包含A和B。
要定义一个类的子类,我们必须在子类的声明中使用冒号(colon)操作符:
,如下所示:
classderived_class_name:
publicbase_class_name;
这里derived_class_name为子类(derivedclass)名称,base_class_name为基类(baseclass)名称。
public也可以根据需要换为protected或private,描述了被继承的成员的访问权限,我们在以下例子后会很快看到:
//derivedclasses
#include
ClassCPolygon{
protected:
intwidth,height;
public:
voidset_values(inta,intb){width=a;height=b;}
};
classCRectangle:
publicCPolygon{
public:
intarea(void){return(width*height);}
};
classCTriangle:
publicCPolygon{
public:
intarea(void){return(width*height/2);}
};
intmain(){
CRectanglerect;
CTriangletrgl;
rect