关键字class让我们能够封装数据和方法,还能封装运算符,以简化对对象执行的操作。通过使用这些运算符,可以像变量赋值一样对对象执行赋值或者加法运算。
再本章中,将学习:
- 使用关键字operator
- 单目运算符与双目运算符
- 转换运算符
- C++1新增的移动复制运算符
- 不能重新定义的运算符
4.1 C++运算符
从语法层面看,除使用operator外,运算符与函数几乎没有差别。声明运算符看起来与声明函数极其相似:
return_type operator operator_symbol (...parameter list...);
其中operator_symbol是程序员可以定义的几种运算符之一。可以是+(加)、&&(逻辑AND)等。编译器可根据操作数区分运算符。那么,C++在支持函数的情况下为何还要提供运算符呢?
来看下封装了年、月、日的实用类Date:
Date Holiday(25,12,2011); //initialed to 25th Dec 2011
如果要将这个Date对象指向下一天(2011年12月26日),下面两种方法哪种更方便?更直观?
方法1(使用运算符):
++Holiday;
方法2:(使用虚构的函数Date::Increment()):
Holiday.Increment();
显然,方法1优于方法2。基于运算符的机制更容易实现,也更直观。通过在Date类中实现运算符<,将可以像下面这样对两个日期进行比较:
if (Date1<Date2)
{
//Do something
}
else
{
//Do something else
}
运算符并非仅能用于管理日期的类。想想,拼接字符串的运算符 "+".
注意:要实现相关运算符,需要做额外的工作,但类使用起来将更容易,因此秩的这样做。
C++运算符分为两大类:单目运算符与双目运算符。
4.2 单目运算符
顾名思义,单目运算符只对一个操作数进行操作。
实现为全局函数或静态成员函数的单目运算符的典型定义如下:
return_type operator operator_type(parameter_type)
{
//implementation
}
作为类成员的单目运算符的定义如下:
return_type operator operator_type()
{
//...implementation
}
4.2.1 单目运算符的类型
可重载(或重新定义)的单目运算符如下表所示:
运算符 | 名称 | 运算符 | 名称 |
++ | 递增 | & | 取地址 |
-- | 递减 | ~ | 求反 |
* | 解除运算符 | + | 正 |
-> | 成员运算符 | - | 负 |
! | 逻辑非 | 转换运算符 | 转换运算符 |
4.2.2 单目递增与单目递减运算符
要在类声明中编写单目前缀递增运算符(++),可采用如下语法:
Date& operator ++()
{
//operator implementation code
return *this;
}
而后缀递增运算符(++)的返回值不同,且有一个输入参数(但并非总是用它):
Date operator ++(int)
{
//Store a copy of the current state of the object,before incrementing day
Date Copy(*this);
//operator implementation code (that increments this object)
//Return the state before increment was performed
return Copy;
}
前缀和后缀递减运算符的声明语法与递增类似,只是将声明中的++换成了--。
且看下面的实践代码:
#include <iostream>
using namespace std;
class Date
{
private:
int Day;
int Month;
int Year;
public:
//Constructor that initializes the object to a day,month and year
Date(int InputDay,int InputMonth,int InputYear):Day(InputDay),Month(InputMonth),Year(InputYear){};
//Unary increment operator(prefix)
Date& operator ++()
{
++Day;
return *this;
}
Date& operator --()
{
--Day;
return *this;
}
void DisplayDate()
{
cout<<Day<<"/"<<Month<<"/"<<Year<<endl;
}
};
int main()
{
//Instantiate and initialize a date object to 25 Dec 2011
Date Holiday(25,12,2011);
cout<<"The date object is initialized to: "<<endl;
Holiday.DispalyDate();
//Applying the prefix increment operator
++Holiday;
cout<<"Date after prefix-increment is: ";
//Display date after incrementing
Holiday.DispalyDate();
--Holiday;
--Holiday;
cout<<"Date after two prefix-decrements is: ";
Holiday.DisplayDate();
return 0;
}
最令人感兴趣的是类中递增和递减运算符的实现(上面用的都是前缀),这些运算符让我们能够将Date对象存储的日期向前或者向后推一天。前缀递增运算符先执行递增操作,再返回指向当前对象的引用。
要支持后缀递增和递减运算符,只需要再Date类中添加如下代码:
//postfix differs from prefix operator in return-type and parameters
Date operator ++(int)
{
//Store a copy of the current state of the object, before incrementing day
Date Copy(Day,Month,Year);
++Day;
//Return the state before increment was performed
return Copy;
}
Date operator --(int)
{
Date Copy(Day,Month,Year);
--Day;
return Copy;
}
仔细体会下前缀和后缀的区别。
注意:再上述后缀运算符的实现中,首先复制了当前对象,再将当前对象执行递增或者递减运算,最后返回复制的对象。
换句话说,如果只想执行递增运算,可使用++object,也可以使用object++,但应选择前者,这样可以避免创建一个未被使用的临时拷贝。
4.2.3 转换运算符
如果在最开始的代码main()中添加如下代码:
cout<<Holiday; //error in absence of conversion operator
将导致这样的编译错误:erroe:binary"<<":no operator found which takes a right-hand operand of type "Date"(or there is no acceptable conversion)。这种错误表明,cout不知道如何解读Date实例,因为Date类不支持相关的运算符。
然而,cout能够很好地显示const char*:
std::cout<<"Hello world"; //const char* works!
因此,要让cout能够显示Date对象,只需添加一个返回const char*地运算符:
operator const char*()
{
//operator implementation that returns a char*
}
且看下面地代码:
#include <iostream>
#include <sstream>
#include <string>
uaing namespace std;
class Date
{
private:
int Day;
int Month;
int Year;
string DateInString;
public:
//Constructor that initializes the object to a day,month and year
Date(int InputDay,int InputMonth,int InputYear):Day(InputDay),Month(InputMonth),Year(InputYear){}
operator const char*()
{
ostringstream formattedDate;
formattedDate<<Day<<"/"<<Month<<"/"<<Year;
DateInString = formattedDate.str();
return DateInString.c_str();
}
};
int main()
{
//Instantiate and initialize a date object to 25 Dec 2011
Date Holiday(25,12,2011);
cout<<"Holiday is on: "<<Holiday<<endl;
return 0;
}
代码中实现了将Date转换为const char*地运算符,main()中地第35行演示了这样做的好处。现在,可在cout语句中直接使用Date对象,因为cout能够理解const char*。编译器自动将合适运算符(这里只有一个)地返回值提供给cout,从而在屏幕上显示显示日期。
在转换为const char*地运算符中,使用std::ostringstream将整型变量转换为一个std::string对象。原本也可以直接返回formattedDate.str(),但没有这么做,而将其拷贝到私有成员Date::DateInString中.这是因为formattedDate是一个局部变量,将在运算符返回时被销毁,因此,通过str()获得地指针将无效。
这个运算符能够让我们以新的方式使用Date类。我们甚至可以将Date对象直接赋给string对象:
string strHoliday(Holiday); //OK! Compiler invokes operator const char*
strHoliday = Date(11,11,2011); //also OK
注意:
应该根据类地可能用法编写尽可能多地运算符。如果应用程序需要Date对象地整数表示,可以编写如下转换运算符:
operator int()
{
//your conversion code here
}
这样便可将Date对象当作整数使用:
SomeFuncThatTakesInt(Date(25,12,2011));
4.2.4 解除引用运算符(*)和成员选择运算符(->)
解除引用运算符(*)和成员函数选择运算符(->)在智能指针类编程中应用最广泛。智能指针是封装常规指针地类,旨在通过管理所有权和复制问题简化内存管理。在有些情况下,智能指针甚至能够提高应用程序地性能。智能指针以后专门开博客讲解,这里主要介绍如何重载运算符,以帮助智能指针完成其他工作。
看下面的用法,std::unique_prt的用法,它使用运算符*和->,让我们能够像使用普通指针那样使用智能指针类。
#include <iostream>
#include <memory> // include this to use std::unique_ptr
using namspace std;
class Date
{
private:
int Day;
int Month;
int Year;
string DateInString;
public:
//Constructor that initializes the object to a day, month and year
Date (int InputDay,int InputMonth,int InputYear):Day(InputDay),Month(InputMonth),Year(InputYear){};
void DisplayDate()
{
cout<<Day<<" / "<<Month<<" / "<<Year<<endl;
}
};
int main()
{
unique_ptr<int> pDynamicAllocInteger(new int);
*pDynamicAllocInteger = 42;
//Use smart pointer type like an int*
cout<<"Integer value is: "<<*pDynamicAllocInteger<<endl;
unique_ptr<Date> pHoliday(new Date(25,12,2011));
cout<<"The new instance of date contains; "<<endl;
//use pHoliday just as you would a Date*
pHoliday->DisplayDate();
//no need to do the following when using unique_ptr:
//delete pDynamicAllocInteger;
//delete pHoliday;
return 0;
}
这个代码声明了一个指向int的智能指针,它演示了智能指针类unique_ptr的模板初始化语法。同样,声明了一个指向Date对象的智能指针。这里重点是模式,先不考虑其他细节。
这个例子表明,可像使用普通指针那样使用智能指针。*pDynamicAllocInteger来显示指向的int的值,而下面使用pHoliday->DisplayData(),就像这两个变量的类型int*和Date*。其中秘诀在于,智能指针类std::unique_ptr实现了运算符*和->。
程序代码:
#include <iostream>
using namespace std;
template <typename T>
class smart_pointer
{
private:
T* m_pRawPointer;
public:
smart_pointer(T* pData):m_pRawPointer(pData){} // Constructor
~smart_pointer(){delete m_pRawPointer;} //destructor
T& operator* () const //dereferencing operator
{
return *(m_pRawPointer);
}
T* operator-> () const //member selection operator
{
return m_pRawPointer;
}
};
class Date
{
private:
int Day,Month,Year;
string DateInString;
public:
//Constructor that initializes the object to a day,month and year
Date (int InputDay,int InputMonth,int InputYear):Day(InputDay),Month(InputMonth),Year(InputYear){};
void DisplayDate()
{
cout<<Day<<" / "<<Month<<" / "<<Year<<endl;
}
};
int main()
{
smart_pointer<int> pDynamicInt(new int(42));
cout<<"Dynamically allocated integer value = "<<*pDynamicInt;
smart_pointer<Date> pDate(new Date(24,12,2011));
cout<<"Date is = ";
pDate->DisplayDate();
return 0;
}
治理智能指针说得太多了。。有点看不懂,先搁置。内容先贴上来:
4.3 双目运算符
对两个操作数进行操作的运算符称为双目运算符。以全局函数或静态变量成员函数的方式实现的双目运算符的定义如下:
return_type operator_type(parameter1,parameter2);
以类成员的方式实现的双目运算符定义如下:
return_type operator_type(parameter);
以类成员的方式实现的双目运算符只接受一个参数,其原因是第二个参数通常是从类属性获得的。
4.3.1 双目运算符类型
4.3.2 双目加法与双目减法
与递增/递减运算符类似,如果类实现了双目加法和双目减法运算符,即可将其对象加上或减去指定类型的值。再来看看日历类Date,虽然前面实现了将Date递增以便前移一天的功能,但它还不支持增加五天的功能。为实现这种功能,需要实现双目加法运算符,且看下面的程序:
#include <iostream>
using namespace std;
class Date
{
private:
int Day,Month,Year;
public:
//Constructor that initializes the object to a day, month ang year
Date(int InputDay,int InputMonth,int InputYear):Day(InputDay),Month(InputMonth),Year(InputYear){};
//Binary addtion operator
Date operator + (int DaystoAdd)
{
Date newDate(Day+DaystoAdd);
return newDate;
}
//Binary subtravtion operator
Date operator - (int DaystoSub)
{
return Date(Day-DaystoSub,Month,Year);
}
void DisplayDate()
{
cout<<Day<<" / "<<Month<<" / "Year<<endl;
}
};
int main()
{
//Instantiate and initialize a date object to 25 Dec 2011
Date Holiday(25,12,2011);
cout<<"Holiday on: ";
Holiday.DisplayDate();
Date PreviousHoliday(Holiday - 19);
cout<<"Previous holiday on: ";
PreviousHoliday.DislayDate();
Date NextHoliday(Holiday + 6);
cout<<"Next holiday on: ";
NextHoliday.DisplayDate();
return 0;
}
在这个代码实现了双目运算符 + - 的实现,让我们能够使用简单的类的加减法。
对于字符串类来说,双目加法运算也很有用。
String中有+的运算,用于拼接字符。
4.3.3 实现运算符+=与-=
加并赋值运算符支持语法 a += b;这样程序员可以将对象a增加b。这样,可以重载加并赋值运算符,时期接受不同类型的参数b。
且看下面的代码:
#include <iostream>
using namespace std;
class Date
{
private:
int Day,Month,Year;
public:
//Constructor that initializes the object to a day, month and year
Date(int InputDay,int InputMonth,int InputYear):Day(InputDay),Month(InputMonth),Year(InputMonth){};
//Binary addition assignment
void operator+=(int DaystoAdd)
{
Day += DaystoAdd;
}
//Binary subtraction assignment
void operator-=(DaystoSub)
{
Day -= DaystoSub;
}
void DisplayDate()
{
cout<<Day<<" / "<<Month<<" / "<<Year<<endl;
}
};
int main()
{
//Instantiate and initialize a date object to 25 Dec 2011
Date Holiday(25,12,2011);
cout<<"Holiday is on: ";
Holiday.DisplayDate();
cout<<"Holiday -= 19 gives: ";
Holiday -= 19;
Holiday.DispalyDate();
cout<<"Holiday += 325 gives: ";
Holiday += 25;
Holiday.DispalyDate();
return 0;
}
运算符+= 和 -=接受一参数int,让我们能够给Date的对象加上或者减去指定的天数,就像正常处理整数一样。
当然,我们还可以提供运算符+=的重载版本,让它接受一个虚构的CDays对象作为参数:
//The addition-assignment aperator that add a CDays to an exiting Date
void operator += (const CDays& mDaystoAdd)
{
Day += mDaystoAdd.GetDays();
}
4.3.4 重载等于运算符(==)和不等运算符(!=)
略,中间讲的都是类中对各种运算符的重载,先略去
4.4 函数运算符
operator()让对象像函数,被称为函数运算符。函数运算符用于标准模板库(STL)中,通常是STL算法中。用途包括决策。根据使用的操作数数量,这样的函数称为单目谓词或者双目谓词。(什么是单目谓词或者双目谓词?)下面看一个代码,理解一下:
#include <iostream>
#include <string>
using namespace std;
class CDisplay
{
public:
void operator () (string Input) const
{
cout<<Input<<endl;
}
};
int main()
{
CDisplay mDisplayFuncObject;
//equivalent to mDisplayFuncObject.operator() ("Display this string!");
mDisplayFuncObject("Display this string!");
return 0;
}
代码中实现了operator(),然后main()中使用了它。
注意,之所以能够在代码中将对象用作函数,是因为编译器隐式地讲它转换为对函数地operator()调用。
因此,这个运算符也成为operator()函数,对象也称为函数对象或者functor。(这个将在后面的博客中详细讨论)
用于高性能编程地移动构造函数和移动赋值运算符
这部分先略去(细节部分,下次复习在细究)
4.5 不能重载地运算符
虽然C++提供了很大地灵活性,让我们能够自定义运算符地行为,让类能够更易于使用,但是C++有所保留,不允许程序员改变这些运算符地行为:
注意:
务必实现让类更易于使用地运算符。
对于包含原始指针成员的类,除给它提供复制构造函数和析构函数外,无比给他提供复制赋值运算符。如果使用的是遵循C++11的编译器,则对于管理动态分配资源(如数组)的类,务必给它提供移动赋值运算符和移动构造函数。