1.不可以重载的运算符: ::, .*, ., ? :。
2.一元重载:只有一个参数
二元重载:有两个参数,左侧运算对象传递第一个参数,右侧运算对象传递第二个参数。
- 在类外:void operator+(int, int)
- 类内:Name operator(int)。因为会有一个隐含的this指针,因此参数会少一个。
除了operator()之外,其他重载运算符不能含有默认实参。
3.使用与内置类型一致的含义:
- 如果类执行IO操作,定义的位运算符应该与内置类型的IO保持一致。
- 如果类定义了operator==那么也应该定义operator!=。
- 如果类定义了单序比较操作(operator<),那么也应该含有其他关系操作。
- 重载运算符的返回类型通常应该与其内置版本的返回类型兼容:逻辑运算符和关系运算符应该返回bool,算术运算符应该返回一个类类型的值,赋值运算符和复合赋值运算符应该返回左侧运算对象的一个引用。
4.成员函数还是非成员函数
- 赋值=,下标[],调用(),成员访问箭头->运算符必须是成员函数。
- 复合赋值运算符一般来说应该是成员函数。
- 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增,递减和解引用等,通常应该是普通的非成员函数。
- 具有对称性的运算符可能转换任意一端的运算对象,如算术,相等性,关系和位运算等,通常应该是普通的非成员函数。
---------------------------------------------------------------------------------------------------------------
输入和输出运算符
1.重载输出运算符<<
一般来说:
- 输出运算符的第一个形参是一个非常量的ostream对象的引用。非常量是因为ostream输入会改变流的状态,引用是因为不能直接复制一个ostream对象。
- 第二个形参一般来说是一个常量引用,通常是要输出的类型,引用的原因是因为不希望复制实参,常量原因是因为不希望改变实参。
- 返回类型一般是ostream的引用。
如:
ostream& operator<<(std::ostream& os, const Sales_data& item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " "
<< item.avg_price();
return os;
}
注意:输出运算符应该负责打印对象的内容而非控制格式,输出运算符不应该打印换行符。输入输出运算符必须是非成员函数。
2.重载输入运算符>>
一般来说:
- 第一个形参是运算符将要读取的流的引用。
- 第二个形参是将要读入到的非常量对象的引用。非常量是因为输入会改变对象。
- 通常返回某个给定流的引用。
如:
istream& operator>>(std::istream& is, Sales_data& item)
{
double price = 0.0;
is >> item.bookNo >> item.units_sold >> price;
if (is)
item.revenue = price * item.units_sold;
else
item = Sales_data();
return is;
}
注意:在重载输入函数中,最好检查一个流是否成功。并且处理失败的情况。当读取发生错误时,输入运算符应该负责从错误中恢复。
---------------------------------------------------------------------------------------------------------------
算术和关系运算符
1.算术运算符
一般来说:
- 把算术和关系运算符定义成非成员函数以此允许对左侧或右侧的运算对象进行转换。并且这些运算符一般不需要改变运算对象的状态,形参都是常量的引用。
- 算术运算符会计算它的两个运算对象并得到一个新值,这个值不同于任意一个运算对象,常常位于一个局部变量之内,操作完成后返回该局部变量的副本作为其结果。
- 如果定义了算术运算符,一般也要定义一个对应的复合赋值运算符。最好的方式是使用复合赋值来定义算术运算符(在算术运算符重载中使用复合赋值运算符来实现功能)。
如:
Sales_data operator+(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum += rhs;
return sum;
}
2.相等运算符
相等运算符会比较类中所有数据,只有所有数据相同时才会相等。因此返回类型应该是bool。
设计准则:
- 如果一个类含有判断两个对象是否相等的操作,则应该把函数定义operator==。
- 如果类定义了一个operator==,则该运算符应该要能判断一组给定的对象中是否含有重复数据。
- 通常,相等运算符应该具有传递性,如果a==b和b==c为真,那么a==c也应该为真。
- 如果定义了operator==,也应该定义operator!=,反之也如此。
- 相等运算符和不相等运算符中的第一个应该把工作委托给另外一个,也就是其中一个运算符应该负责实际比较对象的工作,而另一个运算符则只是调用那个真正工作的运算符。
如:
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
return !(lhs == rhs);
}
3.关系运算符
主要是一些标准库算法和关联容器中会用到,定义operator<会比较有用。
一般来说:
- 定义顺序关系,令其与关联容器中对关键字要求一致,并且
- 如果类同时含有==运算符,定义一种关系令其与==保持一致。特别是,如果两个对象是!=的,那么一个对象应该<另外一个。
---------------------------------------------------------------------------------------------------------------
赋值运算符
1.赋值运算符
包括拷贝赋值运算符,移动赋值运算符和另一种接受花括号内的元素列表作为参数。
例如:vector<string> vec = {"a","b”};
因此为了与内置类型保持一致,也应该定义这种。
如:
StrVec& StrVec::operator=(std::initializer_list<std::string> il)
{
auto data = alloc_n_copy(il.begin(), il.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
注意:返回类型应该是左侧运算对象的引用。同时也必须先释放当前内存空间,再创建一片新的空间,但不需要检查对象自身赋值的情况。
2.复合赋值运算符
一般来说:
- 是类中的成员函数。
- 为了与内置类型保持一致,应该返回左侧运算对象的引用。
如:
Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
---------------------------------------------------------------------------------------------------------------
下标运算符
一般来说:
- 下标运算符必须是成员函数。
- 为了与原始定义兼容,返回值通常是下标所访问元素的引用。
- 应该定义常量和非常量两个版本。一个是返回普通引用,另一个是类的常量成员并且返回常量引用。
如:
string& operator[](size_t n)
{
return elements[n];
}
const string& operator[](size_t n) const
{
return elements[n];
}
---------------------------------------------------------------------------------------------------------------
递增递减运算符
一般来说:
- 最好将其设定为成员函数,因为它们会改变操作对象的状态。
- 应该同时定义前置版本和后置版本,同时前置版本应该返回对象的引用,后置版本应该返回对象原值而不是一个引用。
- 前置递增和递减应该检查操作是否安全。
如下:
StrBlobPtr &StrBlobPtr::operator++()//前置
{
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
StrBlobPtr &StrBlobPtr::operator--()//前置
{
--curr;
check(curr, "decrement past begin of StrBlobPtr");
return *this;
}
StrBlobPtr StrBlobPtr::operator++(int)//后置
{
StrBlobPtr ret = *this;
++*this;
return ret;
}
StrBlobPtr StrBlobPtr::operator--(int)//后置
{
StrBlobPtr ret = *this;
--*this;
return ret;
}
---------------------------------------------------------------------------------------------------------------
成员访问运算符
指的就是箭头运算符->和解引用运算符*
箭头运算符必须是类的成员,解引用运算符通常也是类的成员,但不完全如此
解引用运算符应该返回对象的引用,重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个对象
class StrBlobPtr
{
public:
string& operator* () const
{
.........
return (*p)[curr];
}
string* operator->() const
{
return &this->opertor*(); //将实际工作委托给解引用运算符
}
};
箭头运算符永远都不能丢掉成员访问这个含义,不论如何重载,箭头获取成员的事实是不能被改变的
形如point->mem表达式来说执行过程如下:
1.如果point为指针,则等价于(*point).mem。首先解引用该指针,然后从对象中获取指定成员
2.如果point重载了箭头运算符的一个类,则相当于point.operator->()来获取mem。如果该结果是一个指针,则执行第一步;如果该结果本身含有重载的operator->(),则重复调用。
---------------------------------------------------------------------------------------------------------------
函数调用运算符
如果累重载了函数调用预算福,则我们可以像使用函数一样使用该类的对象。
struct absInt
{
int operator() (int val) const { return val;}
};
int i = 0;
absInt obj;
int num = obj(i);
函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量上或类型上有所区别。
如果类定义了调用运算符,则该类的对象称作函数对象。