接着上一篇《友元是什么》中,我们发现Remote友元类的大多数方法都是用Tv类的公有接口实现。这意味着这些方法并不是真正需要友元。

事实上唯一直接访问Tv成员的Remote方法是Remote::set_chan(),因此它是唯一需要作为友元的方法。

确实可以仅让特定的类成员成为另一类的友元。

这种做法稍微有点麻烦,必须小心排列各种声明和定义的顺序。

让Remote::set_chan()成为Tv类的友元的方法是,在Tv类声明中将其声明为友元:

class Tv

{

  friend void Remote::set_chan(Tv & t, int c);

}

要让编译器能够处理这条语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而set_chan是一个类方法;

这就意味着要把Remote的定义放到Tv类定义之前。

但是Remote方法提到了Tv对象,而这就意味着Tv定义应当放在Remote定义之前。

这就产生了循环依赖的问题。要避免循环依赖关系,就要使用前向声明(forward declaration)

解决方法如下:

class Tv;  //forward declaration  告诉编译器Tv是一个类

class Remote {...};  //然后再Remote中出现set_chan 方法时,知道其中Tv是一个类

class Tv {...};

//这里补充一句,让整个Remote类成为友元并不需要前向声明,因为友元语句本身已经指出Remote是一个类;

friend class Remote;

但是能否像下面这样排列呢?

class Remote ;  //forward declaration

class Tv {...};

class Remote {...};

答案是不能,因为在编译器看到Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应先看到Remote类的声明和set_chan()方法的声明。

还有一个麻烦就是。Remote声明中包含了内联代码

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

由于这将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明。这样才能知道Tv有哪些方法,但正如看到的,该声明位于Remote声明的后面。

这种问题的解决方法是,使Remote声明中只包含方法声明,并将实际的定义放在Tv类之后。

class Tv;  //forward declaration

class Remote {...};  //Tv-using methods as prototypes only  只包含方法的声明

class Tv {...};

//put Remote method definitions here  定义在这里写

Remote方法的声明与下面类似

void onoff(Tv & t);

检查该原型时,编译器都需要知道Tv是一个类,而向前声明提供了这样的信息。

当编译器到达真正的方法定义时,它已经读取到了Tv类的声明,并拥有编译这些方法的所需信息。

通过在方法定义中使用inline关键字,仍然可以使其成为内联方法。

//这种友元成员函数的声明、定义顺序非常微妙,令人抓狂。很容易造成错误,一旦问题复杂起来,定位bug都很困难。难怪C++是个大坑。友元的存在就是其中一个大坑。把类的关系,函数的关系搞复杂了。

tvfm.h

 #ifndef TVFM_H_
#define TVFM_H_ class Tv; //forward declaration 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);
bool onoff(Tv & t);
bool chanup(Tv & t);
bool chandown(Tv & t);
void set_mode(Tv & t);
void set_input(Tv & t);
void set_chan(Tv & t, int c);
}; 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)?Cable:Antenna;}
void set_input() {input = (input == TV)?DVD:TV;}
void settings() const; private:
int state;
int volume;
int channel;
int maxchannel;
int mode;
int input;
}; //Remote methods as inline functions
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_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}

C++_友元2-友元成员函数的更多相关文章

  1. C++ 友元 (全局函数做友元) (类做友元) (成员函数做友元)

    1 //友元 全局函数做友元 2 /* 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 ...

  2. 读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数

    1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { pu ...

  3. 重载运算符:类成员函数or友元函数

    类成员函数: bool operator ==(const point &a)const { return x==a.x; } 友元函数: friend bool operator ==(co ...

  4. C++学习之路—运算符重载(二)运算符重载作为类的成员函数和友元函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 对运算符重载的函数有两种处理方式:(1)把运算符 ...

  5. 友元(友元函数、友元类和友元成员函数) C++

    有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的.例如被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员. 友元(frend)机制允许一个类将 ...

  6. C++运算符重载三种形式(成员函数,友元函数,普通函数)详解

    首先,介绍三种重载方式: //作为成员函数重载(常见) class Person{ Private: string name; int age; public: Person(const char* ...

  7. C++运算符重载形式——成员函数or友元函数

    运算符重载是C++多态的重要实现手段之一.通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能. 运算符重载的我们需要坚持四项基本原则: (1)不可 ...

  8. C++ 类 & 对象-类成员函数-类访问修饰符-C++ 友元函数-构造函数 & 析构函数-C++ 拷贝构造函数

    C++ 类成员函数 成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义. 需要强调一点,在 :: 运算符之前必须使用类名.调用成员函数是在对象上使用点运算符(.),这样它就能操作与 ...

  9. c++友元函数、友元类、友成员函数

    友元函数:不是类成员函数,是一个类外的函数,但是可以访问类所有成员. class Point{ public: friend void fun(Point t);//友元函数 private: int ...

随机推荐

  1. DataSet、DataTable转换List(泛型集合与DataSet互相转换 )

    using System.Data; using System.Reflection; using System.Collections; using System.Collections.Gener ...

  2. 【bzoj1083】[SCOI2005]繁忙的都市

    1083: [SCOI2005]繁忙的都市 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2424  Solved: 1591[Submit][Sta ...

  3. ROS编译:catkin简析

    博客转载自:https://blog.csdn.net/zyh821351004/article/details/50388429 Catkin tutorials: http://wiki.ros. ...

  4. 认识Session

    Session在不同环境下的不同含义 session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session ...

  5. MVC下的cshtml和aspx页面

    MVC中的aspx页面是System.Web.Mvc.ViewPage类的实例. 表示将视图呈现为 Web 窗体页所需的属性和方法. 继承层次结构 System.Object System.Web.U ...

  6. 第01章-Spring之旅

    一.简化Java开发 1. Spring的主要特性 依赖注入DI和面向切面编程AOP. 2. 关键策略 轻量级和最小侵入性编程:POJO 松耦合:DI和AOP 声明式编程:切面和惯例 减少样板式代码: ...

  7. 编写javascript的基本技巧一

    自己从事前端编码也有两年有余啦,时间总是比想象中流逝的快.岁月啊,请给我把时间的 脚步停下吧.不过,这是不可能的,我在这里不是抒发时间流逝的感慨.而是想在这分享两 年来码农生活的一些javascrip ...

  8. 用JS实现表格的高亮显示

    1.所用事件详解 2.<thead>,<tbody>标签介绍 <thead> 标签定义表格的表头.该标签用于组合 HTML 表格的表头内容. thead 元素应该与 ...

  9. C# 真正完美的 汉字转拼音

    网上有很多说自己整理的汉字转拼音是完美的,但使用后才发现都是半吊的瓶子,问题多多. 常见的生僻字,或多音字识别,转换后简直让人感觉可怕. 主流的转换有三种:hash匹配,Npinyin,微软PinYi ...

  10. win8 附件数据库失败解决方案《1》

    sql server 2005附加数据库错误:尝试打开或创建物理文件 无法打开物理文件 "E:\works\database\northwnd\northwnd.mdf".操作系统 ...