接着上一篇《友元是什么》中,我们发现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. mysql中如何不重复插入满足某些条件的重复的记录的问题

    最近在项目中遇到了这样的一个问题“: 在mysql数据库中需要每次插入的时候不能插入三个字段都相同的记录.在这里使用到了 insert into if not exists  和insert igno ...

  2. SQL CLR学习

    SQL CLR (SQL Common Language Runtime) 是自 SQL Server 2005 才出现的新功能,它将.NET Framework中的CLR服务注入到 SQL Serv ...

  3. 70个HR面试题

    请你自我介绍一下你自己,      回答提示:一般人回答这个问题过于平常,只说姓名.年龄.爱好.工作经验,这些在简历上都有,其实,企业最希望知道的是求职者能否胜任工作,包括:最强的技能.最深入研究的知 ...

  4. 使用 tpl 标签和 for 读取对象属性值中的数组

    来源于<sencha touch 权威指南> ----------------------------- 只摘抄app.js代码: Ext.require(['Ext.form.Panel ...

  5. laravel与front-end

    准备工作 在此之前要安装node  . npm .这里安装node . npm 就不介绍了,百度一大把. 安装所有的npm依赖包 //进入项目的根目录 npm install 安装完后会出现一个nod ...

  6. JMS-消息中间件的应用02-安装ActiveMQ-来自慕课学习-新手学习

    What is ActiveMQ?       -----突然好想打英文,好奇怪 请看来自官网的介绍: Apache ActiveMQ ™ is the most popular and powerf ...

  7. h5存储的优点

    1.解决4k大小问题2.解决请求头常带存储信息的问题3.解决关系型存储问题4.可以跨浏览器

  8. Responsive设计——meta标签

    media-queries.js(http://code.google.com/p/css3-mediaqueries-js/) respond.js(https://github.com/scott ...

  9. [GO]变量内存和变量地址

    package main import "fmt" func main() { //每个变量都有两层含义,变量的内存和变量的地址 fmt.Printf("a = %d\n ...

  10. Chrome浏览器控件安装方法

    说明:只需要安装up6.exe即可,up6.exe为插件集成安装包. 1.以管理员身份运行up6.exe.up6.exe中已经集成Chrome插件.