重载操作符

转自VC共享乐园

前言

  多态性是面向对象程序设计的重要特征之一。它与前面讲过的封装性和继承性构成了面向对象程序设计的三大特征。这三大特征是相互关联的。封装性是基础,继承性是关键,多态性是补充,而多态又必须存在于继承的环境之中。

  所谓多态性是指发出同样的消息被不同类型的对象接收时导致完全不同的行为。这里所说的消息主要是指对类的成员函数的调用,而不同的行为是指不同的实现。利用多态性,用户只需发送一般形式的消息,而将所有的实现留给接收消息的对象。对象根据所接收到的消息而做出相应的动作(即操作)。

  函数重载和运算符重载是简单一类多态性。函数重载的概念及用法在《函数重载》一讲中已讨论过了,这里只作简单的补充,我们重点讨论的是运算符的重载。

  所谓函数重载简单地说就是赋给同一个函数名多个含义。具体地讲,C++中允许在相同的作用域内以相同的名字定义几个不同实现的函数,可以是成员函数,也可以是非成员函数。但是,定义这种重载函数时要求函数的参数或者至少有一个类型不同,或者个数不同。而对于返回值的类型没有要求,可以相同,也可以不同。那种参数个数和类型都相同,仅仅返回值不同的重载函数是非法的。因为编译程序在选择相同名字的重载函数时仅考虑函数表,这就是说要靠函数的参数表中,参数个数或参数类型的差异进行选择。 由此可以看出,重载函数的意义在于它可以用相同的名字访问一组相互关联的函数,由编译程序来进行选择,因而这将有助于解决程序复杂性问题。如:在定义类时,构造函数重载给初始化带来了多种方式,为用户提供更大的灵活性。

  下面我们重点讨论运算符重载。

  运算符重载就是赋予已有的运算符多重含义。C++中通过重新定义运算符,使它能够用于特定类的对象执行特定的功能,这便增强了C++语言的扩充能力。

  运算符重载的几个问题

  1. 运算符重载的作用是什么?

  它允许你为类的用户提供一个直觉的接口。

  运算符重载允许C/C++的运算符在用户定义类型(类)上拥有一个用户定义的意义。重载的运算符是函数调用的语法修饰:

class Fred
{
public:
// ...
};

#if 0
// 没有算符重载:
Fred add(Fred, Fred);
Fred mul(Fred, Fred);

Fred f(Fred a, Fred b, Fred c)
{
return add(add(mul(a,b), mul(b,c)), mul(c,a)); // 哈哈,多可笑...
}
#else
// 有算符重载:
Fred operator+ (Fred, Fred);
Fred operator* (Fred, Fred);

Fred f(Fred a, Fred b, Fred c)
{
return a*b + b*c + c*a;
}
#endif 

  2. 算符重载的好处是什么?

  通过重载类上的标准算符,你可以发掘类的用户的直觉。使得用户程序所用的语言是面向问题的,而不是面向机器的。

  最终目标是降低学习曲线并减少错误率。

  3. 哪些运算符可以用作重载?

  几乎所有的运算符都可用作重载。具体包含:

  算术运算符:+,-,*,/,%,++,--;
  位操作运算符:&,|,~,^,<<,>>
  逻辑运算符:!,&&,||;
  比较运算符:<,>,>=,<=,==,!=;
  赋值运算符:=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=;
  其他运算符:[],(),->,,(逗号运算符),new,delete,new[],delete[],->*。

  下列运算符不允许重载:

  .,.*,::,?:

  4. 运算符重载后,优先级和结合性怎么办?

  用户重载新定义运算符,不改变原运算符的优先级和结合性。这就是说,对运算符重载不改变运算符的优先级和结合性,并且运算符重载后,也不改变运算符的语法结构,即单目运算符只能重载为单目运算符,双目运算符只能重载双目运算符。

  5. 编译程序如何选用哪一个运算符函数?

  运算符重载实际是一个函数,所以运算符的重载实际上是函数的重载。编译程序对运算符重载的选择,遵循着函数重载的选择原则。当遇到不很明显的运算时,编译程序将去寻找参数相匹配的运算符函数。

  6. 重载运算符有哪些限制?

  (1) 不可臆造新的运算符。必须把重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中。

  (2) 重载运算符坚持4个“不能改变”。

  ·不能改变运算符操作数的个数;
  ·不能改变运算符原有的优先级;
  ·不能改变运算符原有的结合性;
  ·不能改变运算符原有的语法结构。

  7. 运算符重载时必须遵循哪些原则?

  运算符重载可以使程序更加简洁,使表达式更加直观,增加可读性。但是,运算符重载使用不宜过多,否则会带来一定的麻烦。

  使用重载运算符时应遵循如下原则:

  (1) 重载运算符含义必须清楚。

  (2) 重载运算符不能有二义性。 
 

