C++运算符重载为成员函数和非成员函数的限制

我们知道,基本数据类型的定义包括:

① 值域或需要的内存空间;

② 位模式与编码方案;

③ 可以适用的操作(能够使用的运算符);

例如double类型,使用8个字节的内存空间,使用IEEE754的编译方案(1个符号位,11个指数位使用移码,52个小数位),double可以使用+-*/等运算符,但不能使用模运算符%。

一个操作符的运算可以理解为一个函数形式:

a+b;operator+(a,b);

自然,如果类类型要使用运算符,需要明确定义操作符操作数据成员的逻辑(也就是重载操作符),否则,会出现编译错误。

需要注意的是,运算符(operator)重载要求必须至少有一个类类型参数(或两个类类型参数),如果参数一个都不是类类型,则也就没有必要重载。对于第一个操作数(operand,出现在操作符的左边)的类型也是有限制的。

例如运算符[]要求第一个操作数只能是类对象,所以需要重载[]为成员函数(其第一个参数是隐含的this指针,指向类对象)。

运算符<<要求第一个操作数为std::ostream类型对象,所以不能重载为成员函数,可以重载为友元函数。

1 重载双目运算符[]为成员函数

双目运算符[]限制了第一个参数必须为类类型,所以要重载为成员函数(第一个参数为类类型指针this)。

#include class IntList{private: int m_list[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // give this class some initial state for this examplepublic: int& operator[] (int index); int operator[] (int index) const; // could also return const int& // if returning a non-fundamental type, use as a const argument};int& IntList::operator[] (int index) // for non-const objects: can be used for assignment{ return m_list[index];}int IntList::operator[] (int index) const // for const objects: can only be used for access{ return m_list[index];}int main(){ IntList list{}; list[2] = 3; // okay: calls non-const version of operator[] std::cout << list[2] << ''; const IntList clist{}; clist[2] = 3; // compile error: calls const version of operator[], which returns by value. Cannot assign to this because it is an rvalue. std::cout << clist[2] << ''; return 0;}

2 重载双目运算符<<为友元函数

双目运算符<<的第一个参数必须为std::ostream类型对象,为避免第一个对象为this指针,所以要避免重载为成员函数,可以重载为友元函数。

#include class Point{private: double m_x {}, m_y {}, m_z {};public: Point(double x=0.0, double y=0.0, double z=0.0) : m_x { x }, m_y { y }, m_z { z } { } friend std::ostream& operator<< (std::ostream &out, const Point &point);};std::ostream& operator<< (std::ostream &out, const Point &point){ // Since operator<< is a friend of the Point class, we can access Point's members directly. out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")"; return out;}int main(){ Point point1 { 2.0, 3.0, 4.0 }; std::cout << point1; return 0;}

3 重载单目运算符++、–为成员函数

