重载操作符

对类重载操作符,永远是对类的对象自身起作用,而不可能对于类的成员。当调用操作符的时候,编译器会解析调用操作符的对象的类型。  
  fm.myName++;  
  这样实际上是在对MyName调用++操作符,而MyName类型是一个枚举,对枚举重载运算符是必须使用全局定义的(稍后会讲如何全局定义),所以你的 定义完全不可能达到你的期望。  
   
  ++和--运算符各有两种,第一种是前置,第二种是后置。如何区分这两种形式也让C++标准委员会的人伤透了脑筋,最后他们终于想出了一个没有办法,那就 是让后置比前置多一个int参数加以区别,尽管这个int什么也不做,没有任何意义,请看:  
   
  class   A  
  {  
  public:  
  A&   operator++(   void   )   {   m   =   m   +   1;   return   *this;}   //前置  
  const   A&   operator++(   int   );   {   int   n   =   m;   m   =   m   -   1;   return   *this;}   //   后置  
  private:  
  int   m;  
  };  
   
  这里面有几个问题:  
   
  一,因为后置运算符的int参数什么也不做,所以我们没有必要为其定义具名实参,空着就可以了,这样的语法是允许的。  
   
  二,在C++中对于一个int型变量i的操作:  
  (++i)++;   //允许  
  ++(i++);   //不允许  
  因此我们需要模仿内置内型的形为,让前置返回非常量,而后置返回常量。  
   
  三,我们必须返回引用,因为必竟这是基于类自身的形为,而且用户可能会对于你返回的对象做一些其它的操作,比如:  
  A   a;  
  (++a).somefunc();   //   假设somefunc是非const成员函数,并对数据成员做了修改  
  a.show();   //打印出了数据成员  
  如果你返回的不是引用,那么打印出的数据成员可能与你的想象不符  
   
   
  四,前置的概念是先算后用,后置的概念是先用再算,所以代码上有较大区别,但如果重载的是一个模版的话,我们必须要考虑在后置类型中的构造行为是否会与实 例化的类型有冲突,比如用户的类类型有一个private的默认构造……扯的太远了。  
   
   
   
   
  下面讲一下如何定义全局重载操作符,还是用++来说话。比如你有一个类的定义如下:  
  class   B  
  {  
  public:  
  int   m;  
  };  
   
  这也许是你使用的别的类库中的一个类,但你还是想为它重载++,那么你可以这样定义两个全局函数:  
   
  B&   operator++(   B&   lhs   )     //   前置++  
  {  
          lhr.m   =   lhr.m   +   1;  
          return   lhr;  
  }  
   
  const   B&   operator++(   B&   lhs,   int   )     //   后置++  
  {  
          int   n   =   lhs.m;  
          lhs.m   =   lhs.m   +   1;  
          return   lhs;  
  }  
   
  可以看到,全局的运算符比起局部的运算符重载多了一个参数,那就是参数表的头一个,指向需要操作的对象的引用。这样的行为和局部运算符重载其实很类似,重 载的局部运算符实际上的头一个参数也是指向自身的指针,也就是传说中的this指针,只不过被隐藏起来了罢了。

 

==========================

 