运算符重载函数的两种形式

  运算符重载的函数一般地采用如下两种形式:成员函数形式和友元函数形式。这两种形式都可访问类中的私有成员。

  1. 重载为类的成员函数

  这里先举一个关于给复数运算重载复数的四则运算符的例子。复数由实部和虚部构造,可以定义一个复数类,然后再在类中重载复数四则运算的运算符。先看以下源代码:

#include <iostream.h>

class complex
{
public:
complex() { real=imag=0; }
complex(double r, double i)
{
real = r, imag = i;
}
complex operator +(const complex &c);
complex operator -(const complex &c);
complex operator *(const complex &c);
complex operator /(const complex &c);
friend void print(const complex &c);
private:
double real, imag;
};

inline complex complex::operator +(const complex &c)
{
return complex(real + c.real, imag + c.imag);
}

inline complex complex::operator -(const complex &c)
{
return complex(real - c.real, imag - c.imag);
}

inline complex complex::operator *(const complex &c)
{
return complex(real * c.real - imag * c.imag, real * c.imag + imag * c.real);
}

inline complex complex::operator /(const complex &c)
{
return complex((real * c.real + imag + c.imag) / (c.real * c.real + c.imag * c.imag),
(imag * c.real - real * c.imag) / (c.real * c.real + c.imag * c.imag));
}

void print(const complex &c)
{
if(c.imag<0)
cout<<c.real<<c.imag<<'i';
else
cout<<c.real<<'+'<<c.imag<<'i';
}

