- 函数重载
一、什么是函数重载?
函数重载overload是指不同的函数采用相同的函数名,彼此间通过形参列表加以区分。
举例:
函数名都为distance,但形参列表的个数不同;
#include <iostream>
#include <cmath>
using namesapce std;
double distance(float,float);
double distance(float,float,float,float);
int main()
{
float point1X,point1Y,point2X,point2Y;
point1X=5.0;
point1Y=5.0;
point2X=7.0;
point2Y=7.0;
cout<<"The distance to the origin is:\n";
cout<<"Point 1 ("<<point1X<<","<<point1Y<<"): "<<distance(point1X,point1Y)<<endl;
cout<<"Point 2 ("<<point2X<<","<<point2Y<<"): "<<distance(point2X,point2Y)<<endl;
cout<<"The distance between two points ("<<point1X<<","<<point1Y<<") and ("
<<point2X<<","<<point2Y<<") is "<<distance(point1X,point1Y,point2X,point2Y)<<endl;
return 0;
}
double distance(float pX,float pY)
{
return sqrt(pX*pX+pY*pY);
}
double distance(float p1X,float p1Y,float p2X,float p2Y)
{
return sqrt((p2X-p1X)*(p2X-p1X)+(p2Y-p1Y)*(p2Y-p1Y));
}
函数名都为equal,但形参列表的数据类型不同;
#include <iostream>
using namesapce std;
bool equal(int,int);
bool equal(float,float);
bool equal(char*,char*);
int main()
{
int i1,i2;
float f1,f2;
char *c1,*c2;
i1=3;i2=6;
f1=3.33333;f2=3.33332;
c1=new char[20];c2=new char[20];
strcpy(c1,"Hello");
strcpy(c2,"hello");
switch(equal(i1,i2))
{
case true:
cout<<i1<<"=="<<i2<<endl;break;
case false:
cout<<i1<<"!="<<i2<<endl;break;
}
switch(equal(f1,f2))
{
case true:
cout<<f1<<"=="<<f2<<endl;break;
case false:
cout<<f1<<"!="<<f2<<endl;break;
}
switch(equal(c1,c2))
{
case true:
cout<<c1<<"=="<<c2<<endl;break;
case false:
cout<<c1<<"!="<<c2<<endl;break;
}
delete[] c1;delete[] c2;
return 0;
}
bool equal(int a,int b)
{
return a==b?true:false;
}
bool equal(float a,float b)
{
if(fabs(a-b)<1e-6)
return true;
else
return false;
}
bool equal(char* a,char* b)
{
if(strcmp(a,b)==0)
return true;
else
return false;
}
类的构造函数是函数重载使用最普遍的地方;
<pre name="code" class="cpp">#include <iostream>
#include <string>
using namesapce std;
class ScoreRec{
public:
ScoreRec()
{
name="";
ID="";
score=' ';
}
ScoreRec(string newName,string newID,char newScore)
{
name=newName;
ID=newID;
Score=newScore;
}
void getRecord(string& nameGet,string& IDGet,char& scoreGet)
{
nameGet=name;
IDGet=ID;
scoreGet=score;
}
private:
string name;
string ID;
char score;
};
int main()
{
ScoreRec A;
ScoreRec B("Henry","123456","B");
string name,id;
char score;
A.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
B.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
return 0;
}
二、为什么使用函数重载?
在不同情形下,虽然处理数据的个数、数据类型可能不同,但用相同的函数名表达本质上相同的操作更符合人们的习惯,故函数重载给编程提供便利。
三、使用函数重载时需要注意的问题
1 若函数名、函数形参个数及对应的数据类型都相同,只是函数的返回类型不同,则不是有效的函数重载;编译时出现“函数定义重复”错误;
2 若函数返回类型、函数名、形参个数及对应数据类型相同,只有参数传递方式不同,则不是有效的函数重载;
3 调用重载函数时的二义性问题
3.1 隐式类型转换引起的二义性问题
在赋值、参数传递等情形下,当数据类型不匹配时,编译器会自动进行数据类型转换,即隐式类型转换;
#include <iostream>
#include <cmath>
using namesapce std;
bool equal(int,int);
bool equal(double,double);
int main()
{
int i;
double d;
i=3;
d=3.0;
switch(equal(i,d))
{
case true:
cout<<i<<"=="<<d<<endl;
break;
case false:
cout<<i<<"!="<<d<<endl;
break;
}
return 0;
}
bool equal(int a,int b)
{
return a==b?true:false;
}
bool equal(double a,double b)
{
if(fabs(a-b)<1e-6)
return true;
else
return false;
}
编译上述程序,出现以下错误:
error C2666: 'equal' : 2 overloads have similar conversions.
原因是可将equal(i,d)调用中的i转换成d型,也可将d转换成i型,编译器无法确定如何转换,因为两种转换都有相应的函数对应;
3.2 使用默认参数时的二义性
使用默认参数的函数本身就是将几个重载函数合而为一;
#include <iostream>
#include <cmath>
using namesapce std;
double distance(float,float);
double distance(float,float,float x=0,float y=0);
int main()
{
float point1X,point1Y,point2X,point2Y;
point1X=5.0;
point1Y=5.0;
point2X=7.0;
point2Y=7.0;
cout<<"The distance to the origin is:\n";
cout<<"Point 1 ("<<point1X<<","<<point1Y<<"): "<<distance(point1X,point1Y)<<endl;
return 0;
}
double distance(float pX,float pY)
{
return sqrt(pX*pX+pY*pY);
}
double distance(float p1X,float p1Y,float p2X,float p2Y)
{
return sqrt((p2X-p1X)*(p2X-p1X)+(p2Y-p1Y)*(p2Y-p1Y));
}
编译上述程序,出现以下错误:
error C2668: 'distance' : ambiguous call to overloaded function.
原因是double distance(float p1X,float p1Y,float p2X=0,float p2Y=0)使用默认参数,它实际上是多个函数的集合体:
若调用该函数时,给出4个实际参数,相当于调用double distance(float p1X,float p1Y,float p2X,float p2Y)函数;
若调用该函数时,给出3个实际参数,相当于调用double distance(float p1X,float p1Y,float p2X)函数,同时将函数体中的p2Y设为0;
若调用该函数时,给出2个实际参数,相当于调用double distance(float p1X,float p1Y)函数,同时将函数体中的p2X和p2Y设为0,实际上等价于求到原点的距离;
在main函数中调用distance(point1X,point1Y)函数时,编译器无法确定调用的是哪个distance函数;
注意:
在指定默认参数时,必须从参数表的最右边开始并连续指定默认参数。
- 复制构造函数
一、复制构造函数对的语法形式
复制构造函数的形参类型是类类型本身,且使用引用方式进行参数的传递。复制构造函数的原型形式如:X(const X&)
其中,X是类名,形参通常指定为const;
举例:
class Computer{
public:
Computer(const Computer& anotherComputer);
};
二、复制构造函数的使用场合
若程序员没有为类定义复制构造函数,编译器会自动生成一个复制构造函数。
1 用已有的对象对新对象进行初始化
举例1:
#include <iostream>
#include <string>
using namesapce std;
class ScoreRec{
public:
ScoreRec()
{
name="";
ID="";
score=' ';
cout<<"Default constructor.\n";
}
ScoreRec(string newName,string newID,char newScore)
{
name=newName;
ID=newID;
Score=newScore;
cout<<"Constructor with parameters.\n";
}
ScoreRec(const ScoreRec& anotherScoreRec)
{
name=anotherScoreRec.name;
ID=anotherScoreRec.ID;
score=anotherScoreRec.score;
cout<<"Copy constructor.\n";
}
void getRecord(string& nameGet,string& IDGet,char& scoreGet)
{
nameGet=name;
IDGet=ID;
scoreGet=score;
}
private:
string name;
string ID;
char score;
};
int main()
{
ScoreRec A;
ScoreRec B("Henry","123456","B");
ScoreRec C(B);
string name,id;
char score;
A.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
B.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
C.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
return 0;
}
上述程序的运行结果:
Default constructor.
Constructor with parameters.
Copy constructor.
Henry
123456
B
Henry
123456
B
分析:
ScoreRec A;语句生成一个ScoreRec对象,每个对象的初始化由默认构造函数完成,则有Default constructor.输出;
ScoreRec B("Henry","123456",'B');语句调用带参数的构造函数,则有Constructor with parameters.输出;该语句用参数对对象B的数据成员进行初始化;
ScoreRec C(B);语句调用复制构造函数,用已有的对象B初始化新的对象C,则有Copy constructor.输出;用已有的对象初始化新对象时,新对象的数据成员的值和已有对象的数据成员的值完全相等;
注意:
编译器自动生成的复制构造函数的工作是将已有对象的内存空间中的数据按位复制一份到新对象的内存空间,称为浅复制。
浅复制采用按位复制方法,当类的数据成员中有指针,会引发一些意想不到的问题。
举例2:
#include <iostream>
#include <string>
using namesapce std;
class ScoreRec{
public:
ScoreRec()
{
name=NULL;
ID=NULL;
score=' ';
cout<<"Default constructor.\n";
}
~ScoreRec()
{
if(name!=NULL)
delete[] name;
if(ID!=NULL)
delete[] ID;
cout<<"Deconstructor.\n";
}
ScoreRec(char* newName,char* newID,char newScore)
{
name=new char[strlen(newName)+1];
strcpy(name,newName);
ID=new char[strlen(newID)+1];
strcpy(ID,newID);
Score=newScore;
cout<<"Constructor with parameters.\n";
}
ScoreRec(const ScoreRec& anotherScoreRec)
{
name=new char[strlen(anotherScoreRec.name)+1];
ID=new char[strlen(anotherScoreRec.ID)+1];
strcpy(name,anotherScoreRec.name);
strcpy(ID,anotherScoreRec.ID);
score=anotherScoreRec.score;
cout<<"Copy constructor.\n";
}
void getRecord(string& nameGet,string& IDGet,char& scoreGet)
{
strcpy(nameGet,name);
strcpy(IDGet,Id);
scoreGet=score;
}
private:
char* name;
char* ID;
char score;
};
int main()
{
ScoreRec B("Henry","123456","B");
ScoreRec C(B);
char name[50],id[50];
char score;
B.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
C.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
return 0;
}
上述程序的运行结果:
Constructor with parameters.
Copy constructor.
Henry
123456
B
Henry
123456
B
Deconstructor.
Deconstructor.
分析:
由于ScoreRec类的数据成员使用指针类型,故特别留意内存空间的申请与释放!
在上述程序中,内存空间的申请放在构造函数中,空间的释放放在析构函数中;构造函数在创建类对象时调用,析构函数在类对象的生命周期结束时调用;
如下图为复制构造函数执行后两个ScoreRec类对象的内存情况,B和C中name的值不同,ID的值也不同。由于name和ID是指针数据类型,可理解为B和C中,name指向不同的内存空间,ID指向不同的内存空间;但内存空间中存储的是相同的内容,称为深复制,将一个对象中指针成员所指的内存空间的内容复制到另一个对象对应指针成员所指的内存空间中。
若没有定义复制构造函数,编译器会自动生成一个复制构造函数。编译器自动生成的复制构造函数通过浅复制将已有对象中指针成员所指向的内存空间中的数据按位复制一份到新对象的对应指针成员所指向的内存空间。如下图为浅复制构造函数执行后两个ScoreRec类对象的内存情况。由于name和ID是指针类型数据,可理解为按位复制后,B和C中name指向相同的内存空间,ID也指向相同的内存空间。
当对象的生命期结束时,系统会自动调用解析函数。同一内存的重复释放会导致运行时错误。这一点在类的数据成员中包含有指针时,需要特别注意!因此,需要程序员自己定义复制构造函数,通过深复制解决多个指针指向同一内存的问题。深复制时,B和C的name值是分别通过new操作赋值的,指向不同内存空间,不会出现同一内存空间的重复释放。
2 调用以值形参传递类对象的函数
举例:
#include <iostream>
#include <string>
using namesapce std;
class ScoreRec{
public:
ScoreRec()
{
name=NULL;
ID=NULL;
score=' ';
cout<<"Default constructor.\n";
}
~ScoreRec()
{
if(name!=NULL)
delete[] name;
if(ID!=NULL)
delete[] ID;
cout<<"Deconstructor.\n";
}
ScoreRec(char* newName,char* newID,char newScore)
{
name=new char[strlen(newName)+1];
strcpy(name,newName);
ID=new char[strlen(newID)+1];
strcpy(ID,newID);
Score=newScore;
cout<<"Constructor with parameters.\n";
}
ScoreRec(const ScoreRec& anotherScoreRec)
{
name=new char[strlen(anotherScoreRec.name)+1];
ID=new char[strlen(anotherScoreRec.ID)+1];
strcpy(name,anotherScoreRec.name);
strcpy(ID,anotherScoreRec.ID);
score=anotherScoreRec.score;
cout<<"Copy constructor.\n";
}
void getRecord(string& nameGet,string& IDGet,char& scoreGet)
{
strcpy(nameGet,name);
strcpy(IDGet,Id);
scoreGet=score;
}
private:
char* name;
char* ID;
char score;
};
void writeScoreRec(ScoreRec rec)
{
char name[50],id[50];
char score;
rec.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
return;
}
int main()
{
ScoreRec B("Henry","123456","B");
ScoreRec C(B);
writeScoreRec(B);
writeScoreRec(C);
return 0;
}
上述程序的运行结果:
Constructor with parameters.
Copy constructor.
Copy constructor.
Henry
123456
B
Deconstructor.
Copy constructor.
Henry
123456
B
Deconstructor.
Deconstructor.
Deconstructor.
分析:
writeScoreRec的形参rec相当于局部对象,将实参B传递给形参rec,相当于用已有对象初始化新对象,需要调用复制构造函数,因此输出Copy constructor.;
当函数writeScoreRec返回时,局部对象rec的生命周期结束,需要调用析构函数,因此输出Deconstructor.;
向值形参传递实参时,需生成一个实参的副本。当函数以值传递的方式进行类对象的传递时,会导致复制构造函数的调用,其语义相对复杂,因此在以类对象作为参数时,应使用引用形参的方式定义类对象的传递。
3 调用返回值为类对象的函数
增加函数setScoreRec,用以生成一个ScoreRec对象,根据参数设置该对象的属性数据,并返回该对象。
举例:
#include <iostream>
#include <string>
using namesapce std;
class ScoreRec{
public:
ScoreRec()
{
name=NULL;
ID=NULL;
score=' ';
cout<<"Default constructor.\n";
}
~ScoreRec()
{
if(name!=NULL)
delete[] name;
if(ID!=NULL)
delete[] ID;
cout<<"Deconstructor.\n";
}
ScoreRec(char* newName,char* newID,char newScore)
{
name=new char[strlen(newName)+1];
strcpy(name,newName);
ID=new char[strlen(newID)+1];
strcpy(ID,newID);
Score=newScore;
cout<<"Constructor with parameters.\n";
}
ScoreRec(const ScoreRec& anotherScoreRec)
{
name=new char[strlen(anotherScoreRec.name)+1];
ID=new char[strlen(anotherScoreRec.ID)+1];
strcpy(name,anotherScoreRec.name);
strcpy(ID,anotherScoreRec.ID);
score=anotherScoreRec.score;
cout<<"Copy constructor.\n";
}
void getRecord(string& nameGet,string& IDGet,char& scoreGet)
{
strcpy(nameGet,name);
strcpy(IDGet,Id);
scoreGet=score;
}
void setName(char* newName)
{
if(name!=NULL)
delete[] name;
name=new char[strlen(newName)+1];
strcpy(name,newName);
}
void setID(char* newID)
{
if(ID!=NULL)
delete[] ID;
ID=new char[strlen(newID)+1];
strcpy(ID,newID);
}
void setScore(char newScore)
{
score=newScore;
}
private:
char* name;
char* ID;
char score;
};
ScoreRec setScoreRec(char* newName, char* newID, char newScore)
{
ScoreRec tempRec;
tempRec.setName(newName);
tempRec.setID(newID);
tempRec.setScore(newScore);
return tempRec;
}
void writeScoreRec(ScoreRec rec)
{
char name[50],id[50];
char score;
rec.getRecord(name,id,score);
cout<<name<<endl<<id<<endl<<score<<endl;
return;
}
int main()
{
char stuName[50]="Henry";
char stuID[50]="123456";
char stuScore='B';
writeScoreRec(setScoreRec(stuName,stuID,stuScore));
return 0;
}
上述程序的运行结果:
Default constructor.
Copy constructor.
Deconstructor.
Henry
123456
B
Deconstructor.
分析:
setScoreRec函数中有ScoreRec tempRec;语句,调用默认构造函数,输出Default constructor.
setScoreRec函数中将tempRec的各项数据进行设置,再return tempRec;语句。当以值方式返回类对象时,系统实际创建一个临时的全局类对象(假设为tempObj),作为待返回对象的副本,并调用复制构造函数初始化tempObj对象。return tempRec;语句会导致一个tempObj的创建即复制构造函数的调用;同时由于setScoreRec函数返回,tempRec生命期结束,需要调用析构函数。
最后一个Deconstructor.用于撤销tempObj。
- 操作符重载
操作符重载是指同一个操作符表示不同的实际操作;
一、C++操作符的函数特性
若将C++的操作符看作函数,操作数看作函数的参数,则一元操作符有一个参数,二元操作符有两个参数;
如加法操作符是二元操作符,a+b可写成函数的调用方式+ (a, b);
操作符具有函数的特性,也可像函数一样进行重载;操作符的重载可通过类的成员函数实现,也可通过友元函数实现;
二、操作符重载的规则
1 不是所有的操作符都可重载
常见的不能重载的操作符包括:
:: . .* ?: sizeof
2 实现操作符重载的方法
可用成员函数对操作符进行重载,也可用友元函数对操作符进行重载;
操作符重载后对从操作符做出解释,但原有的基本语义,包括操作符的优先级、结核性和所需要的操作数个数均保持不变;
注意:=、[]、()、->等操作符只能使用类成员函数进行重载!
三、类成员函数操作符重载
1 语法及实例
操作符是一种特殊的成员函数,其语法形式为:
<em>返回值类型 类名</em>:: operator <em>操作符 </em>(<em>形参列表</em>)
{
<em>函数体代码</em>
}
举例:
#include <iostream>
#include <ctime>
using namespace std;
class StopWatch{ // 秒表类
public:
StopWatch();
void setTime(int newMin, int newSec);
StopWatch operator - (StopWatch&);
void showTime();
private:
int min;
int sec;
};
StopWatch::StopWatch()
{
min = 0;
sec = 0;
}
void StopWatch::setTime(int newMin, int newSec)
{
min = newMin;
sec = newSec;
}
StopWatch StopWatch::operator - (StopWatch& anotherTime)
{
StopWatch tempTime;
int seconds;
seconds = min * 60 + sec - (anotherTime.min * 60 + anotherTime.sec);
if (seconds < 0)
{
seconds = -seconds;
}
tempTime.min = seconds / 60;
tempTime.sec = seconds % 60;
return tempTime;
}
void StopWatch::showTime()
{
if (min>0)
cout << min << " minutes " << sec << "seconds" << endl;
else
cout << sec << " seconds" << endl;
}
int main()
{
StopWatch startTime, endTime, usedTime;
cout << "Press enter key to start.";
cin.get();
time_t curtime = time(0);
tm tim = *localtime(&curtime);
int min, sec;
min = tim.tm_min;
sec = tim.tm_sec;
startTime.setTime(min, sec);
cout << "Press enter key to start.";
cin.get();
curtime = time(0);
tim = *localtime(&curtime);
min = tim.tm_min;
sec = tim.tm_sec;
endTime.setTime(min, sec);
usedTime = endTime - startTime;
cout << "Used time: ";
usedTime.showTime();
system("pause");
return 0;
}
运行结果:
Press enter key to start.<回车>
Press enter key to start.<回车>
Used time: 5 seconds
分析:
在上述StopWatch类中,我们重载了-操作符,实现了两个秒表时间之间的差操作;这里重载的是二元操作符;
重载操作符的成员函数的函数头为:StopWatch StopWatch::operator - (StopWatch& anotherTime)
其中包含以下信息:
a 函数名是operator -,表示重载操作符-
b StopWatch::表示重载该操作符的类是StopWatch类
c 该函数的返回类型是StopWatch类
d 该函数带一个参数,参数类型是StopWatch类,应以引用方式传递
在main函数中usedTime=endTime-startTime;解释为usedTime=endTime.operator - (startTime);
即调用endTime的operator -函数,参数是startTime,返回值赋值给usedTime;
2 参数个数和操作数个数的关系
用成员函数实现操作符重载,函数参数个数个操作符所带操作数个数之间有以下规则:
2.1 一元操作符实现为不带参数的成员函数
2.2 二元操作符实现为带一个参数的成员函数
注意:假设我们把二元操作符的操作数分别称为左操作数和右操作数,用成员函数重载时,则函数的参数中只包含右操作数,左操作数是调用该函数的类对象,该类对象通过this指针隐含传递给操作符函数;
3 典型的操作符重载:赋值操作符重载
#include <iostream>
#include <string>
using namespace std;
class ScoreRec{
public:
ScoreRec()
{
name = NULL;
ID = NULL;
score = ' ';
cout << "Default constructor." << endl;
}
ScoreRec(char* newName, char* newID, char newScore)
{
name = new char[strlen(newName) + 1];
strcpy(name, newName);
ID = new char[strlen(newID) + 1];
strcpy(ID, newID);
score = newScore;
cout << "Constructor with parameters." << endl;
}
~ScoreRec()
{
if (name!=NULL)
delete[] name;
if(ID!=NULL)
delete[] ID;
cout << "Deconstructor." << endl;
}
ScoreRec(ScoreRec& anotherScoreRec)
{
name = new char[strlen(anotherScoreRec.name) + 1];
strcpy(name, anotherScoreRec.name);
ID = new char[strlen(anotherScoreRec.ID) + 1];
strcpy(ID, anotherScoreRec.ID);
score = anotherScoreRec.score;
cout << "Copy constructor." << endl;
}
void getScoreRec(char* nameGet, char* IDGet, char& scoreGet)
{
strcpy(nameGet, name);
strcpy(IDGet, ID);
scoreGet = score;
}
void setName(char* newName)
{
if (name != NULL)
delete[] name;
name = new char[strlen(newName) + 1];
strcpy(name, newName);
}
void setID(char* newID)
{
if (ID != NULL)
delete[] ID;
ID = new char[strlen(newID)+1];
strcpy(ID, newID);
}
void setScore(char newScore)
{
score = newScore;
}
ScoreRec& operator = (ScoreRec& anotherScoreRec)
{
if (name != NULL)
delete[] name;
if (ID != NULL)
delete[] ID;
name = new char[strlen(anotherScoreRec.name)+1];
strcpy(name, anotherScoreRec.name);
ID = new char[strlen(anotherScoreRec.ID)+1];
strcpy(ID, anotherScoreRec.ID);
score = anotherScoreRec.score;
return *this;
}
private:
char* name;
char* ID;
char score;
};
ScoreRec setScoreRec(char* newName, char* newID, char newScore)
{
ScoreRec tempRec;
tempRec.setName(newName);
tempRec.setID(newID);
tempRec.setScore(newScore);
return tempRec;
}
void writeScoreRec(ScoreRec rec)
{
char name[50], id[50];
char score;
rec.getScoreRec(name, id, score);
cout << name << endl << id << endl << score << endl;
return;
}
int main()
{
char stuName[50] = "Henry";
char stuID[50] = "123456";
char stuScore = 'A';
ScoreRec tempRec;
tempRec = setScoreRec(stuName, stuID, stuScore);
writeScoreRec(tempRec);
system("pause");
return 0;
}
默认赋值操作符采用浅复制,即按位复制,当复制内容中存在指针变量时,需要定义自己的赋值操作符进行深复制;
四、友元操作符重载
1 友元的概念
对于普通的类外函数f,要访问一个类A的私有成员和受保护成员是不可能的,除非将A的私有成员和受保护成员的访问权限改为public,但这样失去了类的封装作用,任何类外函数都可毫无拘束地使用其成员;但有时一个类外函数确实需要访问类的private或protected成员,因此在开放和封装之间折中,C++利用friend修饰符,设定类的友元,友元可对私有和受保护的成员进行操作。
友元使类外函数能直接访问类的私有和受保护数据,提供了程序设计时的灵活性,但同时也破坏了累的封装特性,因此使用友元要慎重!
类的友元可以使一个普通函数(非成员函数)、另一个类的成员函数、另一个完整的类;类的友元在类定义中使用friend保留字进行说明,在friend保留字后列出友元的名字(若友元为函数,则给出函数原型;若友元为类,则给出class类名);将另一个完整的类设为友元时,该类中所有成员函数都视为本类的友元函数。
举例:
#include <iostream>
#include <string>
using namespace std;
class House{
public:
House(string name,string address)
{
House::name = name;
House::address = address;
}
friend void showHouse(House& newHouse);
private:
string name;
string address;
};
void showHouse(House& newHouse)
{
cout << newHouse.name << endl;
cout << newHouse.address << endl;
}
int main()
{
House clientHouse("YuQian", "ZhongShan Road");
showHouse(clientHouse);
system("pause");
return 0;
}
类House的定义中包含友元函数的声明:friend后紧跟函数showHouse的原型;
注意:showHouse并不是类House的成员函数,因此在函数定义时不能写成:
void House::showHouse(House& newHouse);
但该函数可访问类House的私有成员name和address。
2 友元操作符重载
友元的另一个作用是操作符重载;
举例:
定义描述二维坐标(x,y)的类Pos,用友元函数实现+操作符的重载;
#include <iostream>
using namespace std;
class Pos{
public:
Pos(float newPos_x = 0, float newPos_y = 0)
{
pos_x = newPos_x;
pos_y = newPos_y;
}
void showPos()
{
cout << "x="<<pos_x << "\ty=" << pos_y << endl;
}
friend Pos operator + (Pos&, Pos&);
// 重载+操作符的友元函数的声明
private:
float pos_x;
float pos_y;
};
Pos operator + (Pos& pos1, Pos& pos2)
// 友元函数的定义
{
Pos temp;
temp.pos_x = pos1.pos_x + pos2.pos_x;
temp.pos_y = pos1.pos_y + pos2.pos_y;
return temp;
}
int main()
{
Pos pos1(25, 50), pos2(1, 2);
Pos pos3;
cout << "Pos 1:" << endl;
pos1.showPos();
cout << "Pos 2:" << endl;
pos2.showPos();
pos3 = pos1 + pos2;
cout << "Pos 1 + Pos 2:" << endl;
pos3.showPos();
system("pause");
return 0;
}
分析:
程序中定义的类Pos描述一个二维坐标点,用友元函数重载+操作符,实现两个坐标点想家的操作:对两个坐标对象执行+运算,将两个坐标点的两个分量分别相加;
在主函数中,生命了三个坐标对象,pos_1, pos_2, pos_3,其中pos_1+pos_2被解释为对+操作符重载函数的调用;
上述操作符重载也可用成员函数来实现:
#include <iostream>
using namespace std;
class Pos{
public:
Pos(float newPos_x = 0, float newPos_y = 0)
{
pos_x = newPos_x;
pos_y = newPos_y;
}
void showPos()
{
cout << "x="<<pos_x << "\ty=" << pos_y << endl;
}
Pos operator + (Pos&);
private:
float pos_x;
float pos_y;
};
Pos Pos::operator + (Pos& anotherPos)
{
Pos temp;
temp.pos_x = pos_x + anotherPos.pos_x;
temp.pos_y = pos_y + anotherPos.pos_y;
return temp;
}
int main()
{
Pos pos1(25, 50), pos2(1, 2);
Pos pos3;
cout << "Pos 1:" << endl;
pos1.showPos();
cout << "Pos 2:" << endl;
pos2.showPos();
pos3 = pos1 + pos2;
cout << "Pos 1 + Pos 2:" << endl;
pos3.showPos();
system("pause");
return 0;
}
注意:友元操作符重载和成员函数操作符重载的不同在于:
a 一元操作符可实现不带参数的成员函数或带一个参数的友元函数;
b 二元操作符可实现带一个参数的成员函数或带两个参数的友元函数;