[关闭]
@cxm-2016 2016-09-30T01:29:54.000000Z 字数 2524 阅读 1951

C++:友元函数

c++

版本:2
作者:陈小默
说明:调整了部分语句结构

友元

通常情况下,类的私有变量是不能够被外界直接访问的,但是在某些情况下我们需要在非成员的方法中使用这些私有变量,于是C++提供了一种新的语法机制叫做友元。


为什么需要友元

以运算符重载为例,我们在上一节创建了一个Time类的例子。里面重载了*运算符。于是我们可以采用以下形式来将时间扩大三倍:

  1. time = time * 3;

但是下面的写法有没有问题呢?

  1. time = 3 * time;

从概念上说这两句应该具有相同的效果。但是却不能通过编译,因为我们没有对3进行过运算符重载。
通常情况我们有两种解决方案:

  1. Time operator*(int n,const Time & t);

如果我们要使用这种方式的话就需要给所有需要被访问的私有属性设个getter方法。这样又会导致我们对外暴露出我们本想隐藏的数据。
为了解决这个问题我们引入了友元函数。

如何创建友元函数

第一步:在声明中使用friend关键字声明友元函数原型:

  1. friend Time operator*(int n,Time &t);

注意:虽然友元函数在类的声明中声明,但其不是类的成员函数,因此不能使用成员运算符调用;虽然友元函数不是成员函数,但是具有与成员函数相同的访问权限。

第二步:实现友元函数:

  1. Time operator*(int n,Time &t){
  2. t.time*=n;
  3. return t;
  4. }

注意:因为友元函数不是成员函数,所以不能使用函数限定符Time::;其次不要在定义中使用friend关键字,除非声明即定义。

重载<<运算符


假使我们有这样一个需求:让cout对象能够直接对Time对象进行输出。我们应该怎么办呢?根据上面的学习我们应该能够想到,就是采用友元函数对<<运算符进行重载。

  1. #ifndef primer_time_h
  2. #define primer_time_h
  3. #include<string>
  4. #include<iostream>
  5. class Time{
  6. private:
  7. enum {SECOND_MAX=1000,MINUTE_MAX=60*1000,HOURS_MAX=60*60*1000};
  8. long long time;
  9. public:
  10. void setMinutes(int m);
  11. void setHours(int h);
  12. void setSecondes(int s);
  13. long long getLongTime();
  14. Time& operator+(Time &t);
  15. Time& operator-(Time &t);
  16. Time& operator*(int n);
  17. Time& operator/(int n);
  18. friend Time& operator*(int n,Time &t);
  19. friend std::ostream& operator<<(std::ostream &os,Time &t){
  20. int hours = int(t.time/HOURS_MAX);
  21. int minutes = int((t.time%HOURS_MAX)/MINUTE_MAX);
  22. int seconds = int(t.time%MINUTE_MAX/SECOND_MAX);
  23. os<<"Time["<<hours<<":";
  24. if(minutes<10)
  25. os<<0;
  26. os<<minutes<<":";
  27. if(seconds<10)
  28. os<<0;
  29. os<<seconds<<"]";
  30. return os;
  31. };
  32. };
  33. #endif

接下来实现代码

  1. #include "stdafx.h"
  2. #include "time.h"
  3. void Time::setHours(int h){
  4. if(h>=0){
  5. time = time%HOURS_MAX+h*HOURS_MAX;
  6. }
  7. }
  8. void Time::setMinutes(int m){
  9. if(m>=0&&m<60){
  10. time = (time-time%HOURS_MAX)+time%MINUTE_MAX+m*MINUTE_MAX;
  11. }
  12. }
  13. void Time::setSecondes(int s){
  14. if (s>=0&&s<60){
  15. time = (time-time%MINUTE_MAX)+s*SECOND_MAX;
  16. }
  17. }
  18. long long Time::getLongTime(){
  19. return time;
  20. }
  21. Time& Time::operator+(Time &t){
  22. time+=t.getLongTime();
  23. return *this;
  24. }
  25. Time& Time::operator-(Time &t){
  26. time-=t.getLongTime();
  27. return *this;
  28. }
  29. Time& Time::operator*(int n){
  30. time*=n;
  31. return *this;
  32. }
  33. Time& Time::operator/(int n){
  34. time/=n;
  35. return *this;
  36. }
  37. Time& operator*(int n,Time &t){
  38. t.time*=n;
  39. return t;
  40. }

现在我们看一下修改后的效果

  1. #include"stdafx.h"
  2. #include"time.h"
  3. int main(int argc, const char * argv[]) {
  4. Time t1,t2;
  5. t1.setHours(23);
  6. t1.setMinutes(59);
  7. t1.setSecondes(58);
  8. t2.setSecondes(1);
  9. t1 = t1 + 2 * t2;
  10. cout<<t1<<endl;
  11. return 0;
  12. }

这里演示了23小时59分58秒加行两个一秒钟之后的时间。
运行结果

Time[24:00:00]

这里程序很完美的完成了这个任务。

友元和成员


学到这里我们发现一个方法可以有多种的重载方式。对于一个重载,我们可以使用下面两种方式中的任意一种。

  1. Time& operator+(Time &t);
  1. friend Time& operator+(Time &t1,Time &t2);

但是,我们不能同时声明这两种方式,因为编译器无法确定具体要重载哪一个方法。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注