void main()
{
complex c1(2.0, 3.0), c2(4.0, -2.0), c3;
c3 = c1 + c2;
cout<<"/nc1+c2=";
print(c3);
c3 = c1 - c2;
cout<<"/nc1-c2=";
print(c3);
c3 = c1 * c2;
cout<<"/nc1*c2=";
print(c3);
c3 = c1 / c2;
cout<<"/nc1/c2=";
print(c3);
c3 = (c1+c2) * (c1-c2) * c2/c1;
cout<<"/n(c1+c2)*(c1-c2)*c2/c1=";
print(c3);
cout<<endl;


  该程序的运行结果为:

c1+c2=6+1i
c1-c2=-2+5i
c1*c2=14+8i
c1/c2=0.45+0.8i
(c1+c2)*(c1-c2)*c2/c1=9.61538+25.2308i 

  在程序中,类complex定义了4个成员函数作为运算符重载函数。将运算符重载函数说明为类的成员函数格式如下:

  <类名> operator <运算符>(<参数表>)

  其中,operator是定义运算符重载函数的关键字。

  程序中出现的表达式:

  c1+c2

  编译程序将给解释为:

  c1.operator+(c2)

  其中,c1和c2是complex类的对象。operator+()是运算+的重载函数。

  该运算符重载函数仅有一个参数c2。可见,当重载为成员函数时,双目运算符仅有一个参数。对单目运算符,重载为成员函数时,不能再显式说明参数。重载为成员函数时,总时隐含了一个参数,该参数是this指针。this指针是指向调用该成员函数对象的指针。

  2. 重载为友元函数

  运算符重载函数还可以为友元函数。当重载友元函数时,将没有隐含的参数this指针。这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。但是,有些运行符不能重载为友元函数,它们是:=,(),[]和->。

  重载为友元函数的运算符重载函数的定义格式如下:

  friend <类型说明符> operator <运算符>(<参数表>)
  {……}

  下面用友元函数代码成员函数,重载编写上述的例子,程序如下:

#include <iostream.h>

class complex
{
public:
complex() { real=imag=0; }
complex(double r, double i)
{
real = r, imag = i;
}
friend complex operator +(const complex &c1, const complex &c2);
friend complex operator -(const complex &c1, const complex &c2);
friend complex operator *(const complex &c1, const complex &c2);
friend complex operator /(const complex &c1, const complex &c2);
friend
void print(const complex &c);
private:
double real, imag;
};

complex operator +(const complex &c1, const complex &c2)
{
return complex(c1.real + c2.real, c1.imag + c2.imag);
}

complex operator -(const complex &c1, const complex &c2)
{
return complex(c1.real - c2.real, c1.imag - c2.imag);
}

complex operator *(const complex &c1, const complex &c2)
{
return complex(c1.real * c2.real - c1.imag * c2.imag, c1.real * c2.imag + c1.imag * c2.real);
}

complex operator /(const complex &c1, const complex &c2)
{
return complex((c1.real * c2.real + c1.imag + c2.imag) / (c2.real * c2.real + c2.imag * c2.imag),
(c1.imag * c2.real - c1.real * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag));
}

void print(const complex &c)
{
if(c.imag<0)
cout<<c.real<<c.imag<<'i';
else
cout<<c.real<<'+'<<c.imag<<'i';
}

void main()
{
complex c1(2.0, 3.0), c2(4.0, -2.0), c3;
c3 = c1 + c2;
cout<<"/nc1+c2=";
print(c3);
c3 = c1 - c2;
cout<<"/nc1-c2=";
print(c3);
c3 = c1 * c2;
cout<<"/nc1*c2=";
print(c3);
c3 = c1 / c2;
cout<<"/nc1/c2=";
print(c3);
c3 = (c1+c2) * (c1-c2) * c2/c1;
cout<<"/n(c1+c2)*(c1-c2)*c2/c1=";
print(c3);
cout<<endl;


  该程序的运行结果与上例相同。前面已讲过,对又目运算符,重载为成员函数时,仅一个参数,另一个被隐含;重载为友元函数时,有两个参数,没有隐含参数。因此,程序中出现的 c1+c2

  编译程序解释为:

  operator+(c1, c2)

  调用如下函数,进行求值,

  complex operator +(const coplex &c1, const complex &c2) 
 3. 两种重载形式的比较

  一般说来,单目运算符最好被重载为成员;对双目运算符最好被重载为友元函数,双目运算符重载为友元函数比重载为成员函数更方便此,但是,有的双目运算符还是重载为成员函数为好,例如,赋值运算符。因为,它如果被重载为友元函数,将会出现与赋值语义不一致的地方。 其他运算符的重载举例

  1).下标运算符重载

  由于C语言的数组中并没有保存其大小,因此,不能对数组元素进行存取范围的检查,无法保证给数组动态赋值不会越界。利用C++的类可以定义一种更安全、功能强的数组类型。为此,为该类定义重载运算符[]。

  下面先看看一个例子:

#include <iostream.h>

class CharArray
{
public:
CharArray(int l)
{
Length = l;
Buff = new char[Length];
}
~CharArray() { delete Buff; }
int GetLength() { return Length; }
char & operator [](int i);
private:
int Length;
char * Buff;
};

char & CharArray::operator [](int i)
{
static char ch = 0;
if(i<Length&&i>=0)
return Buff[i];
else
{
cout<<"/nIndex out of range.";
return ch;
}
}

void main()
{
int cnt;
CharArray string1(6);
char * string2 = "string";
for(cnt=0; cnt<8; cnt++)
string1[cnt] = string2[cnt];
cout<<"/n";
for(cnt=0; cnt<8; cnt++)
cout<<string1[cnt];
cout<<"/n";
cout<<string1.GetLength()<<endl;


  该数组类的优点如下:

  (1) 其大小不心是一个常量。
  (2) 运行时动态指定大小可以不用运算符new和delete。
  (3) 当使用该类数组作函数参数时,不心分别传递数组变量本身及其大小,因为该对象中已经保存大小。

  在重载下标运算符函数时应该注意:

  (1) 该函数只能带一个参数,不可带多个参数。
  (2) 不得重载为友元函数,必须是非static类的成员函数。 2). 重载增1减1运算符

  增1减1运算符是单目运算符。它们又有前缀和后缀运算两种。为了区分这两种运算,将后缀运算视为又目运算符。表达式

  obj++或obj--

  被看作为:

  obj++0或obj--0

  下面举一例子说明重载增1减1运算符的应用。

#include <iostream.h>

class counter
{
public:
counter() { v=0; }
counter operator ++();
counter operator ++(int );
void print() { cout<<v<<endl; }
private:
unsigned v;
};

counter counter::operator ++()
{
v++;
return *this;
}

counter counter::operator ++(int)
{
counter t;
t.v = v++;
return t;
}

void main()
{
counter c;
for(int i=0; i<8; i++)
c++;
c.print();
for(i=0; i<8; i++)
++c;
c.print();


  3). 重载函数调用运算符

  可以将函数调用运算符()看成是下标运算[]的扩展。函数调用运算符可以带0个至多个参数。下面通过一个实例来熟悉函数调用运算符的重载。

#include <iostream.h>

class F
{
public:
double operator ()(double x, double y) const;
};

double F::operator ()(double x, double y) const
{
return (x+5)*y;
}

void main()
{
F f;
cout<<f(1.5, 2.2)<<endl;

<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、付费专栏及课程。

余额充值