一、什么是操作符重载?
一看到重载,很容易就让人联想到成员函数重载,函数重载可以使名称相同的函数具有不同的实际功能,只要赋给这些同名函数不同的参数就可以了,操 作符重载也是基于这一机制的。系统为我们提供了许多操作符,比如“+”,“[ ]”等,这些操作符都有一些默认的功能,而操作符重载机制允许我们给这些操作符赋予不同的功能,并能够按照普通操作符的使用格式来使用自己定义功能的操作符(即重载的操作符)
定义之后,我们就可以按照平常使用操作符的格式来使用我们自己的重载操作符了。
操作符重载一般在类内部定义,就像成员函数一样定义,这叫做类成员重载操作符。当然也可 以在类外定义 ,即非类成员操作符重载。
二、为什么要使用操作符重 载?
举例说明,比如类String,该类有这样一个功能,可以将两个字符串连接成一个字符串,为此,我们可以给类String定义一个成员函数实现 此功能,可以给该函数取一个形象的名字,比如concatenate或append,但是相比较,这两个名字都不如操作符“+=”形象直观。在这种情况 下,我们就可以定义操作符“+=”的重载,来实现此功能。
也就是说,如果要定义一个函数,而这个函数的功能与操作符的功能比较类似时,这个时候我们就可以定义重载操作符,而不使用通常的成员函数定义。 这里所说的操作符重载,指的是与系统定义的操作符重载,而不是说定义两个“ += ”,这两个重载,这一点需要清楚。
但是这四个操作符不能用于重载: :: *   ? :
三、如何声明操作符重载?
同普通函数类似,只不过它的名字包括关键字 operator ,以及紧随其后的一个预 定义操作符 。例如:
String& operator+=(const String&) ;
String& operator+=(const char*) ;
注意:上面的括号表示形式参数,即使操作符重载不需要参数,也应该写上一个空的“( )”,而不是将其省略,这一点其实和普通函数的声明是类似的。其实,声明的唯一区别就是名字不同而已
四、怎样使用操作符重载?
两种操作符重载:类成员操作符重载和非类成员操作符重载。
1、类成员操作符重载
已知类String中声明了两个“==”操作符重载,分别是:
bool operator==(const char*) const;
bool operator==(const String&) const;
其中第一个重载的操作符允许我们比较一个String类对象是否等于一个C风格字符串,第二个允许我们比较两个String类对象是否相等。
示例代码:
#include<String.h>
int main()
{
       String flower;
       If(flower==”lily”) // 正确:调用bool operator==(const char*) const;
       ……
       else
              if(“tulip”==flower) // 错误
              …….
}
关键看一下,为什么第二个重载操作符的使用是错误的?
因为:只有在左操作数是该类类型的对象时,才会考虑使用作为类成员的重载操作符。
因为这里的”tulip”不是String类型对象,所以编译器试图找到一个内置操作符,它可以有一个C风格字符串的左操作数,然而事实上并不 存在这样的操作符,所以编译时产生错误。
疑问:我们可以使用 String类的构造函数将一个C风格字符串,转换成一个String对象,为什么编译器不能做以上转换呢?即
      if(String(“tulip”)==flower);// 这样就是正确的
答:为了效率和正确 性
重载操作符并不要求两个操作数的类型一定相同。可能有这样一个类Text,这个类的构 造函数的参数及其成员重载操作符的参数都与String类一致,如果使编译器能够自动将C风格字符串转换成某个类型的对象,那么编译器首先会检索所有的类 定义,选择能够提供正确构造函数和重载操作符的类进行转换,这无疑会增加程序的编译时间,还有就是类String和类Text均合适,编译器也不知道该将 C风格字符串转换成String还是Text对象了。
 
对于类成员重载操作符 ,隐式的this指针被用作隐式的第一个参数,对于 成员操作符,flower==”lily” 会被编译器重写为: flower.operator==(“lily”);
 
2、非类成员操作符重载
为了解决上面的问题,我们可以考虑使用非类成员操作符代替类成员操作符,这样做的好处是左操作数不必非要是某个类的类型对象 了,对 于需要两个操作数的操作符重载,我们就可以定义两个参数了 。比如:
bool operator==(const String&,const String&);
bool operator==(const String&,const char*);
可以看到,这两个全局重载操作符比成员操作符多了一个参数。
这样定义之后,还是上面的代码,当调用flower==”lily”时,会调用上面的bool operator==(const String&,const char*);。
然而“tulip”==flower会调用哪个操作符重载呢,我们并没有定义bool operator==(const char*,const String&);,我们是不是必须定义这样一个全局操作符重载呢?答案是否定的,因为当一个重载操作符是一个名字空间函数时,对于操作符的第一 个和第二个参数,即等于操作符的左右两个操作数都会考虑转换,就像
int vi=1; double vd=2.0; vi=vi+vd; 会先将vd转换成int型,再做加法一样
这意味着,编译器将解释第二个用法如下:
bool operator==(String(“tulip”),flower)。这样会增加系统转换开销。
因此,如果需要频繁比较C风格字符串和String对象,那么最好定义上面的操作符重载,如果不频繁,我们只需定义下面一个就够了:
bool operator==(const String&,const String&);
 
分析:什么时候定义类成员操作符重载,什么时候定义非类成员操作符重载?
答:(1)如果一个重载操作符是类成员,那么只有当跟它一起使用的左操作数是该类对象时,它才会被调用,如果该操作符的左操作数必须是其他类 型,那么重载操作符必须是非类成员操作符重载。
(2)C++要求,赋值(=),下标([ ]),调用(())和成员访问箭头(->)操作符必须被指定为类成员操作符,否则错误。
 
 
我们只能为类类型或枚举类型的操作数定义重载操作符,我们可以这样实现:把重载操作符声 明为类的成员,或者声明为非类成员重载操作符但同时至少有一个类或者枚举类型的参数(按值传递或按 引用传递)。 这句话的意思,实际是说我们不能修改内置类型的操作符重载或添加操作符重载,比如下面的声明便是错误的:
int operator+(int,int);
这改变了内置类型int的+操作符的功能,所以错误。
 
