C++友元函数

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


一个相等性函数

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

#include <iostream>

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;
}

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


友元函数

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

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

bool equal(DayOfYear date1, DayOfYear date2)
{
	return ( date1.month == date2.month && date1.day === date2.day );
}

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

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

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

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

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

#include <iostream>

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;
}

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

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

const 参数修饰符

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

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

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

#include <iostream>

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;
}

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

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

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

void output() const;

函数定义部分如下:

void DayOfYear::output() const
{
    cout<< "month = " <<month
        << ", day = " << day << endl;
}

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值