C++Primer Plus书之--运算符重载和友元函数

运算符重载:

要重载运算符, 需要使用被称为运算符函数的特殊函数形式, 运算符函数的格式如下:

operatorop(argument list)

例如:operator+()重载+运算符, operator*()重载*运算符, op必须是有效的C++运算符.

例如, 假设有一个Sales类, 并为他定义了一个operator+()成员函数, 以重载+运算符, 以便能够将两个Sales对象的销售额相加, 则如果district2, sid和sara都是Sales类对象, 便可以写这种等式:

district2 = sid + sara;

编译器发现, 操作数是Sales类对象, 因此使用相应的运算符函数替换上述运算符:

district2 = sid.operator+(sara);

然后该函数将隐式的使用sid(因为他调用了方法), 而显示的使用sara对象(因为他被作为参数传递), 来计算总和, 并返回这个值.

来看一个重载运算符的例子:

先看不添加重载运算符函数的代码:

// 第一个文件
// mytime0.h
#ifndef MYTIME0_H_
#define MYTIME0_H_

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);
    // 最后一个const表明函数不会隐式修改成员变量
    Time Sum(const Time & t) const;
    void Show() const;
};

#endif

第二个文件

// 第二个文件
// mytime0.cpp, Time类的函数的实现文件
#include <iostream>
#include "mytime0.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::Sum(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意这里, 由于返回类型是Time, 所以可以直接返回sum
    // 如果返回类型是Time & , 则不能返回sum
    // 因为sum是临时变量, 函数执行完毕后就会释放, 因此不能返回sum的引用
    return sum;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes" << std::endl;    
}

第三个文件:

// 第三个文件:
// usetime0.cpp
// compile with mytime0.cpp
#include <iostream>
#include "mytime0.h"

int main()
{
    using std::cout;
    using std::endl;
    
    // 调用没有参数的构造函数
    Time planning;
    // 调用两个参数的构造函数
    Time coding(2, 40);
    Time fixing(5, 55);
    
    Time total;
    
    cout << "planning time = ";
    planning.Show();
    cout << endl;
    
    cout << "Coding time = ";
    coding.Show();
    cout << endl;
    
    cout << "fixing time = ";
    fixing.Show();
    cout << endl;
    
    total = coding.Sum(fixing);
    cout << "coding.Sum(fixing) = ";
    total.Show();
    cout << endl;
    
    return 0;
}

程序运行结果为:

扫描二维码关注公众号,回复: 4525033 查看本文章

对Time类添加重载+操作符的代码:

// 第一个文件
// mytime1.h
#ifndef MYTIME0_H_
#define MYTIME0_H_

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);
    // 最后一个const表明函数不会隐式修改成员变量
    Time Sum(const Time & t) const;
    // 重载+运算符
    Time operator+(const Time & t) const;
    void Show() const;
    
};

#endif

第二个文件

// mytime1.cpp, Time类的函数的实现文件
#include <iostream>
#include "mytime1.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::Sum(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意这里, 由于返回类型是Time, 所以可以直接返回sum
    // 如果返回类型是Time & , 则不能返回sum
    // 因为sum是临时变量, 函数执行完毕后就会释放, 因此不能返回sum的引用
    return sum;
}

Time Time::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意这里, 由于返回类型是Time, 所以可以直接返回sum
    // 如果返回类型是Time & , 则不能返回sum
    // 因为sum是临时变量, 函数执行完毕后就会释放, 因此不能返回sum的引用
    return sum;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes" << std::endl;    
}

第三个文件

// 第三个文件:
// usetime0.cpp
// compile with mytime1.cpp
#include <iostream>
#include "mytime1.h"

int main()
{
    using std::cout;
    using std::endl;
    
    // 调用没有参数的构造函数
    Time planning;
    // 调用两个参数的构造函数
    Time coding(2, 40);
    Time fixing(5, 55);
    
    Time total;
    
    cout << "planning time = ";
    planning.Show();
    cout << endl;
    
    cout << "Coding time = ";
    coding.Show();
    cout << endl;
    
    cout << "fixing time = ";
    fixing.Show();
    cout << endl;
    
    total = coding.Sum(fixing);
    cout << "coding.Sum(fixing) = ";
    total.Show();
    cout << endl;
    
    
    total = coding + fixing;
    cout << "coding + fixing = ";
    total.Show();
    cout << endl;
    
    
    Time morefixing(3, 28);
    cout << "more fixing time = ";
    morefixing.Show();
    cout << endl;
    
    total = morefixing.operator+(total);
    cout << " morefixing.operator+(total) = ";
    total.Show();
    cout << endl;
    
    return 0;
}

程序运行结果:

C++运算符重载的限制:

1.重载后的运算符必须至少有一个操作数是用户定义的类型, 这能防止用户重载标准类型运算符.

2.使用运算符时不能违反运算符原来的语法规则. 例如:不能将球磨运算符(%)重载成为一个操作数:

int x;
Time t1;
// 这个不符合语法
% x;
// 非法的重载
% t1;

3.不能修改运算符的优先级.

4.不能创建新的运算符.

5.不能重载下面这些运算符:

sizeof

. 成员运算符

.* 成员指针运算符

:: 作用域解析运算符

?: 条件运算符

typeid 一个RTTI运算符

const_cast, dynamic_cast, reinterpret_cast, static_cast 强制类型转换运算符

6.大多数运算符都能通过成员或非成员函数进行重载, 但下面的运算符只能通过成员函数进行重载:

=, (), [], ->

友元:

C++提供了另一种形式的访问权限: 友元, 友元有三种:

友元函数

友元类

友元成员函数

通过让函数称为类的友元, 可以赋予该函数与类的成员函数相同的访问权限.