class Digit{private: int m_digit;public: Digit(int digit=0) : m_digit{digit} { } Digit& operator++(); // prefix has no parameter Digit& operator–(); // prefix has no parameter Digit operator++(int); // postfix has an int parameter Digit operator–(int); // postfix has an int parameter friend std::ostream& operator<< (std::ostream& out, const Digit& d);};// No parameter means this is prefix operator++Digit& Digit::operator++(){ // If our number is already at 9, wrap around to 0 if (m_digit == 9) m_digit = 0; // otherwise just increment to next number else ++m_digit; return *this;}// No parameter means this is prefix operator–Digit& Digit::operator–(){ // If our number is already at 0, wrap around to 9 if (m_digit == 0) m_digit = 9; // otherwise just decrement to next number else –m_digit; return *this;}// int parameter means this is postfix operator++Digit Digit::operator++(int){ // Create a temporary variable with our current digit Digit temp{*this}; // Use prefix operator to increment this digit ++(*this); // apply operator // return temporary result return temp; // return saved state}// int parameter means this is postfix operator–Digit Digit::operator–(int){ // Create a temporary variable with our current digit Digit temp{*this}; // Use prefix operator to decrement this digit –(*this); // apply operator // return temporary result return temp; // return saved state}std::ostream& operator<< (std::ostream& out, const Digit& d){out << d.m_digit;return out;}int main(){ Digit digit(5); std::cout << digit; std::cout << ++digit; // calls Digit::operator++(); std::cout << digit++; // calls Digit::operator++(int); std::cout << digit; std::cout << –digit; // calls Digit::operator–(); std::cout << digit–; // calls Digit::operator–(int); std::cout << digit; return 0;}

4 重载函数调用符()为成员函数

#include class Accumulator{private: int m_counter{ 0 };public: int operator() (int i) { return (m_counter += i); }};int main(){ Accumulator acc{}; std::cout << acc(10) << ''; // prints 10 std::cout << acc(20) << ''; // prints 30 return 0;}

You may wonder why we couldn’t do the same thing with a normal function and a static local variable to preserve data between function calls. We could, but because functions only have one global instance, we’d be limited to using it for one thing at a time. With functors, we can instantiate as many separate functor objects as we need and use them all simultaneously.

您可能想知道为什么我们不能用普通函数和静态局部变量来保存函数调用之间的数据。我们可以,但因为函数只有一个全局实例,所以我们只能一次使用一个实例。使用函数对象,我们可以根据需要实例化任意多个单独的函数对象,并同时使用它们。

Operator() is sometimes overloaded with two parameters to index multidimensional arrays, or to retrieve a subset of a one dimensional array (with the two parameters defining the subset to return). Anything else is probably better written as a member function with a more descriptive name.

操作符()有时会重载两个参数来索引多维数组,或检索一维数组的子集(两个参数定义要返回的子集)。其他任何内容都最好用更具描述性的名称作为成员函数编写。

5 重载赋值运算符=为成员函数

MyString& MyString::operator= (const MyString& str){// self-assignment checkif (this == &str)return *this;// if data exists in the current string, delete itif (m_data) delete[] m_data;m_length = str.m_length;// copy the data from str to the implicit objectm_data = new char[str.m_length];for (int i { 0 }; i < str.m_length; ++i)m_data[i] = str.m_data[i];// return the existing object so we can chain this operatorreturn *this;}

6 When to use a normal, friend, or member function overload

何时使用普通、友元或成员函数重载

In most cases, the language leaves it up to you to determine whether you want to use the normal/friend or member function version of the overload. However, one of the two is usually a better choice than the other.

在大多数情况下,该语言将由您决定是否要使用重载的普通/友元或成员函数版本。然而,两者中的一个通常比另一个更好。

When dealing with binary operators that don’t modify the left operand (e.g. operator+), the normal or friend function version is typically preferred, because it works for all parameter types (even when the left operand isn’t a class object, or is a class that is not modifiable). The normal or friend function version has the added benefit of “symmetry”, as all operands become explicit parameters (instead of the left operand becoming *this and the right operand becoming an explicit parameter).

处理不修改左操作数的二元运算符(例如运算符+)时,通常首选普通或友元函数版本,因为它适用于所有参数类型(即使左操作数不是类对象,或是不可修改的类)。普通或友元函数版本具有“对称”的额外好处,因为所有操作数都成为显式参数(而不是左操作数变成*this,右操作数变成显式参数)。

When dealing with binary operators that do modify the left operand (e.g. operator+=), the member function version is typically preferred. In these cases, the leftmost operand will always be a class type, and having the object being modified become the one pointed to by *this is natural. Because the rightmost operand becomes an explicit parameter, there’s no confusion over who is getting modified and who is getting evaluated.

处理确实修改左操作数的二进制运算符(例如运算符+=)时,通常首选成员函数版本。在这些情况下,最左边的操作数将始终是类类型,并且将要修改的对象变为*所指的对象是很自然的。因为最右边的操作数成为显式参数,所以对于谁要修改谁来求值没有任何混淆。

Unary operators are usually overloaded as member functions as well, since the member version has no parameters.

一元运算符通常也作为成员函数重载,因为成员版本没有参数。

The following rules of thumb can help you determine which form is best for a given situation:

以下经验法则可以帮助您确定哪种形式最适合特定情况:

If you’re overloading assignment (=), subscript ([]), function call (()), or member selection (->), do so as a member function.

如果要重载赋值(=)、下标([])、函数调用(())或成员选择(->),请作为成员函数进行重载。

If you’re overloading a unary operator, do so as a member function.

如果要重载一元运算符,请作为成员函数进行重载。

If you’re overloading a binary operator that does not modify its left operand (e.g. operator+), do so as a normal function (preferred) or friend function.

如果要重载不修改其左操作数的二元运算符(例如运算符+),请将其作为普通函数(首选)或友元函数进行重载。

If you’re overloading a binary operator that modifies its left operand, but you can’t add members to the class definition of the left operand (e.g. operator<<, which has a left operand of type ostream), do so as a normal function (preferred) or friend function.

如果要重载修改其左操作数的二元运算符,但不能将成员添加到左操作数的类定义中(例如运算符<<,其左操作数类型为ostream),请将其作为普通函数(首选)或友元函数。

If you’re overloading a binary operator that modifies its left operand (e.g. operator+=), and you can modify the definition of the left operand, do so as a member function.

如果要重载修改其左操作数的二元运算符(例如运算符+=),并且可以修改左操作数的定义,请作为成员函数进行修改。

-End-

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
用户投稿
上一篇 2022年6月16日 18:17
下一篇 2022年6月16日 18:17

相关推荐

  • 存储过程语法(sql server存储过程语法)

    今天小编给各位分享存储过程语法的知识,其中也会对sql server存储过程语法进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧! oracle存储过程基本语法…

    2022年11月26日
  • 淘宝直播开通后带货链接怎么做(淘宝直播需要开通淘宝店铺吗)

    直播带货无论是对于商家来说还是主播收益都是非常可观的,所以不少平台都有直播带货功能,一些小伙伴也想加入淘宝直播,那么淘宝直播开通后带货链接怎么做?下面小编为大家带来淘宝直播开通后带…

    2022年11月24日
  • 锐龙97900x参数规格跑分评测 锐龙97900x属于什么档次

    锐龙9 7900X是锐龙7000系列处理器中性能顶尖的型号之一,它采用了这一代标配的zen4架构和5nm制程工艺,那么它具体的参数跑分如何,在电脑上世纪发挥怎么样呢,下面就来看看锐…

    2022年11月24日
  • 1千克等于多少磅

    克,此定义在1958年被美国以及其他英联邦会员国承认换算回来,一千克等于262磅,一磅等于037千克英国在1963年开始,依据度量衡法案的规;1 磅=16 盎司=04536 千克 …

    2022年11月24日
  • vivox90pro配置参数跑分详情 vivox90pro是什么屏幕

    vivox90pro参数配置详情怎么样?vivo手机在昨晚举行了X90系列发布会,有普通款、pro款以及pro+款三个版本,那么vivox90pro具体配置怎么样呢?下面就让小编为…

    2022年11月23日
  • vivox90参数配置详情 vivox90处理器功耗跑分介绍

    vivox90参数配置详情怎么样?vivo手机在昨晚举行了X90系列发布会,有普通款、pro款以及pro+款三个版本,那么vivox90具体配置怎么样呢?下面就让小编为大家介绍一下…

    2022年11月23日
  • 快手限流多久能解除(快手限流什么意思)

    我相信很多人都看中了快手平台的商机,都争先恐后地想要抢占机会,可一些人刚刚作出一点成绩,就被降权了,自己也不知道什么原因。所以今天就来聊聊快手账号降权操作分享,赶快来看看避免违规!…

    2022年11月23日
  • Win11 22H2再出新问题Bug:无法弹出USB设备

    作为Windows 11的首次大更新,在Win11 22H2发布后并没有带来预想的场景,各种问题频现成为了一种常态。 近日有消息称,Win11 22H2存在一个占用冲突Bug,当用…

    2022年11月22日
  • 美团月付300小额取现?美团月付取现300不见了

    很多上班族每天都在使用美团点外卖,你知道美团现在推出了一款类似花呗的产品吗?可以在美团消费的时候先消费后还款,叫做美团月付,是美团推出的一款消费型产品,不能直接提现到银行卡,只能用…

    2022年11月21日
  • 京东店铺类型有哪些京东入驻有什么资质要求

    今天的互联网发展迅速,让传统企业有了更多选择,但也同样也对刚触网的商家增添了许多迷茫,近日知舟电商就收到很多商家朋友询问京东入驻相关问题,今天知舟君就给大家分享下。 一.京东入驻准…

    2022年11月18日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息