zoukankan      html  css  js  c++  java
  • C++友元函数

    C++控制对类对象私有部分的访问。通常,公有类方法提供惟一的访问途径,但是有时候这种限制太严格,以至于不适合特定的编程问题。在这种情况下,C++提供了另外一种形式的访问权限:友元。友元有3种:
    * 友元函数;
    * 友元类;
    * 友元成员函数。
    通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
    这里只介绍友元函数(其他两种友元 在 第15章)。
    为什么需要友元?在为类重载二元运算符时(带两个参数的运算符)常常需要友元。将Time对象成一实数就属于这种情况。
    在前面(书中)的Time类示例中,重载的惩罚运算符与其他两种重载运算符的差别在于,它使用了两种不同的类型。也就是说,加法和剑法运算符都结合两个Time值,而惩罚运算符将一个Time值与一个double值结合在一起。这显示了该运算符的使用方式。记住:左侧的操作数是调用对象。也就是说,下面的语句:
    A = B * 2.75;
    将被转换为下面的成员函数调用:
    A.operator+(2.75);
    但下面的语义又如何呢?
    A = 2.75 * B;    // cannot correspond to a member function
    从概念上说,2.75*B应与B*2.75相同,但第一个表达式不对应于成员函数,因为2.75不是Time类型的对象。记住,左侧的操作数应是调用对象,但2.75不是对象。因此,编译器不能使用成员函数调用来替换该表达式。
    解决这个难题的一种方式是,告知每个人(包括程序员自己),只能按B*2.75这种格式编写,不能写2.75*B。这时一种对服务器友好-客户警惕的(server-friendly,client-baware)解决方案,与OOP无关。
    然而,还有另一种解决方式——非成员函数(记住,大多数运算符都可以通过成员或非成员函数来重载)。非成员函数不是由对象调用的,它使用的所有值(包括对象)都是显式参数。这样,编译器能够将下面的表达式:
    A = 2.75 * B;    // cannot corredpond to a member function
    与下面的非成员函数调用匹配:
    A = operator*(2.75, B);
    该函数的原型如下:
    Time operator*(double m, const Time & t);
    对于非成员运算符函数来说,运算符函数左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数。而原来的成员函数则按相反方向的顺序处理操作数,也就是说,double值成一Time值。
    使用非成员函数可以按所需的顺序获得操作数(显示double,后是Time),但引发一个新问题:非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。然而,有一类特殊的非成员函数可以访问类的私有成员,它们被称为友元函数。

    11.3.1 创建友元
    创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend:
    friend Time operator*(double m, const Time & t);    // goes in class declaration
    该原型意味着下面亮点:
    * 虽然operator*()函数是在类声明中生命的,但它不是成员函数,因此不能使用成员运算符来调用;
    * 虽然operator*()函数不是成员函数,单它与成员函数的访问权限相同。
    第二步是编写函数定义。因为它不是成员函数,所以不要使用Time::限定符。另外,不要再定义中使用关键字friend,定义应该如下:
    Time operator*(double mult, const Time & t) // friend not used in definition
    {
        Time result;
        long totalminutes = t.hours * mult * 60 + t.minutes * mult;
        result.hours = totalminutes / 60;
        result.minutes = totalminutes % 60;
        return result;
    }
    有了上述声明和定义后,下面的语句:
    A = 2.75 * B;
    将转换为如下语句,从而调用刚才定义的非成员友元函数:
    A = operator*(2.75, B);
    总之,类的友元函数是非成员函数,其访问权限与成员函数相同。

    11.3.2 常用的友元:重载<<运算符
    一个很有用的类特性是,可以对<<运算符进行重载,使之能与cout一起来显示对象的内容。与前面介绍的示例相比,这种重载要复杂些,因此我们分两步(而不是一步)来完成。
    假设trip是一个Time对象。为显示Time的值,前面使用的是Show()。然而,如果可以像下面这样操作将更好:
    cout << trip;    // make cout recognize Time class?
    之所以可以这样做,是因为<<是可悲重载的C++运算符之一。实际上,它已经被重载很多次了。最初,<<运算符是C和C++的位运算符,将值中的位左移。ostream类对该运算符进行了重载,将其转换为一个输出工具。前面讲过,cout是一个ostream对象,它是智能的,能够识别所有的C++基本类型。这是因为对于每种基本类型,ostream类声明中都包含了相应的重载的operator<<()定义。也就是说,一个定义使用int参数,一个定义使用double参数,等等。因此,要使cout能够识别Time对象,一种方法是将一个新的函数运算符定义添加到ostream类声明中。单修改iostream文件是个危险的注意,这样做会在标准接口上浪费时间。相反,通过Time类声明来让Time类知道如何使用cout。
    1. <<的第一个重载版本
    要使Time类知道使用cout,必须使用友元函数。这是因为下面这样的语句使用了两个对象,其中第一个是ostream类对象(cout):
    cout << trip;
    如果使用一个Time类成员函数来重载<<,Time对象将是第一个操作数,就像使用成员函数重载*运算符那样。这意味着必须这样使用<<:
    trip << cout;    // if operator<<() were a Time member function
    这样会令人迷惑。但通过使用友元函数,可以像下面这样重载运算符:
    void operator<<(ostream & os, const Time & t)
    {
        os << t.hours << " hours, " << t.minutes << " minutes";
    }
    这样可以使用下面的语句:
    cout << trip;
    按下面这样的格式打印数据:
    4 hours, 23 minutes

    2. <<的第二种重载版本
    前面介绍的实现存在一个问题。像下面这样的语句可以正常工作:
    cout << trip;
    但这种实现不允许像通常那样将重新定义的<<运算符与cout一起使用:
    cout << "Trip time: " << trip << " (Tuesday) ";    // can't do
    要理解这样做不可行的原因以及必须如何做才能使其可行,首先需要了解关于cout操作的一点知识。
    如下面的语句:
    int x = 5;
    int y = 8;
    cout << x << y;
    C++从左至右读取输出语句,意味着它等同于:
    (cout << x) << y;
    正如iostream中定义的那样,<<运算符要求左边是一个ostream对象。显然,因为cout是ostream对象,所以表达式cout << x满足这种要求。然而,因为表达式cout << x位于<< y的左侧,所以输出语句也要求该表达式是一个ostream类型的对象。因此,ostream类将operator<<()函数实现为返回一个指向ostream对象的引用。具体地说,它返回一个指向调用对象(这里是cout)的引用。因此,表达式(cout << x)本身就是ostream对象cout,从而可以位于<<运算符的左侧。
    可以对友元函数采用相同的方法。只要operator<<()函数,让它返回ostream对象的为引用即可:
    ostream & operator<<(ostream & os, const Time & t)
    {
        os << t.hours << " hours, " << t.minutes << " minutes";
        return os;
    }
    注意,返回类型是ostream &。这意味着该函数返回ostream对象的引用。因为函数开始执行时,程序传递了一个对象引用给它,这样做的最终结果是,函数的返回值就是传递给它的对象。也就是说,下面的语句:
    cout << trip;
    将被转换为下面的调用:
    operator<<(cout, trip);
    而该调用返回cout对象。因此,下面的语句可以正常工作:
    cout << "Trip time: " << trip << " (Tuesday) "; // can do
    程序清单11.10列出了修改后的类定义,其中包括operator*()和operator<<()这两个友元函数。它将第一个友元函数作为内联函数,因为其代码很短。(当定义同时也是原型时,就像这个例子中那样,要使用frinend前缀。)
    程序清单11.10 mytime3.h

    // mytime3.h -- Time class with friends
    #ifndef MYTIME3_H_
    #define MYTIME3_H_
    #include <iostream>
    
    class Time
    {
    private:
        int hours;
        int minutes;
    public:
        Time();
        Time(int h, int m = 0);
        void AddMin(int m);
        void AddHr(int h);
        void Reset(int h = 0, int m = 0);
        Time operator+(const Time & t) const;
        Time operator-(const Time & t) const;
        Time operator*(double n) const;
        friend Time operator*(double m, const Time & t)
            { return t * m; }
        friend std::ostream & operator<<(std::ostream & os, const Time & t);
    };
    
    #endif // MYTIME3_H_

    程序清单11.11列出了修改后的定义。
    程序清单11.11 mytime3.cpp

    // mytime3.cpp -- implementing Time methods
    #include "mytime3.h"
    
    Time::Time()
    {
        hours = minutes = 0;
    }
    
    Time::Time(int h, int m)
    {
        hours = h;
        minutes = m;
    }
    
    void Time::AddMin(int m)
    {
        minutes += m;
        hours += minutes / 60;
        minutes %= 60;
    }
    
    void Time::AddHr(int h)
    {
        hours += h;
    }
    
    void Time::Reset(int h, int m)
    {
        hours = h;
        minutes = m;
    }
    
    Time Time::operator+(const Time & t) const
    {
        Time sum;
        sum.minutes = minutes + t.minutes;
        sum.hours = hours + t.hours + sum.minutes / 60;
        sum.minutes %= 60;
        return sum;
    }
    
    Time Time::operator-(const Time & t) const
    {
        Time diff;
        int tot1, tot2;
        tot1 = t.minutes + 60 * t.hours;
        tot2 = minutes + 60 * hours;
        diff.minutes = (tot2 - tot1) % 60;
        diff.hours = (tot2 - tot1) / 60;
        return diff;
    }
    
    Time Time::operator*(double mult) const
    {
        Time result;
        long totalminutes = hours * mult * 60 + minutes * mult;
        result.hours = totalminutes / 60;
        result.minutes = totalminutes % 60;
        return result;
    }
    
    std::ostream & operator<<(std::ostream & os, const Time & t)
    {
        os << t.hours << " hours, " << t.minutes << " minutes";
        return os;
    }

    程序清单11.12是一个示例程序。
    程序清单11.12 usetime3.cpp

    // usetime3.cpp -- using the fourth draft of the Time class
    // compile usetime3.cpp and mytime3.cpp together
    #include <iostream>
    #include "mytime3.h"
    
    int main()
    {
        using std::cout;
        using std::endl;
        Time aida(3, 35);
        Time tosca(2, 48);
        Time temp;
    
        cout << "Aida and Tosca:
    ";
        cout << aida << "; " << tosca << endl;
        temp = aida + tosca;    // operator+()
        cout << "Aida + Tosca: " << temp << endl;
        temp = aida * 1.17;     // member operator*()
        cout << "Aida * 1.17 " << temp << endl;
        cout << "10.0 * Tosca: " << 10.0 * tosca << endl;
    
        return 0;
    }

    下面是程序清单11.10~11.12组成的程序的输出:

    Aida and Tosca:
    3 hours, 35 minutes; 2 hours, 48 minutes
    Aida + Tosca: 6 hours, 23 minutes
    Aida * 1.17 4 hours, 11 minutes
    10.0 * Tosca: 28 hours, 0 minutes



  • 相关阅读:
    p1012拼数题解
    LeetCode OJ :Unique Binary Search Trees II(唯一二叉搜索树)
    UVA 11827 Maximum GCD
    LightOJ1336 Sigma Function(约数和为偶数的个数)
    LightOJ 1197 Help Hanzo(区间素数筛选)
    LightOJ 1236
    BZOJ3339 Rmq Problem
    COJ983 WZJ的数据结构(负十七)
    LCA的五种解法
    hdu4223(dp)
  • 原文地址:https://www.cnblogs.com/moonlightpoet/p/5660733.html
Copyright © 2011-2022 走看看