1、操作符
在C++中,除了四种操作符外(“::”,” .*”,”.”,” ?:”),其它内置的操作符大部分都可以是重载,但不能创造其它新的运行符如 “**”。
在类类型中,操作符重载即可以是类成员操作符也可是非类成员操作符,如果一个操作符不属于该类成员,那一般是以友元的形式出现。
操作符重载的一般格式如下:
[返回类型] operator<操作符>([形参列表])
{
// add code
}
当操作符为类成员,定义时在operator前请加上类名,它的函数参数列表要比非成员操作符少一个,因为成员操作符含有this指针,可以当作一个参数。当双目操作符时,对象本身代表左操作数,另外一个参数代表右操作数如赋值操作(=);单目操作符不带参数,如自加操作(++)等。
一般来说,如赋值操作符(=),复合赋值(+=),下标([]),成员访问(->),自加(++), 自减(--),解引用 (*)等应该定义为类的成员操作符。
成员操作符的调用形式与普通成员函数调用形式一致,其调用格式如下:
成员操作符: 对象名.operator<操作符>([参数列表]),
成员函数: 对象名.成员函数名([参数列表])。
成员操作符可把“.operator”省略,即:对象名<操作符>参数列表。其中<>内容表示必须要有,[]的内容表示可选。
如:a.operator++等价于a++。
如果操作符被定义为非成员,那么它在类中应该声明为友元操作符。一般算术操作符(+,-,*,/,%),相等操作符(==,!=),关系操作符(>,>=,<,<=),位操作符(&,|,!)等定义为非成员操作符,其声明格式为:
friend [返回类型] operator<操作符> ([参数列表])
{
// add code
}
非成员操作符的调用形式相比于成员操作符在区别在于没有对象调用(“.”操作没有),参数列表多一个参数,非成员操作符定义如下:
Operator<操作符>(参数1,参数2,,,,)同样可以等价于:参数1(一般为对象)<操作符>参数2;
如:operator==(a,b)等价于a==b。
总之成员操作符与非成员操作符之间主要是有没有this指针。This指针相当于一个隐形的参数。
2、各种操作符重载
在C中,没直接的字符串类,处理任何字符串都必须通过使用指针(数组)+各种字符串函数这种形式,比较繁琐。在C++中,提供了对象的机制,其标准库当中有一个非常实用的字符串类(string),该类提供了丰富的外部接口函数,同时重载了很多操作符如+,+=,<,=,[],<<,>>等。下面通过一个简单的自定义String类来介绍操作符重载中的一些细节问题。
class String
{
public:
String(const char * szpStr = NULL);
String(const String &s);//
String &operator = (const String &s);//
char &operator [](const unsigned int index);//
const char&operator[const unsigned int inedx] const;//
String &operator +=(const String &lhs);
unsigned int GetLength();
~String();
// 算术与关系运算符一般应该友元化
friend String operator+(const String &lhs, const String &rhs);
friend bool operator == (const String &lhs, const String &rhs);
friend bool operator !=(const String &lhs, const String &rhs);
friend bool operator <(const String &lhs, const String &rhs);
friend bool operator >(const String &lhs, const String &rhs);
friend ostream & operator<< (ostream &os, const String &s);
friend istream & operator<< (istream &is, const String &s);
private:
const static int BUFF_SIZE = 512;
unsigned int m_nLen;
char *m_pData;
};
2.1 输入输出操作符
在C++的iostram库中定义了istream与ostream两种类型的输入输出流,并分别定义了两个对象:cin与cout。在标准库中很多类都重载了“<<”与“>>”作为非成员操作符,如string 类等。一般将操作符“<<”与“>>”重载为非成员操作符,其重载的第一个参数为ostream或istream的对象,第二个参数为自定义类类型对象。
在示例程序中String类中“<<”与“>>”操作符重载实现如下:
ostream & operator << (ostream &os, const String &s)
{
os << s.m_pData;
return os;
}
istream & operator >> (istream &is, const String &s)
{
is>>s.m_pData;
return is;
}
函数的返回值都是ostream与instream的引用,这样做的好处就是可以实现链式的输入输出操作。
2.2 算术与关系操作符
算术操作符一般应该定义为非成员函数,因为这样才符合我们日常算术运算规律:如sum = a+b, 操作数有两个,相加后两个操作数的值不变,只是把相加结果赋值给了sum。示例程序String中的”+”操作符实现下如:String operator+(const String &lhs, const String &rhs)
{
String ret(lhs);
ret += rhs;
return ret;
}
函数实现上直接调用了String类的复合赋值操作符,返回一个右值。在实际应用当中,一般应该重载那些简单明了实用的操作符,“+”操作符在字符串类中就非常实用,但“-”,“*”等其它算术操作符就不明所以了。所以在重载算术操作符时应该特别注意操作符的实际意义,如意义明确,则不应该提供重载接口。
在C++的比较两个对象相等通常具有意义的。相等即现在对象的数据成员是一样的。同时如果为类定义了相等操作符,还应该相应的给出不等操作符(在实现中直接调用相等操作符),并且还应该考虑是是否给出相应的关系操作符。
示例当中相待操作与不等操作如下:
bool operator == (const String &lhs, const String &rhs)
{
int ret = strcmp(lhs.m_pData,rhs.m_pData);
if (ret = 0)
{
return true;
}
else return false;
}
bool operator != (const String &lhs, const String &rhs)
{
if (lhs == rhs)// 直接调用 operator ==
{
return false;
}else
{
return true;
}
}
2.3 赋值操作符
赋值操作与复制构造函数有点类似,如果类没有实现,编译器为会它自动合成。自动合成的赋值或者构造都是简单进行数据成员拷贝,同样都会造成指针悬空的问题,这一点在上节深度复制中已经深入分析了,在这不作细说。所以为类实现赋值操作是一个好的编程习惯。赋值与复制构造的区别是,复制构造是通过一个现有的对象去构造一个新的空的对象,而赋值是把一个现有对象去覆盖一个旧的对象(可能是一个空的,没有初始化的对象),在赋值当中同样应该注意把指针所指向的内存空间进行相应的拷贝,在这之前还应该把原对象指针成员所指向的内存空间进行释放。在String类当中,赋值操作实现如下:String & String::operator=(const String &s)
{
if (this != &s)
{
if (m_pData != NULL)
{
delete [] m_pData;//释放指针
}
m_pData = NULL;
m_pData = new char[strlen(s.m_pData)+1];
memset(m_pData,0,strlen(s.m_pData)+1);
memcpy(m_pData, s.m_pData,strlen(s.m_pData));
}
return *this;
}
2.4 下标及成员访问操作符
下标操作是为实现类似于数组元素的读取,即通过“[]“直接进行元素索引。下标操作结果应该即可读亦可写,所以需要同时定义两个版本,一个const型,用于取元素值,作为右值,不可写;一个非const型,作为左值,可读可写。 String中的下标操作符实现如下:char & String::operator[](const unsigned int index)
{
if (index < strlen(m_pData))
{
return m_pData[index];
}
}
const char & String::operator[](const unsigned int index)const
{
if (index < strlen(m_pData))
{
return m_pData[index];
}
}
2.5 其它操作符
复合赋值操作符,自增与自减操作符一般应该定义为成员函数,如果需要赋值操作符,则应该考虑给相应的复合赋值操作符(+=),通过调用复合赋值操作符可是很方便实现算术操作符(+)。在String类中,复合赋值操作实现如下:
String & String::operator +=(const String &s)
{
if (this != &s)
{
if (m_pData && s.m_pData != NULL)
{
char *pTem = new char[strlen(m_pData)+strlen(s.m_pData) +1];
memset(pTem,0,strlen(m_pData)+strlen(s.m_pData) +1);
strcpy(pTem,m_pData);
strcpy(pTem+strlen(m_pData),s.m_pData);
m_pData = pTem;
}
}
return *this;
}
3、相关总结
操作符重载与函数重载一样:形参不一样。即在内置操作符当中参数是内置数据对象 (如int), 而在我们自定义类中,形参是自定义类对象(String, string)。下面对操作重载作简单总结下。1、 重载操作符中一个操作数必须是类类型,如果不是,则就不是重载,而重新定义了内置操作符的含义。
2、 必须与内置操作符的优先级保持一致。
3、 类成员操作符与非类成员操作符的主要区别在于参数个数差别,成员操作符比非成员操作符少一个。
4、 非成员操作符,应该定义为友元。
参考文献《C++ primer》