• C++友元函数


    对于普通函数,无法直接访问类的私有成员,还有时候需要普通函数来访问类的私有成员以简化代码,这时就需要友元函数登场了。


    一个相等性函数

    下面代码实现一个 DayOfYear 的类,它记录一个日期。并在其中加入一个相等性函数(判断两个日期是否相等)。

    #include 
    
    using namespace std;
    
    class DayOfYear
    {
    public:
        void input();
        void output();
    
        void set(int newMonth, int newDay);
        // 前条件:newMonth 和 newDay 构成一个可能的日期
        // 后条件:日期根据传递的实参来重置
    
        int getMonth();
        // 返回月份,1代表1月,2代表2月,依次类推
        int getDay();
        // 返回天数
    private:
        void checkDate();
        int month;
        int day;
    };
    
    // 相等性函数
    bool equal(DayOfYear date1, DayOfYear date2);
    // 前条件:date1 和 date2 已被赋值
    // 如果 date1 和 date2 表示相同的日期,就返回true,否则返回false
    
    int main()
    {
        DayOfYear today, bachBirthday;
        cout<<"Enter today's date:\n";
        today.input();
        cout<<"Today's date is ";
        today.output();
    
        bachBirthday.set(3, 21);
        cout<< "J. S. Bach's birthday is ";
        bachBirthday.output();
    
        if ( equal(today, bachBirthday) )
            cout<<"Happy Birthday Johann Sebastian!\n";
        else
            cout<<"Happy Unbirthday Johann Sebastian!\n";
        return 0;
    }
    
    bool equal(DayOfYear date1, DayOfYear date2)
    {
        return ( date1.getMonth() == date2.getMonth() &&
                date1.getDay() == date2.getDay() );
    }
    
    // 使用 iostream
    void DayOfYear::input()
    {
        cout<<"Enter the month as a number: ";
        cin>>month;
        cout<<"Enter the day of the month: ";
        cin>>day;
        checkDate();
    }
    
    void DayOfYear::output()
    {
        cout<< "month = " <<month
            << ", day = " << day << endl;
    }
    
    void DayOfYear::set(int newMonth, int newDay)
    {
        month = newMonth;
        day = newDay;
        checkDate();
    }
    
    void DayOfYear::checkDate()
    {
        if( (month < 1) || (month > 12) || (day < 1) || (day > 31) )
        {
            cout<<"Illegal date. Aborting program.\n";
            exit(1); // 用于退出程序
        }
    }
    
    int DayOfYear::getMonth()
    {
        return month;
    }
    
    int DayOfYear::getDay()
    {
        return day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    相等性函数 equal 的定义很直观。如两个日期代表同一个月,以及一个月中的同一天,两个日期就相等。 equal 的定义使用取值函数 getMonthgetDay 来比较两个对象代表的月份和天数。


    友元函数

    上面的相等性函数 equal 使用取值函数 get 访问私有成员变量,如果能直接访问成员变量,代码会变得更简洁高效。

    equal 更简单有效的定义如下:

    bool equal(DayOfYear date1, DayOfYear date2)
    {
    	return ( date1.month == date2.month && date1.day === date2.day );
    }
    
    • 1
    • 2
    • 3
    • 4

    这样做虽然能使代码更简洁,但其是非法的。因为成员变量 monthdayDayOfYear 类的私有成员。私有成员通常不能在一个函数的主体中引用,除非函数是一个成员函数。

    现在的问题是,equal 并非 DayOfYear 的成员函数。但有一个办法可以为非成员函数赋予和成员函数一样的访问权限。让 equal 函数成为 DayOfYear 的友元,上述 equal 定义就完全合法了。

    类的友元函数不是这个类的成员函数,而是一个 ”友好“ 的函数,它能像成员函数那样访问类的私有成员。友元函数可以直接读取成员变量的值,甚至能直接更改成员变量的值。

    在类定义中声明友元函数只需在函数声明前添加关键字 friend。友元函数不是成员函数,其本质上仍然是普通函数,只是被特别授予了访问类的数据成员的权限。友元的定义和调用方式与普通函数无异。

    下面在之前的代码中将相等性函数 equal 更新为友元函数。

    #include 
    
    using namespace std;
    
    class DayOfYear
    {
    public:
        // 相等性函数
        friend bool equal(DayOfYear date1, DayOfYear date2);
        // 前条件:date1 和 date2 已被赋值
        // 如果 date1 和 date2 表示相同的日期,就返回true,否则返回false
    
        void input();
        void output();
    
        void set(int newMonth, int newDay);
        // 前条件:newMonth 和 newDay 构成一个可能的日期
        // 后条件:日期根据传递的实参来重置
    
        int getMonth();
        // 返回月份,1代表1月,2代表2月,依次类推
        int getDay();
        // 返回天数
    private:
        void checkDate();
        int month;
        int day;
    };
    
    
    int main()
    {
        DayOfYear today, bachBirthday;
        cout<<"Enter today's date:\n";
        today.input();
        cout<<"Today's date is ";
        today.output();
    
        bachBirthday.set(3, 21);
        cout<< "J. S. Bach's birthday is ";
        bachBirthday.output();
    
        if ( equal(today, bachBirthday) )
            cout<<"Happy Birthday Johann Sebastian!\n";
        else
            cout<<"Happy Unbirthday Johann Sebastian!\n";
        return 0;
    }
    
    bool equal(DayOfYear date1, DayOfYear date2)
    {
        return ( date1.month == date2.month &&
                date1.day == date2.day );
    }
    
    // 使用 iostream
    void DayOfYear::input()
    {
        cout<<"Enter the month as a number: ";
        cin>>month;
        cout<<"Enter the day of the month: ";
        cin>>day;
        checkDate();
    }
    
    void DayOfYear::output()
    {
        cout<< "month = " <<month
            << ", day = " << day << endl;
    }
    
    void DayOfYear::set(int newMonth, int newDay)
    {
        month = newMonth;
        day = newDay;
        checkDate();
    }
    
    void DayOfYear::checkDate()
    {
        if( (month < 1) || (month > 12) || (day < 1) || (day > 31) )
        {
            cout<<"Illegal date. Aborting program.\n";
            exit(1); // 用于退出程序
        }
    }
    
    int DayOfYear::getMonth()
    {
        return month;
    }
    
    int DayOfYear::getDay()
    {
        return day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    友元函数可以简化函数定义,并提高效率。对于使用成员还是非成员函数,一个简单的使用规则如下:

    • 如果任务与单个对象密切相关,就使用成员函数。
    • 如果任务涉及多个对象,而且对象被均匀地使用,就使用非成员函数。

    const 参数修饰符

    ”传引用“ 参数效率上优于 ”传值“ 参数。传值参数是局部变量,被初始化成实参的值,所以调用函数时会存在实参的两个拷贝。而传引用参数只是占位符,会被实参取代,所以只存在实参的一个拷贝。对于简单类型(比如 intdouble)的参数,这种效率上的差异可以忽略不计。但对于类类型的参数,两者效率上的区别有时就非常明显,必须引起重视。

    如果使用传引用参数,而且函数不更改参数,就可为参数做上标记,让编译器知道参数不应更改。为此,要在参数类型前添加修饰符 const。这样的参数是常量参数。

    之前代码中的相等性函数 equal 并不会对参数进行修改,我们可以将其改为传引用的常量参数。

    #include 
    
    using namespace std;
    
    class DayOfYear
    {
    public:
        // 相等性函数
        friend bool equal(const DayOfYear &date1, const DayOfYear &date2);
        // 前条件:date1 和 date2 已被赋值
        // 如果 date1 和 date2 表示相同的日期,就返回true,否则返回false
    
        void input();
        void output();
    
        void set(int newMonth, int newDay);
        // 前条件:newMonth 和 newDay 构成一个可能的日期
        // 后条件:日期根据传递的实参来重置
    
        int getMonth();
        // 返回月份,1代表1月,2代表2月,依次类推
        int getDay();
        // 返回天数
    private:
        void checkDate();
        int month;
        int day;
    };
    
    
    int main()
    {
        DayOfYear today, bachBirthday;
        cout<<"Enter today's date:\n";
        today.input();
        cout<<"Today's date is ";
        today.output();
    
        bachBirthday.set(3, 21);
        cout<< "J. S. Bach's birthday is ";
        bachBirthday.output();
    
        if ( equal(today, bachBirthday) )
            cout<<"Happy Birthday Johann Sebastian!\n";
        else
            cout<<"Happy Unbirthday Johann Sebastian!\n";
        return 0;
    }
    
    bool equal(const DayOfYear &date1, const DayOfYear &date2)
    {
        return ( date1.month == date2.month &&
                date1.day == date2.day );
    }
    
    // 使用 iostream
    void DayOfYear::input()
    {
        cout<<"Enter the month as a number: ";
        cin>>month;
        cout<<"Enter the day of the month: ";
        cin>>day;
        checkDate();
    }
    
    void DayOfYear::output()
    {
        cout<< "month = " <<month
            << ", day = " << day << endl;
    }
    
    void DayOfYear::set(int newMonth, int newDay)
    {
        month = newMonth;
        day = newDay;
        checkDate();
    }
    
    void DayOfYear::checkDate()
    {
        if( (month < 1) || (month > 12) || (day < 1) || (day > 31) )
        {
            cout<<"Illegal date. Aborting program.\n";
            exit(1); // 用于退出程序
        }
    }
    
    int DayOfYear::getMonth()
    {
        return month;
    }
    
    int DayOfYear::getDay()
    {
        return day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    常量参数是自动错误检查的一种形式。函数定义不慎更改了常量参数,编译器会报错。参数修饰符 const 适合任何参数,但通常只将它用于传引用的类参数(偶尔也用于其他一些参数,前提是传递的实参值较大)。

    调用成员函数时,调用对象的行为和传引用参数很相似,因为成员函数调用可更改调用对象的值。我们可以将 const 修饰符标记成员函数,表示成员函数不应更改调用对象的值。函数代码不慎更改了调用对象的值,编译器会报错。

    在成员函数的情况下,关键字 const 要放到函数声明的后面,刚好在末尾的分号之前,比如对之前代码的 outputconst 修饰。函数声明部分如下:

    void output() const;
    
    • 1

    函数定义部分如下:

    void DayOfYear::output() const
    {
        cout<< "month = " <<month
            << ", day = " << day << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对于 const 在类中的使用有一个准则,要么都用,要么都不用。即为特定类型的参数使用了 const 之后,对于同类型的其他所有参数,如果它们不由函数调用更改,那么也应该使用 const。此外,凡是不更改调用对象值的成员函数都应使用 const 修饰符。之所以制定这个规矩,原因和函数调用中的函数调用有关。例如以下 guarantee 函数定义”

    void guarantee( const Money& price )
    {
    	cout<< 2 * price.getValue() <<endl;
    }
    
    • 1
    • 2
    • 3
    • 4

    不为成员函数 getValue 的函数声明加上 const 修饰符,guarantee 函数会在大多数编译器上报错。成员函数 getValue 不更改调用对象 price。但当编译器处理 guarantee 的函数定义时,它认为 getValue 确实(或至少有可能)会更改 price 的值。这时由于当编译器翻译 guarantee 的函数定义时,对于成员函数 getValue ,它目前唯一知道的只有 getValue 的函数声明。在函数声明中,如果不包含告诉编译器调用对象不会被更改的 const,编译器就假定调用对象会被更改。所以,一旦为 Money 类型的参数使用了修饰符 cosnt,就要为不更改调用对象值的所有 Money 成员函数都使用 const

  • 相关阅读:
    正点原子嵌入式linux驱动开发——Linux 4G通信
    【Autosar 存储栈Memery Stack 4.Tc397的Flash编程】
    BP神经网络参数设置总结
    Git常用指令-1
    Linux:keepalived + ipvsadm
    UVA 12716 GCD等于XOR GCD XOR
    Java接口的相关知识
    ES6知识点(1)
    Java中的集合框架
    亚马逊是如何铺设多个IP账号实现销量大卖的?
  • 原文地址:https://blog.csdn.net/weixin_44491423/article/details/125999353