一、引言

C++控制对类对象私有部分(private)的访问,通常只能通过公有的(public)类方法去访问。但是有时候这种限制太严格,不适合特定的问题,于是C++提供了另外一种形式的访问权限:友元。友元有三种:

  • 友元函数
  • 友元类
  • 友元成员函数

二、友元函数

通过让函数称为友元函数,可以赋予该函数与类的成员函数相同的访问权限。为何需要友元呢?在为类重载二元运算符时常常需要友元,关于运算符的重载可以参考我的博文:

C++学习笔记之运算符重载

下面举例说明:

 //mytime0
#ifndef MYTIME0_H
#define MYTIME0_H class Time
{
private:
int hours;
int minutes;
public:
Time();
Time(int h, int m = );
void addMin(int m);
void addHr(int h);
void reset(int h = , int m = );
Time operator*(double n) const;
void show() const;
}; #endif
 #include <iostream>
#include "mytime0.h" Time::Time()
{
hours = minutes = ;
} Time::Time(int h, int m)
{
hours = h;
minutes = m;
} void Time::addMin(int m)
{
minutes += m;
hours += minutes / ;
minutes %= ;
} void Time::addHr(int h)
{
hours += h;
} void Time::reset(int h, int m)
{
hours = h;
minutes = m;
} Time Time::operator*(double mult)const
{
Time result;
long totalminutes = hours * mult * + minutes * mult;
result.hours = totalminutes / ;
result.minutes = totalminutes % ;
return result;
}
void Time::show() const
{
std::cout << hours << " hours, " << minutes << " minutes";
}

上述代码建立了一个Time类并重载了这个类的*运算符,将一个Time值与一个double值结合在一起。但是,这将会产生一个问题,重载函数使得*运算符左侧的操作数是调用它的对象,即,下面的语句:

A =  B * 1.75;(这里A,B均为Time类对象)

将被转化为:A = B.operator*(1.75);

但是,下面的语句会怎么转化呢?

A = 1.75 * B;

从概念上说,B * 1.75应该与1.75 * B相同,但是因为1.75不是Time类的对象,所以1.75无法调用被重载的*的成员函数operator*(),所以编译器这时候就不知道如何处理这个表达式,将会报错。

如果要求使用Time的*运算符时,只能按照B * 1.75这种格式书写,当然可以解决问题,但是显然不友好。

另一种解决方式--非成员函数。假如我们定义了一个非成员函数重载*:

Time operator*(double m, const Time & t);

则编译器既可以将 A = 1.75 * B与下面的非成员函数A = operator*(1.75, B);

但是如何实现这个函数呢?因为非成员函数不能直接访问类的私有数据,至少常规非成员函数不可以。但是,事事皆有例外,有一类特殊的非成员函数可以访问类的私有成员,即友元函数。

创建友元函数

  • 将原型前面加上关键字friend,然后放在类声明中:
friend Time operator*(double n, const Time & t); 

该原型有如下两点特性:虽然operator*()函数是在类中声明的,但是它不是成员函数,因此不能用成员运算符来调用;虽然operator*()不是成员函数,但它与成员函数的访问权限相同。

  • 编写函数定义

因为它不是成员函数,故不能用Time::限定符,特别注意,不要在定义中使用关键字friend,如下:

 Time operator*(double m, const Time & t) //不要使用friend关键字
{
Time result;
long totalminutes = t.hours * m * + t.minutes * m;
result.hours = totalminutes / ;
result.minutes = totalminutes % ;
return result;
}

然后,编译器就会调用刚才定义的非成员函数将A = 1.75 * B转换为A = operator*(1.75, B)。本人感觉就是重载了operator*()函数,当然是不是如此有待讨论。

总之,记住,类的友元函数是非成员函数,但其访问权限与成员函数相同。

三、友元类

一个类可以将其他类作为友元,这样,友元类的所有方法都可以访问原始类的私有成员(private)和保护成员(protected),也可以根据需要,只将特定的成员函数指定为另一个类的友元,哪些函数,成员函数或类为友元是由类自己定义的,不能外部强加,就好像你愿意把谁当做是你的朋友,是你自己在心里决定的,别人无法强制。

