C++运算符重载
C++允许程序员重新定义标准运算符在与类对象一起使用时的工作方式。
1.重载赋值运算符
为了解决由对象的按成员赋值引起的问题,就需要修改赋值运算符的行为,以便在将其应用于具有指针成员的类的对象时,执行按成员赋值之外的其他操作。实际上,就是要提供用于该类的对象的赋值运算符的不同版本。所以,这样做也可以说是要重载赋值运算符。
为给定的类重载赋值运算符的一种方式是定义一个名为operator=的运算符函数作为该类的成员函数。示例:
class NumberArray
{
public :
void operator=(const NumberArray &right); //重载运算符
};
函数头的各部分讲解如下:
该函数的名称是operator =。由于该运算符函数是类的实例函数,因此只能通过类的对象调用。通过它调用的类的对象被认为是赋值运算符的左操作数,而传递给该函数的形参被认为是赋值运算符的右操作数。为了说明问题,现在假设程序中定义了left和right两个对象:
NumberArray left, right;
为了把right的值赋给left,可以通过left对象调用成员函数operator=,并且将right对象作为形参传递给它:
left.operator = (right);
虽然可以用这种方式调用运算符函数,但编译器仍然允许使用更常用的表示法:
left = right;
注意:运算符函数的形参不必按引用传递,也不必声明为const。在该示例中使用引用形参是出于提高效率的原因;引用参数避免了复制被传递作为形参的对象的开销。const用于保护形参不被改变。
2.类赋值运算符的返回值
NumberArray& operator= (const NumberArray &right);
上式显示了该重载运算符将返回对NumberArray的引用。这和C++的内置赋值运算符的功能是一致的,它允许像下面这样的层叠赋值语句:
a = b = c;
层叠赋值语句之所以有效,是因为内置赋值运算符在起作用。在赋值操作执行之后,它返回其左操作数的引用。因此,在该语句中,表达式b=c导致c被赋值给b,然后返回对b的引用,而这个返回的引用的值最后被赋给了a。
3. 类赋值运算符的实现
现在来思考一下赋值运算符的实现。首先要注意的是,如果语句像下面这样:
x = x;
那么这并不需要执行任何复制操作,结果就是一个对象被赋值给了它自己。可以通过检查赋值语句左侧对象的this地址是否个右侧对象的地址相同来测试其可能性,示例代码如下:
if( this != right) { /*复制对象*/}
赋值运算符函数通过删除分配给指定对象中的指针的内存开始。在此之后,它将以与此类的复制构造函数几乎相同的方式创建另一个对象的副本。函数的最后操作是通过引用返回赋值语句左侧的*this对象。一下是该函数的代码。
NumberArray & NumberArray::operator= (const NumberArray &right)
{
if(this != &right)
{
if(arraySize > 0)
{
delete [] aPtr;
}
arraySize = right.arraySize;
aPtr = new double[arraySize];
for( int index = 0; index < arraySize ; index++)
{
aPtr[index] = right.aPtr[index];
}
}
return *this;
}
本节讨论的赋值运算符又称为复制赋值或拷贝赋值,它与后面即将介绍的移动赋值运算符有所区别。通常情况下,在构造函数中分配动态内存(或任何类型的资源)的类始终应该定义一个析构函数、一个复制构造函数、一个移动构造函数、一个复制赋值运算符和一个移动赋值运算符。
4,重载其他运算符
有时候,为了能更好的处理程序员定义的一个类,自然会需要重载一些C++内置运算符。例如,假设存在一个名为Date的类,并且Date类的对象可以在成员变量保存日期、月份和年份。假设Date类有一个名为add的成员函数,它可以添加一些天数到日期变量中,并且可以在日期到达另一个月份或年份时调整成员变量。例如,以下语句可以将5天保存到today对象存储的日期中:
today.add(5);
虽然很明显这个语句的意思就是在today中存储的日期增加5天,但使用运算符可能会更直观。例如。来看下面的语句:
today += 5;
这个语句使用标准的+=运算符来添加5到today中。但是,这种行为不会自动发生。必须重载+=运算符才能执行此操作。
运算符重载的一个问题是,不能改变运算符的操作数。例如,=运算符必须始终是二元运算符。同样,递增运算符(++)和递减运算符(--)必须是一元运算符。
虽然大多数的C++运算符可以重载,但并不是所有的C++运算符都可以重载。可以被重载的运算符:
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete
无法被重载的运算符只有如下几个:
?: . .* :: sizeof
5. 运算符重载的方法
有两种方法可以使用重载运算符
1)使重载运算符成为该类的成员函数。这允许运算符函数访问类的私有成员。它也允许函数使用隐私的this指针形参来访问调用对象。
2)使重载的成员函数成为独立分开的函数。当以这种方式重载时,运算符函数必须声明为类的友元才能访问类的私有成员。
某些运算符(如流输入运算符>>和流输出运算符<<)必须作为独立函数重载。其他运算符既可以作为成员函数也可以作为独立函数重载。
6. 重载算术和关系运算符
+ - < ==这些二目运算符需要在重载运算符函数中添加两个对象形参。例如,要扩展一个类的重载运算符,可添加如下声明:
friend Length operator+(Length a, Length b);
friend Length operator+(Length a, Length b);
friend bool operator<(Length a, Length b);
friend bool operator==(Length a, Length b);
代码实现:
Length operator+ (Length a, Length b)
{
Length result(a.len_inches + b.len_inches;
return result;
}
6. 重载流插入和提取运算符
重载流插入运算符<<是很方便的,它允许将对象的值转换为文本并输出到cout、文件对象或从ostream派生的类的任何对象。在适当重载的情况下,来看以下语句:
Length b(4, 8), c(2, 5);
cout<<b;
cout<<b+c;
它被编译器视为以下等效语句:
Length b(4, 8), c(2, 5);
operator<<(cout, b);
operator(cout, b+c);
这种等效性具有以下含义:
1)重载运算符<<采用两个形参,第一个形参是一个ostream对象,第二个形参则是运算符被重载的类的一个对象。对于Length类来说,其原型应该是: operator<<(ostream &strm, Length a)
2)要允许在第二个参数中出现表达式(例如 b+c),则第二个形参应该是按值传递。第一个形参应该通过引用传递,因为ostream形参绝对不应该按值传递。
另外,流插入运算符应该返回它的流形参,以便可以将几个输出表达式链接在一起。流插入运算符应该写成以下形式:
ostream &operator<<(ostream& out, Length a);
重载流输出运算符很有用,因为它允许在输出过程中标记复杂类的各个阶段。
重载流输入运算符与此类似,除了表示要读入对象的类形参必须通过引用传递。因此,流输入运算符的头部如下所示:
istream &operator>>(istream &in, Length &a);
示例代码:
Length.h
#ifndef _LENGTH_H
#define _LENGTH_H
#include <iostream>
using namespace std;
class Length
{
private:
int len_inches;
public:
Length(int feet, int inches)
{
setLength(feet, inches);
}
Length(int inches){len_inches = inches;}
int getFeet() const {return len_inches/12;}
int getInches() const {return len_inches%12;}
void setLength(int feet, int inches)
{
len_inches = 12 * feet +inches;
}
//overload arithmetic and relational operators
friend Length operator+(Length a, Length b);
friend Length operator-(Length a, Length b);
friend bool operator<(Length a, Length b);
friend bool operator==(Length a, Length b);
Length operator++();
Length operator++(int);
//overloaded stream input and output operators
friend ostream &operator<<(ostream &out, Length a);
friend istream &operator>>(istream &in, Length &a);
};
#endif // _LENGTH_H
Length.cpp
#include "Length.h"
istream &operator>>(istream &in, Length &a)
{
//Prompt for and read the object data
int feet, inches;
cout<<"Enter feet: ";
in >> feet;
cout << "Enter inches: ";
in>>inches;
//Modify the object a with the data and return
a.setLength(feet, inches);
return in;
}
ostream &operator<<(ostream& out, Length a)
{
out<<a.getFeet()<<" feet, "<<a.getInches()<<" inches";
return out;
}
Length Length::operator++()
{
len_inches++;
return *this;
}
Length Length::operator++(int)
{
Length temp = *this;
len_inches ++;
return temp;
}
Length operator+(Length a, Length b)
{
return Length(a.len_inches+b.len_inches);
}
Length operator-(Length a, Length b)
{
return Length(a.len_inches - b.len_inches);
}
bool operator== (Length a, Length b)
{
return a.len_inches == b.len_inches;
}
bool operator<(Length a, Length b)
{
return a.len_inches < b.len_inches;
}
main.cpp
#include <iostream>
#include "Length.h"
using namespace std;
int main()
{
Length first(0), second(1,9), c(0);
cout<<"Demonstrating prefix ++ operator and output operator.\n";
for(int count = 0; count < 4; count++)
{
first = ++second;
cout<<"First: "<<first<<".Second: "<<second<<".\n";
}
cout<<"Demonstrating postfix ++ operator and output operator.\n";
for(int count = 0; count < 4; count++)
{
first = second++;
cout<<"First: "<<first<<".Second: "<<second<<".\n";
}
cout<<"\nDemonstrating input and output operators.\n";
cin>>c;
cout<<"You entered "<<c<<"."<<endl;
return 0;
}
7.重载[ ]运算符
任何C++类都可以重载数组索引运算符[],使其对象具有类似数组的行为。实际上,矢量和字符串库类就覆盖了[]运算符,使得它们的对象可以像数组一样进行索引。例如,来看以下代码:
string str = "mad";
cout<<str[0]<<" ";
str[0] = 'b';
cout<<str[0]<<" ";
cout<<str;
以上代码的输出结果为:
m b bad
重载运算符[]必须采用任意类型的单个实参,并且可以返回任意类型的值:
ReturnType & operator[] (inputType T)
要和内置运算符[ ]的工作方式保持一致,则该重载运算符应该通过引用返回其结果,这样结果才可以分配。
假设要编写一个Name类,以代表某个人的全名。对于这样的一个姓名对象,我们需要使用name[1]表示名字,使用name[2]表示姓氏。此外,还需要尝试对该对象使用索引,但如果使用1和2之外的其他数字索引,则退出程序并发布一条出错消息。Name类的内容如下所示。
Name.h源码:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class namespace
{
string first_name;
string last_name;
void subError()
{
cout<<"Index must be 1 or 2\n";
exit(1);
}
public:
string &operator[](int index)
{
switch(index)
{
case 1: return first_name; break;
case 2: return last_name; break;
default: subError();
}
}
};
请注意,运算符[ ]函数采用了int形参并返回对字符串变量的引用。如果name是该类的一个对象,k是一个整数,则函数调用name.operator[](k)和name[k]的作用是一样的。
如下展示了对上述类的使用:
#include "name.h"
int main()
{
Name name;
//set first name and last name
name[1] = "Joseph"; //因为operator[ ]的返回类型是string &类型,所以可以修改
name[2] = "puff";
//Access first name and last name
cout<<name[1]<<" "<<name[2]<<" aka Joe Blow\n";
return 0;
}
[ ]运算符的输入形参类型并不限于int。为了说明这一点,不妨碍创建一个对象,使用英文单词来藐视0...10的相应整数。示例代码如下所示:
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
class Trans
{
vector<string> numerals
{
"zero","one","two","three","four","five",
"six","seven","eight","nine","ten"
};
public:
int operator [](string num_str)
{
for(int k = 0; k < numerals.size(); k++)
{
if(numerals[k] == num_str)
{
return k;
}
}
return -1;
}
};
int main()
{
Trans trans;
cout<<"seven: "<<trans["seven"]<<endl;
cout<<"three: "<<trans["three"]<<endl;
return 0;
}