在前文的Time类示例中, 重载的乘法运算符与其他两种重载运算符(+, -)的差别在于, 它使用了两种不同的类型, 也就是说, 加法和减法运算符都结合两个Time值, 而乘法运算符将一个Time值和一个double值结合在一起, 这限制了该运算符的使用方式. 例如:

A = B * 2.3;

将被转换为:

A = B.operator*(2.3);

但下面的语句就不行了:

A = 2.3 * B;

这条语句就不能正确执行了

从概念上说, 2.3*B应和B*2.3是一样的, 但第一个表达式不对应于成员函数, 因为2.3不是Time类型对象.

C++提供了一种解决办法: 非成员函数(大多数运算符都可以通过成员或非成员函数来重载). 非成员函数不是由对象调用的, 它使用所有的值(包括对象)都是显示参数. 这样, 编译器能够将下面的表达式:

A = 2.3 * B;

与下面的非成员函数调用匹配:

A = operator*(2.3, B);

该函数的原型如下:

Time operator*(double m, const Time & t);

创建友元:

创建友元函数第一步是将其原型放在类声明中, 并在原型声明前加上关键字friend:

// 需要放在类声明中
friend Time operator*(double m, const Time & t);

该原型意味着:

1.虽然operator*()函数时在类声明中声明的, 但它不是成员函数, 因此不能使用成员运算符(.)来调用;

2.虽然operator*()函数不是成员函数, 但它与成员函数的访问权限相同.

第二步是编写函数定义, 因为不是成员函数, 所以不要使用Time::限定符, 并且不要在定义中使用关键字friend:

// 注意不要用Time::限定符, 并且不要使用frined
Time operator*(double mult, const Time & t)
{
    Time result;
    long totalminutes = t.hours * mult * 60 + t.minutes * mult;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

有了上述声明和定义后:

A = 2.3 * B;

将被转换为下面语句, 从而调用刚才定义的非成员友元函数:

A = operator*(2.3, B);

重载<<运算符

还是上面的Time类, 使用友元函数, 重载<<运算符

void operator<<(ostream & os, const Time & t)
{
    // 由于直接访问了Time类的私有变量hours和minutes, 因此必须是Time的友元函数
    os << t.hours << " hours, " << t.minutes << " minutes";
}

这样就可以使用下面的语句:

cout << time1;

但这种实现不允许像通常那样将重新定义的<<运算符与cout一起使用

cout << "Trip time: " << trip << " (Tuesday) \n";

要理解原因, 先看一个关于cout操作的知识点:

int x = 3;
int y = 6;
cout << x << y;

C++从左至右读取输出语句, 意味着它等同于:

(cout << x) << y;

而ostream类将operator<<()函数实现为返回一个指向ostream对象的引用.

因此可以对友元函数采用相同的方法, 只要修改operator<<()函数, 让它返回ostream对象的引用即可:

ostream & operator<<(ostream & os, const Time & t)
{
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}

我们重新看

cout << "Trip time: " << trip << " (Tuesday) \n";

这个语句的工作过程:

首先,

cout << "Trip time: "

这个将调用的是ostream中的<<定义, 它显示字符串并返回cout对象

cout << trip << " (Tuesday) \n";

将调用Time声明显示trip值, 并再次返回cout对象,

cout  << " (Tuesday) \n";

又再次调用ostream的<<定义, 来显示最后一个字符串.

改进之后的版本:

第一个文件

// 第一个文件
// mytime3.h
#ifndef MYTIME3_H_
#define MYTIME3_H_

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;
    
    // 友元函数
    // 直接写了方法的定义, 则实际是个内联函数
    // 方法体里实际调用的是operator*(double n) 这个成员函数
    friend Time operator*(double m, const Time & t)
    {return t * m;}
    // 友元函数重载了<< 运算符
    friend std::ostream & operator<<(std::ostream & os, const Time & t);
    
};

#endif

第二个文件:

// 第二个文件
// mytime3.cpp, Time类的函数的实现文件
#include <iostream>
#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 = sum.minutes % 60;
    // 注意这里, 由于返回类型是Time, 所以可以直接返回sum
    // 如果返回类型是Time & , 则不能返回sum
    // 因为sum是临时变量, 函数执行完毕后就会释放, 因此不能返回sum的引用
    return sum;
}

Time Time::operator-(const Time & t) const
{
    Time diff;
    int tot1, tot2;
    tot1 = t.minutes + t.hours * 60;
    tot2 = minutes + hours * 60;
    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.minutes = totalminutes % 60;
    result.hours = totalminutes / 60;
    return result;
}

// 这个不是成员函数不需要Time::
// 友元函数的实现, 不需要写friend
std::ostream & operator<<(std::ostream & os, const Time & t)
{
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}

第三个文件:

// 第三个文件:
// usetime0.cpp
// compile with mytime3.cpp
#include <iostream>
#include "mytime3.h"

int main()
{
    using std::cout;
    using std::endl;
    
    // 调用两个参数的构造函数
    Time coding(2, 40);
    Time fixing(5, 55);
    
    Time temp;
    
    cout << "coding and fixing : " << endl;
    // 使用的是友元非成员函数 重载<< 
    cout << coding << "; " << fixing << endl;
    
    // 使用的是 operator+()
    temp = coding + fixing;
    // 使用的友元非成员函数 重载<< 
    cout << "coding + fixing : " << temp << endl;
    
    // 使用的是 operator*()
    temp = coding * 1.17;
    cout << "coding * 1.17 : " << temp << endl;
    
    // 使用的是友元非成员函数 重载<< 
    // 还使用了友元非成员函数operator*
    cout << "10.0 * fixing : " << 10.0 * fixing << endl;
    
    return 0;
}

程序运行结果为:

猜你喜欢

转载自blog.csdn.net/c1392851600/article/details/84962933