举例说明,假定需要编写一个模拟电视机和遥控器的简单程序。定义一个Tv类和一个Remote,分别表示电视机和遥控器,遥控器可以改变电视机的状态,应将Remote类作为Tv类的一个友元。

 /*Tv and Remote classes*/
#ifndef TV_H_
#define TV_H_
class Tv
{
public:
friend class Remote; //声明谁是自己的“好基友”(友元)
enum {Off, On}; //
enum {MinVal, MaxVal};
enum {Antenna, Cable};
enum {TV, DVD}; Tv(int s = Off, int mc =) : state(s), volume(),
maxchannel(mc), channel(), mode(Cable), input(TV) {};
void onoff(){state = (state == On)? Off : On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna) ? Antenna : Cable;}
void set_input() {input = (input = TV) ? DVD : TV;}
void settings() const; //显示所有设置
private:
int state; //开或者关
int volume; //音量
int maxchannel; //最多频道数
int channel; //当前频道号
int mode; //Antenna或者Cable模式
int input; //TV或者DVD输入
}; class Remote
{
private:
int mode; //控制是TV或者DVD
public:
Remote(int m = Tv::TV) : mode(m) {}
bool volup(Tv & t) {return t.volup();}
bool voldown(Tv & t) {return t.voldown();}
void onoff(Tv & t) {t.onoff();}
void chanup(Tv & t) {t.chanup();}
void chandown(Tv & t) {t.chandown();} /*此处,友元类成员函数set_chan()访问了原始类Tv的私有成员channel
即使t是Tv的对象,要知道一个类是不允许对象直接访问私有成员的,此处
之所以可以,就是因为Remote是Tv“好基友”(友元)的缘故*/
void set_chan(Tv & t, int c) {t.channel = c;} void set_mode(Tv & t) {t.set_mode();}
void set_input(Tv & t) {t.set_input();}
};
#endif
 #include <iostream>
#include "tv.h" bool Tv::volup()
{
if (volume < MaxVal)
{
volume++;
return true;
}
else
return false;
}
bool Tv::voldown()
{
if (volume > MinVal)
{
volume--;
return true;
}
else
return false;
}
void Tv::chanup()
{
if (channel < maxchannel)
channel++;
else
channel = ;
}
void Tv::chandown()
{
if (channel > )
channel--;
else
channel = maxchannel;
} void Tv::settings() const
{
using std::cout;
using std::endl;
cout << "TV is " << (state == Off ? "Off" : "On") << endl;
if (state == On)
{
cout << "Volume setting = " << volume << endl;
cout << "Channel setting = " << channel << endl;
cout << "Mode = " <<
(mode == Antenna? "antenna" : "cable") << endl;
cout << "Input = "
<< (input == TV? "TV" : "DVD") << endl;
}
}
 /*usetv*/
#include <iostream>
#include "tv.h" int main()
{
using std::cout;
Tv s42;
cout << "Initial settings for 42\" TV: \n";
s42.settings();
s42.onoff();
s42.chanup();
cout << "\n Adjusted settings for 42\" TV: \n";
s42.chanup();
cout << "\n Adjusted settings for 42\" TV: \n";
s42.settings(); Remote grey; grey.set_chan(s42, );
grey.volup(s42);
grey.volup(s42);
cout << "\n42\" settings after using remote:\n";
s42.settings(); Tv s58(Tv::On);//这反应了一个遥控器可以控制多台电视
s58.set_mode();
grey.set_chan(s58, );
cout << "\n58\' settings:\n";
s58.settings();
return ;
}

运行结果:

四、友元成员函数

对于上面的例子,大多数Remote方法都是用Tv的共有接口实现的,意味着这些用Tv的共有接口实现的方法不需要作为友元,唯一直接访问Tv成员的Remote方法是Remote::set_chan(),因此它是唯一需要作为友元的方法,所以可以仅让特定的类成员成为另一个类的友元,而不必将整个类成为友元。

