写点什么

C++ 友元与运算符重载那些事

作者:王玉川
  • 2023-02-10
    上海
  • 本文字数:2140 字

    阅读完需:约 7 分钟

之前用 C++写代码的时候,从没仔细地思考过运算符重载的细节。直到最近复习的时候,才发现这一块知识是自己以前没注意过的。因此记录并分享出来。


友元函数,friend,该函数与类的成员函数具有相同的访问权限。朋友嘛,所以不管类里面的变量是 public、protected 还是 private,都是可以随便访问的。


运算符重载,operator,让用户自定义的类型支持运算符操作,比如+、-、*、/、=、[]、>>、<<、()等等。使得代码看起来更加自然。比如类里面经常使用的赋值操作,就是重载了=运算符。


那么它们俩有啥关系呢?


我们编个美好的故事,一步步的用代码来说明。


首先,王小二写了个 C++的类,代表每个员工的年终奖:


#include <iostream>
class Bonus{public: Bonus(int val = 0) { count = val; } ~Bonus() {}
void Change(int val) { count += val; }
int GetCount() const { return count; }
private: int count;};
int main(){ Bonus a; a.Change(1000); int v = a.GetCount(); std::cout << v << std::endl;}
复制代码


成员函数已经可以实现了奖金的变动。但我们往往习惯直接用四则运算,来操作数值相关的事情。


比如说,这个时候,老板需要王小二的代码可以把每个人的奖金加起来,看看到底花了多少钱。于是王小二加了几行代码:


int main(){    Bonus a;    a.Change(1000);    int v = a.GetCount();    std::cout << v << std::endl;
Bonus b(500); Bonus total = a + b; // Error! std::cout << total.GetCount() << std::endl;}
复制代码


不出意外的,a + b 报错了。因为编译器不知道怎么去处理这个 Bonus 类型的加法。


这个时候,我们需要重载第一个运算符:


    Bonus operator+(const Bonus& b) const    {        Bonus ret(count + b.count);         return ret;     }
复制代码


通过这个运算符的重载,a + b 将被翻译成:a.operator+(b),计算和,返回一个新的 Bonus 实例。


类似加减乘除这样的二元运算符,运算符左侧的 a 是调用方,运算符右侧的 b 是作为参数被传递进去。


实现了这个之后,完成了老板的任务,老板很开心:小伙子干的不错,给你的奖金翻番吧:


    Bonus c = a * 2.0; // Error!    std::cout << c.GetCount() << std::endl;
复制代码


然后,王小二发现又报错了。原来乘法运算符还没重载。趁老板没注意,赶紧加上去:


    Bonus operator*(double times) const    {        Bonus ret(count * times);         return ret;     }
复制代码


阿弥陀佛,这回终于可以做乘法了。


老板欣赏的看着王小二,他决定自己亲自改代码,把王小二的奖金从 2 被变成 3 倍:


    Bonus d = 3.0 * a;     std::cout << d.GetCount() << std::endl;
复制代码


然后,又错了……


难道乘法分配律在这里失效了?a * 2 可以,但 3 * a 不可以?王小二同学一脸问号。


按照前面的描述:“二元运算符,运算符左侧是调用方,运算符右侧是作为参数被传递进去。” 现在左侧是 double,难道王小二需要去改 double 类型的代码,让它认识 Bonus 这个类型吗……


可以使用非 Bonus 类成员函数的运算符重载,王小二急中生智,非成员函数不需要依赖对象进行调用。于是,在 Bonus 类的外面,王小二加了这个函数:


Bonus operator*(double times, const Bonus& b) {    Bonus ret(times * b.count); // Error!    return ret; }
复制代码


但是,编译器继续报错。因为 count 是类的私有成员,非成员函数不能访问。咋办?


这时,我们的另一个主角:友元终于可以登场了:


通过把这个非成员函数声明为 Bonus 的朋友,该函数就可以访问 Bonus 的内部数据了。


在 Bonus 类的声明部分(头文件),加上这么一行:


friend Bonus operator*(double times, const Bonus& b);
复制代码


终于圆满的编译、运行通过了。


完整的代码如下:


#include <iostream>
class Bonus{public: Bonus(int val = 0) { count = val; } ~Bonus() {}
void Change(int val) { count += val; }
int GetCount() const { return count; }
Bonus operator+(const Bonus& b) const { Bonus ret(count + b.count); return ret; }
Bonus operator*(double times) const { Bonus ret(count * times); return ret; }
friend Bonus operator*(double times, const Bonus& b);
private: int count;};
Bonus operator*(double times, const Bonus& b) { Bonus ret(times * b.count); return ret; }
int main(){ Bonus a; a.Change(1000); int v = a.GetCount(); std::cout << v << std::endl;
Bonus b(500); Bonus total = a + b; std::cout << total.GetCount() << std::endl;
Bonus c = a * 2.0; std::cout << c.GetCount() << std::endl;
Bonus d = 3.0 * a; std::cout << d.GetCount() << std::endl;}
复制代码


所以,在为类 Bonus 重载运算符时,如果第一项操作数是 Bonus 类,那么把运算符定义为普通的类成员函数即可;如果第一项操作数并非 Bonus 类,我们则需要使用友元函数来翻转操作数的顺序


至此,王小二同学终于圆满的完成了任务,并且拿到了 3 倍的奖金!

用户头像

王玉川

关注

https://yuchuanwang.github.io/ 2018-11-13 加入

https://www.linkedin.com/in/yuchuan-wang/

评论

发布
暂无评论
C++ 友元与运算符重载那些事_c++_王玉川_InfoQ写作社区