操作符重载相关知识点:
五、友元(friend)
考虑到类成员操作符重载可以访问类中的私有变量,但是非类成员重载操作符却不能很方便的访问类的私有成员,为了方便起见,我们可以通过使用友元( friend 的方式,方便的访问类的私有成员。
举例:
class String
{
       friend bool operator==(const String&,const String&);
       friend bool operator==(const String&,const char*);
       public:
              //……..
       private:
              //………
}
注意: friend 声明紧跟在类名之后,而不是放在 public private protected 中,因为友元不是授权类的成员,并且该关键字只能出现在类中。
经过上述声明之后,我们的非类成员重载操作符就可以直接方位String类的私有成员了。当然我们也可以不使用友元,而使通过该类的共有成员函 数来间接访问该类的私有成员也是可以的,内联函数inline就不错,效率也不低。
 
由此看来,声明友元( friend )主要是为了方便高效的访问类的私有成员变量。
分析:什么时候应该使用友元:
(1)       某个类不提供公有的访问私有成员的函数。
(2)       使用共有成员函数访问私有成员变量效率比较差 时。
 
友元除了用在非成员的重载操作符外,一个名字空间函数(比如全局函数),另一个在此之前定义的类 的成员函数或者一个完整的类,均可以声明为某个类的成员。
如下声明:
class B;
class A
{
       friend class B;
       public:
              //…….
       Private:
              //……
}
通过上述声明,类A的成员函数可以访问所有的类B的私有成员,同样类B的成员函数可以访问所有类A的私有成员。
 
六、 类类型对象的隐式类型转换
我们知道,系统提供的内置类型有隐式类型转换的功能,实际上我们也可以为自己编写的类提 供隐式类型转换功能。这样,当我们在使用这种类的对象时,如果需要,编译器会自动调用该类的类型转换函数,实现类型转换功能。
C++提供了这样一种机制,通过它,每个类都可以定义一组“可被应用在该类型对象上的转换”
举例:
class SmallInt
{
       public:
              SmallInt(int ival):val(ival){};
              operator int(){return value;} ;// 类型转换操作符 SmallInt->int
       private:
              int value;
}
在上例中,操作符int()是一个转换函数,它定义了一个用户转换,实现在类类型和转换函数中指定的类型之间的转换,本例中,目标类型是 int。
经过上面的定义后,SmallInt对象便可以用在任何可以使用int的地方,例如:
SmallInt si(3);
si+3.1415926;
这样,首先调用SmallInt转换函数,产生int型值3,然后将3变为3.0,和3.1415926相加。
实际上,上面类中定义转换函数还有一个好处就是:
省略了为该类定义重载操作符,尤其是重载操作符参数类型多样时。
1、  转换函数
格式:operator type( ); // 参数必须为空,且 ( ) 不能省略,并且无返回值类型。
说明:type 表 示目标类型,它可以用内置类型,类类型或 typedef 名取代,但不允许表示数组,转换函数必须是类的成员函数,并且不能指定返回类型和参数表。
 
显式的强制类型转换会导致调用转换函数 ,如果被转换值的类型是一个类类 型,它有一个转换函数,并且该转换函数的类型是强制转换所指定的类型,则调用这个类的强制转换函数。
比如:
char* tokName=static_cast<char*>(tok);//tok 表 示对象,它所属的类提供一个到char*的转换函数。
2、  用构造函 数作为转换函数
在一个类的构造函数中,凡是只带有一个参数的构造函数,例如 SmallInt 的构造函数SmallInt(int),都定义了一组隐式转换,把构造函数的参数类型转换成该类类 型。
注意:是将int转换成SmallInt型。和前面说的转换函数功能正好相反。
举例:
void calc(SmallInt);
int  vi;
calc(vi);
这时,编译器就会隐式调用类SmallInt的构造函数SmllInt(int),将vi转换成SmallInt对象,然后再将这个对象传递给 函数calc( )。
可以这样理解:
{
       SmallInt temp=SmallInt(i);
       Calc(temp);
}
这个大括号指出temp这个临时对象的生命周期。
 
注意:编译器不会使用一个显式构造函数(关键字explicit标志)来执行隐式类型转换,但是却可以使用这样的构造函数来进行强制转换(即 static_cast<>)。

转自http://blog.youkuaiyun.com/btooth/archive/2006/07/26/981245.aspx

 

<think>我们正在讨论C++操作符重载的实现方法。根据引用内容,操作符重载允许我们为自定义类型(如类)定义操作符的行为。例如,我们可以为自定义的“Integer”类重载“-”和“~”操作符,或者为“DataList”类重载类型转换操作符重载操作符有两种主要方式: 1. 成员函数形式:操作符作为类的成员函数重载,此时左操作数是当前对象(通过this指针访问),右操作数是函数的参数。 2. 全局函数形式:操作符作为全局函数重载,此时所有操作数都作为函数的参数。 另外,有些操作符(如“=”、“[]”、“()”、“->”)必须作为成员函数重载。 引用中给出了几个例子: [^1]提到:重载操作符的格式为“类 operator+(类 p);”(成员函数形式)。 [^2]中提到:重载操作符有语法规则,并举例了等号、小于号、加号以及cout(需要友元或全局函数)。 [^3]展示了单目操作符(相反数、位反)的重载,分别用成员函数和全局函数(友元)实现。 [^4]展示了一个类型转换操作符重载(operator T*)。 下面我将详细解释操作符重载的实现方法。 步骤1:确定操作符重载的形式(成员函数还是全局函数) - 大多数操作符都可以重载为成员函数或全局函数。 - 当重载为成员函数时,左操作数必须是该类的对象(对于二元操作符),且操作符函数隐含一个this指针作为左操作数。 - 当左操作数不是自定义类的对象(比如是基本类型)时,必须使用全局函数(例如重载<<用于输出,因为左操作数是ostream对象)。 步骤2:编写操作符重载函数 - 成员函数形式:参数个数比操作符的操作数少一个(因为this作为第一个操作数)。 - 全局函数形式:参数个数等于操作符的操作数个数。 步骤3:注意返回类型 - 根据操作符的语义,返回类型可以是任何类型,但通常有约定俗成的规则(例如赋值操作符返回引用,算术操作符返回新对象)。 以二元操作符“+”为例: 成员函数形式: class MyClass { public: MyClass operator+(const MyClass& other) const; }; 全局函数形式: MyClass operator+(const MyClass& left, const MyClass& right); 步骤4:考虑是否需要定义为友元 - 如果全局函数需要访问类的私有成员,则需要在类中声明该全局函数为友元。 步骤5:实现操作符重载 - 在函数体内实现操作符的具体运算逻辑。 示例1:重载二元操作符“+”(成员函数形式) 假设有一个复数类Complex: class Complex { private: double real; double imag; public: Complex(double r, double i) : real(r), imag(i) {} // 成员函数形式重载+ Complex operator+(const Complex& other) const { return Complex(real + other.real, imag + other.imag); } }; 使用:Complex c1(1,2), c2(3,4); Complex c3 = c1 + c2; 示例2:重载输出操作符“<<”(全局函数形式,需要友元) class Complex { // ... 同上 friend std::ostream& operator<<(std::ostream& os, const Complex& c); }; // 全局函数定义 std::ostream& operator<<(std::ostream& os, const Complex& c) { os << "(" << c.real << ", " << c.imag << ")"; return os; } 使用:Complex c(1,2); cout << c; 示例3:重载单目操作符“-”(取负,成员函数形式) class Complex { public: Complex operator-() const { // 无参数,因为操作数就是当前对象 return Complex(-real, -imag); } }; 使用:Complex c(1,2); Complex c2 = -c; 示例4:重载类型转换操作符(成员函数形式) class DataList { // 如引用[4]中,重载类型转换到T* operator T* () const { return m_pData; } }; 使用:DataList<int> l; int* lst = l; // 调用operator int*() 注意事项: - 不能重载操作符:".", ".*", "::", "?:", "sizeof", "typeid"。 - 重载操作符时,不能改变操作符的优先级和结合性,也不能改变操作符的操作数个数。 - 重载操作符应当保持操作符的原始语义(例如“+”应该是加法,而不是减法)。 引用[2]中还提到重载等号(赋值操作符)和小于号(用于比较)等常见操作符。 赋值操作符(=)必须作为成员函数重载,通常返回引用以便连续赋值: class MyClass { public: MyClass& operator=(const MyClass& other) { if (this != &other) { // 复制内容 } return *this; } }; 重载小于号(<)通常用于比较,可以用于排序(成员函数或全局函数): class MyClass { public: bool operator<(const MyClass& other) const { // 比较逻辑,返回true或false } }; 最后,引用[3]展示了单目操作符的自增减(++和--)的重载,但注意有前缀和后缀之分。 - 前缀:operator++() (无参数) - 后缀:operator++(int) (有一个int型哑参数) 示例:前缀自增(成员函数形式) class Integer { public: Integer& operator++() { // 返回引用,因为修改了当前对象 ++m_i; return *this; } // 后缀自增,返回自增前的值(副本) Integer operator++(int) { Integer temp = *this; ++m_i; return temp; } }; 总结:C++操作符重载允许自定义类型使用与内置类型一样的操作符语法,提高代码可读性。实现时需要注意选择成员函数还是全局函数,并遵循操作符的语义。 相关问题: 1. 如何重载赋值操作符(=)以实现深拷贝? 2. 重载输入操作符(>>)的方法是什么? 3. 如何区分前缀和后缀的自增自减操作符重载? 4. 操作符重载在什么情况下应该定义为友元函数? 注意:引用内容中给出的代码示例也是很好的参考。 </think>### C++操作符重载的实现方法 操作符重载允许为自定义类型(如类)定义操作符的行为,使其支持类似内置类型的运算(如`+`、`=`、`<<`等)。实现方式有两种核心形式: --- #### 一、成员函数形式(常用) 操作符作为类的成员函数,左操作数是当前对象(通过`this`指针隐式访问),右操作数为参数。 **语法**: ```cpp ReturnType operator@(const ClassName& right_operand); ``` **示例**:重载`+`操作符 ```cpp class Vector { public: int x, y; Vector(int x, int y) : x(x), y(y) {} // 成员函数形式重载+ Vector operator+(const Vector& other) const { return Vector(x + other.x, y + other.y); } }; // 使用 Vector v1(1, 2), v2(3, 4); Vector v3 = v1 + v2; // 等价于 v1.operator+(v2) ``` > **关键点**: > - 左操作数隐含为`this`,参数仅为右操作数[^1] > - 必须为`const`成员函数(若不修改当前对象) > - 适用于大多数二元操作符(如`+`、`-`、`=`)和单目操作符(如`++`、`--`)[^3] --- #### 二、全局函数形式(需友元支持) 当左操作数非当前类对象时(如重载`<<`输出流),需定义为全局函数。若需访问私有成员,需声明为类的`friend`。 **语法**: ```cpp ReturnType operator@(const LeftType& left, const RightType& right); ``` **示例**:重载`<<`输出流 ```cpp class Point { private: int x, y; public: Point(int x, int y) : x(x), y(y) {} friend std::ostream& operator<<(std::ostream& os, const Point& p); // 声明友元 }; // 全局函数定义 std::ostream& operator<<(std::ostream& os, const Point& p) { os << "(" << p.x << ", " << p.y << ")"; return os; } // 使用 Point p(5, 10); std::cout << p; // 输出:(5, 10) ``` > **关键点**: > - 必须为友元函数以访问私有成员[^2] > - 适用于流操作符(`<<`、`>>`)或对称操作符(如`1 + obj`) --- #### 三、特殊操作符重载规则 1. **赋值操作符`=`**: - 必须为成员函数(非`static`) - 需处理自赋值并返回`*this`引用 ```cpp Vector& operator=(const Vector& other) { if (this != &other) { // 防止自赋值 x = other.x; y = other.y; } return *this; } ``` 2. **类型转换操作符**: - 隐式转换到其他类型 ```cpp class DataList { public: operator T*() const { return m_pData; } // 转换为T*指针[^4] }; ``` 3. **自增/自减`++`/`--`**: - 前缀形式:`ReturnType operator++()` - 后缀形式:`ReturnType operator++(int)`(哑元参数`int`区分)[^3] --- #### 四、注意事项 1. **不可重载操作符**: `::`(作用域解析)、`.`(成员访问)、`.*`(成员指针访问)、`?:`(三元操作符)。 2. **保持语义一致性**: - 重载`+`不应实现减法逻辑 - 避免过度使用(如重载`&&`可能影响短路求值) 3. **返回类型选择**: - 算术操作符返回新对象(如`Vector`) - 赋值类操作符返回引用(如`Vector&`) --- ### 相关问题 1. 如何重载下标操作符`[]`以实现自定义容器? 2. 重载`==`和`<`操作符对STL容器(如`std::set`)有何影响? 3. 什么情况下操作符重载必须定义为友元函数? 4. 如何避免隐式类型转换导致的二义性问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值