让Remote::set_chan()成为Tv友元的方法是:

class Tv
{
friend void Remote::set_chan(Tv & t, int c);
...
};

要处理上述语句,编译器必须知道Remote的定义,所以Remote的定义应该放在Tv定义前面,问题是Remote::set_chan(Tv & t, int c)使用了Tv类的对象,故而Tv的定义应该放在Remote定义前面,这就产生了矛盾。

为了解决这个矛盾,需要使用一种叫做前向声明(forward declaration),就是在Remote定义之前插入如下语句:

class Tv;

即排列次序如下:

 class Tv;//前向声明
class Remote {...};
class Tv {...};

能否这样:

 class Remote ;//前向声明
class Tv {...};
class Remote {...};

这样做是不可以的,因为编译器在Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应该先看到Remote类的声明和set_chan()方法的声明,如果像上面这样,虽然看到了Remote类的声明,但是看不到set_chan()方法的声明。

还有一个问题,比如在Remote声明中包含如下内联函数:

void onoff(Tv & t) {t.onoff();}

也就是说,在声明这个方法的时候,就给出了它的定义,即调用了Tv的一个方法,而Tv的声明(不是前向声明哦)是放在Remote声明后面的,显然会有问题。所以解决方法是,在Remote声明中只包含方法的声明,而不去定义,将实际的定义放在Tv类之后,即

void onoff(Tv & t) ;

编译器在检查该原型时,需要知道Tv是一个类,而前向声明提供了这种信息,当编译器到达真正的方法定义时,它已经读取了Tv类的声明,关于函数内联,后面可以使用inline关键字声明。

给出修改后的头文件:

 /*Tv and Remote classes*/
#ifndef TV_H_
#define TV_H_ class Tv; class Remote
{
public:
enum State{Off, On};
enum {MinVal, MaxVal = };
enum {Antenna, Cable};
enum {TV, DVD};
private:
int mode;
public:
//只声明,不定义
Remote(int m =TV) : mode(m) {}
bool volup(Tv & t);
bool voldown(Tv & t);
void onoff(Tv & t);
void chanup(Tv & t);
void chandown(Tv & t);
void set_chan(Tv & t, int c);
void set_mode(Tv & t);
void set_input(Tv & t);
}; class Tv
{
public:
friend void Remote::set_chan(Tv & t, int c); //声明谁是自己的“好基友”(友元)
enum State{Off, On};
enum {MinVal, MaxVal = };
enum {Antenna, Cable};
enum {TV, DVD}; Tv(int s = Off, int mc =) : state(s), volume(),
maxchannel(mc), channel(), mode(Cable), input(TV) {};
void onoff(){state = (state == On)? Off : On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna) ? Antenna : Cable;}
void set_input() {input = (input = TV) ? DVD : TV;}
void settings() const; //显示所有设置
private:
int state; //开或者关
int volume; //音量
int maxchannel; //最多频道数
int channel; //当前频道号
int mode; //Antenna或者Cable模式
int input; //TV或者DVD输入
}; //Remote方法定义为内联函数
inline bool Remote::volup(Tv & t) {return t.volup();}
inline bool Remote::voldown(Tv & t) {return t.voldown();}
inline void Remote::onoff(Tv & t) {t.onoff();}
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
#endif

五、一点补充:共同友元

有时为了让一个函数能够同时访问两个类的私有数据,可以让该函数同时成为这两个类的友元。

