---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
算术和关系运算符
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;
}
---------------------------------------------------------------------------------------------------------------