C++学习笔记之友元的更多相关文章

  1. 初探C++运算符重载学习笔记&lt;2&gt; 重载为友元函数

    初探C++运算符重载学习笔记 在上面那篇博客中,写了将运算符重载为普通函数或类的成员函数这两种情况. 以下的两种情况发生.则我们须要将运算符重载为类的友元函数 <1>成员函数不能满足要求 ...

  2. 初步C++运算符重载学习笔记&lt;3&gt; 增量递减运算符重载

    初步C++运算符重载学习笔记<1> 初探C++运算符重载学习笔记<2> 重载为友元函数     增量.减量运算符++(--)分别有两种形式:前自增++i(自减--i).后自增i ...

  3. 《C++ Primer Plus》学习笔记6

    <C++ Primer Plus>学习笔记6 第11章 使用类 <<<<<<<<<<<<<<<&l ...

  4. Day 3 学习笔记

    Day 3 学习笔记 STL 模板库 一.结构体 结构体是把你所需要的一些自定义的类型(原类型.实例(:包括函数)的集合)都放到一个变量包里. 然后这个变量包与原先的类型差不多,可以开数组,是一种数据 ...

  5. 《C++ Primer Plus》学习笔记9

    <C++ Primer Plus>学习笔记9 第15章 友元.异常和其他 <<<<<<<<<<<<<<& ...

  6. c++中的运算符重载operator1(翁恺c++公开课[30]学习笔记)

    运算符重载规则: 只有已经存在的运算符才能被重载,不能自己制造一个c++中没有的运算符进行重载 重载可以在类或枚举类型内进行,也可以是全局函数,但int.float这种已有的类型内是不被允许的 不能二 ...

  7. C++基础 学习笔记五:重载之运算符重载

    C++基础 学习笔记五:重载之运算符重载 什么是运算符重载 用同一个运算符完成不同的功能即同一个运算符可以有不同的功能的方法叫做运算符重载.运算符重载是静态多态性的体现. 运算符重载的规则 重载公式 ...

  8. Qt Creator 源码学习笔记04,多插件实现原理分析

    阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...

  9. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

随机推荐

  1. 自己的一个LESS工具函数库

    自己大概在一年前开始使用LESS编写样式,现在感觉不用LESS都不会写样式了.现在写静态页面完全离不开LESS与Zen Coding,我可以不用什么IDE,但这两个工具却必须要,当然也强烈推荐看到这篇 ...

  2. 小结JS中的OOP(中)

    此篇文章主要是提炼<JavaScript高级程序设计>中第六章的一些内容. 一:JS中OOP相关的概念 开始之前先总结JS中OOP相关的一些概念: 构造函数:JS中的构造函数就是普通的函数 ...

  3. Jquery 设置style:display 通过ID隐藏区域

    $("#id").css('display','none'); $("#id").css('display','block'); 或 $("#id&q ...

  4. Linux free -m 详细说明

    一.free命令 free命令由procps.*.rpm提供(在Redhat系列的OS上).free命令的所有输出值都是从/proc/meminfo中读出的. 在系统上可能有meminfo(2)这个函 ...

  5. android操作SQLite

    一.SQLite SQLite是一种转为嵌入式设备设计的轻型数据库,其只有五种数据类型,分别是: NULL: 空值 INTEGER: 整数 REAL: 浮点数 TEXT: 字符串 BLOB: 大数据 ...

  6. LoadRunner error -27728

    错误现象1:Action.c(16): Error -27728: Step download timeout (120 seconds) has expired whendownloading no ...

  7. jsoup入门

    官网地址:http://jsoup.org/ Jsoup是一个开源的Java库,它可以用于处理实际应用中的HTML.它提供了非常便利的API来进行数据的提取及修改,充分利用了 DOM,CSS以及jqu ...

  8. 棒棒的毛笔字PS教程

    跟大家分享一下毛笔字怎么做出来的,主要通过字体和素材叠加,十分简单,喜欢的一起练习.做完记得交作业. 先看看最终效果: 在网上是不是经常看这些碉堡了的毛笔感觉是不是很羡慕啊,现在我就教大家怎么做出这样 ...

  9. 20+富有创意的BuddyPress网站

    如果你想构建自己的社区网站,如果你熟悉WordPress,那么用BuddyPress构建它吧!它确实太强大了,本文整理了20个富有创意的BuddyPress网站,看看它们,你也能拥有! 原文地址:ht ...

  10. Git 提交后开始自动构建

    设定Git仓库的钩子 一般路径为 xxx.git/hooks 参考文档 https://git-scm.com/docs/githooks 修改 post-receive #!/bin/bash wh ...