文章目录
- 前言
- 一、操作符重载限制
- 二、操作符重载
- 2.1 操作符重载函数的返回类型选择
- 2.2 如何选择重载方式
- 2.3 操作符重载实例
- 2.3.1 类成员函数重载
- 2.3.1.1 自增(++)自减(--)操作符
- 2.3.1.2 赋值(=)操作符
- 2.3.1.3 函数调用(())操作符
- 2.3.1.4 下标([])操作符
- 2.3.1.5 指针成员访问(->)操作符
- 2.3.1.6 复合运算操作符
- 2.3.2 非类成员函数重载
- 2.3.2.1 算术操作符
- 2.3.2.2 关系操作符
- 2.3.2.3 位操作符
- 2.3.2.4 逻辑操作符
- 2.3.2.5 输入输出操作符
- 三、引用
前言
我们在设计一个类的时候,不可避免的需要在某些时候对这个类的实例进行操作符运算,例如比较、自增、自减等。此时就必须在类中重载相应的操作符,那么C++对操作符重载有哪些规则?具体操作符的又是如何重载的呢?本文就给大家简要介绍一下各类操作符是如何重载的以及重载操作符应遵循的规则。
一、操作符重载限制
多数的C++操作符都可以被重载,重载的操作符(有些情况例外)不必是成员函数,但必须至少有一个操作数是用户定义的类型。下面详细介绍C++对用户定义的操作符重载的限制:
- 重载后的操作符必须至少有一个操作数是用户定义的类型。这将防止用户为标准类型重载操作符。因此,不能将减法操作符(-)重载为两个double值得和,而不是它们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
- 运算符的操作数(operand)个数不可变。每个二元运算符都需要两个操作数,每个一元运算符都需要恰好一个操作数。因此,我们无法定义出一个equality(==)运算符,并令它接受两个以上或两个以下的操作数。
int x;
ClassName obj;
% x; // invalid for modulus operator
% obj; // invalid for overloaded operator
-
运算符的优先级(precedence)不可改变。例如,除法的运算优先级永远高于加法。
-
不能引入新的操作符。例如,不能定义operator**()函数来表示求幂。
-
不能重载以下操作符:
- sizeof ——sizeof操作符。
- .——成员操作符。
- .*——成员指针操作符。
- ::——作用域解析操作符。
- ?:——条件操作符。
- typeid——一个RTTI操作符。
- const_cast——强制类型转换操作符。
- dynamic_cast——强制类型转换操作符。
- reinterpret_cast——强制类型转换操作符。
- static_cast——强制类型转换操作符。
但是,表1.1中的所有操作符都可以被重载。
表1.1 可被重载操作符
+ | - | * | / | % | ^ |
---|---|---|---|---|---|
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | -- | , | ->* | -> |
() | [] | new | delete | new[] | delete[] |
- 表1.1中的大多数操作符都可以通过成员或非成员函数进行重载,但是下面的操作符只能通过成员函数进行重载:
- =——赋值操作符。
- ()——函数调用操作符。
- []——下表操作符。
- ->——通过指针访问类成员的操作符。
二、操作符重载
2.1 操作符重载函数的返回类型选择
重载操作符的返回类型通常情况下应该与其内置版本的返回类型兼容:逻辑操作符和关系操作符应该返回bool,算术操作符应该返回一个类类型的值,赋值操作符和复合赋值操作符应该返回左侧操作对象的一个引用。
2.2 如何选择重载方式
当我们定义重载的操作符时,必须首先决定是将其声明为类的成员函数还是生命为一个普通的非成员函数。在某些时候我们别无选择,因为有的操作符必须作为成员函数;另一些情况下,操作符作为普通函数比作为成员更好。
下面的准则有助于我们在将操作符定义为成员函数还是普通的非成员函数做出抉择:
- 赋值(=)、下标([])、调用(())和成员访问箭头(->)操作符必须是成员。
- 复合赋值操作符一般来说应该是成员,但并非必须,这一点与赋值操作符略有不同。
- 改变对象状态的运算符或者有给定类型密切相关的操作符,如自增、自减和解引用操作符等,通常应该是成员。
- 具有对称性的操作符可能转换任意一端的操作对象,例如算术、相等性、关系和位操作符等,因此它们通常应该是普通的非成员函数。
2.3 操作符重载实例
2.3.1 类成员函数重载
2.3.1.1 自增(++)自减(–)操作符
自增(++)操作符、自减(–)操作符分为前置版本和后置版本,因为这两个操作符的重载代码基本一致,这里只介绍自增操作符的重载,自减操作符的重载可以同理而得之。
/* 前置版本自增操作符重载实现 */
inline ClassName ClassName::operator++()
{
/* do something here */
return *this;
}
/* 后置版本自增操作符重载实现 */
inline ClassName ClassName::operator++(int)
{
ClassName tmp = *this;
/* do something here */
return tm;
}
/* 操作符调用 */
ClassName obj;
++obj;
obj++;
细心的同学应该能够注意到,前置版本与后置版本重载函数在参数列表上的区别:前置版本的参数列表是空的,而后置版本的参数列表多了一个int形参。按理来说后置版本的参数列表应该也是空的才是,然而重载规则要求,参数列表必须独一无二。因此,C++语言想出了一个变通办法,要求在后置版本中必须有一个int形参。
在我们的日常代码编写过程中,我们从未传入一个int实参给后置版本。那么,这个参数是从何而来,又去往哪里呢了?事实上,无需我们主动传参,编译器主动帮助我们做了这一件事。编译器会自动为后置版本产生一个int参数(其值必须为0)。
2.3.1.2 赋值(=)操作符
赋值操作符重载的函数体中,通常需要释放自身原有的内存空间,并申请一段新的内存来存储新的内容。同时应避免赋值给自己。
class ClassName
{
public:
ClassName& operator=(const ClassName& rl)
{
/* check self assignment */
if (this == &rl)
{
return;
}
/* do something here */
return *this; /* return reference of left operand */
}
};
2.3.1.3 函数调用(())操作符
如果类重载了函数调用操作符,我们则可以像使用函数一样使用该类的对象。因为这样的类同时也能存储状态,所以与普通函数相比它们更加灵活。
如果类定义了调用操作符,则该类的对象称作函数对象。因为可以调用这种对象,所以我们说这些对象的“行为像函数一样”。
class ClassName
{
public:
return_type operator()(parameter list)
{
/* do something here */
return something; /* it may be nothing */
}
};
2.3.1.4 下标([])操作符
表示容器的类通常可以通过元素在容器中的位置访问元素,这些类一般会定义下表操作符operator[]。通常我们会定义两个版本的下标操作符:一个返回普通引用,另一个是类的常量成员并且返回常量引用。
class ClassName
{
public:
/* normal version */
TypeName& operator[](std::size_t n)
{
return elements_[n];
}
/* constance version */
const TypeName& operator(std::size_t n) const
{
return elements_[n];
}
private:
TypeName *elements_;
};
2.3.1.5 指针成员访问(->)操作符
在迭代器类和智能指针类中常常用到解引用操作符(*)和箭头操作符(->)。
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlobPtr& a, size_t sz = 0) :
wptr(a.data), curr(sz) {}
std::string& operator*() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];/* (*p)是对象所指的vector */
}
std::string* operator->() const
{
/* 将实际工作委托给解引用操作符 */
return &this->operator*();
}
private:
std:::shared_ptr<std::vector<std::string>> check(std::size_t i, const std::string& msg) const
{
auto ret = wptr.lock();
if (!ret)
{
throw std::runtime_error("unbound StrBlobPtr");
}
if (i >= ret->size())
{
throw std::out_of_range(msg);
}
return ret;
}
std::weak_ptr<std::vector<std::string>> wptr; /* 保存一个weak_ptr, 意味着底层vector可能会被销毁 */
std::size_t curr; /* 在数组中的当前位置 */
};
/**
用法:与指针或者vector迭代器的对应操作完全一致
*/
StrBlob a1 = {"hi", "bye", "now"};
StrBlobPtr p(a1); /* p只想a1中的vector */
*p = "okey"; /* 给a1的收元素赋值 */
cout << p->size() << endl; /* 打印4, 这是a1首元素的大小 */
cout << (*p).size() << endl; /* 等价于p->size() */
2.3.1.6 复合运算操作符
+=,-=,*=,%=,/=,^=,|=,&=,<<=,>>=,~=
class ClassName
{
public:
ClassName& operator+=(const ClassName& rhs)
{
/* do somethin here */
return *this;
}
};
2.3.2 非类成员函数重载
2.3.2.1 算术操作符
+,-,*,/,%
ClassName operator+(const ClassName& lhs, const ClassName& rhs)
{
ClassName sum = lhs;
sum += rhs;
return sum;
}
2.3.2.2 关系操作符
==,!=,>,>=,<,<=
class ClassName
{
public:
int data_;
};
bool operator==(const ClassName& lhs, const ClassName& rhs)
{
return lhs.data_ == rhs.data_;
}
bool operator!=(const ClassName& lhs, const ClassName& rhs)
{
return !(lhs == rhs);
}
bool operator>(const ClassName& lhs, const ClassName& rhs)
{
return lhs.data_ > rhs.data_;
}
bool operator>=(const ClassName& lhs, const ClassName& rhs)
{
return lhs.data_ >= rhs.data_;
}
bool operator<(const ClassName& lhs, const ClassName& rhs)
{
return !(lhs >= rhs);
}
bool operator<=(const ClassName& lhs, const ClassName& rhs)
{
return !(lhs > rhs);
}
2.3.2.3 位操作符
&、|、~、^、>>、<<
class ClassName
{
public:
ClassName() : data_(0) {}
ClassName(const int data) : data_(data) {}
friend ClassName operator&(const ClassName& lhs, const ClassName& rhs);
friend ClassName operator|(const ClassName& lhs, const ClassName& rhs);
friend ClassName operator~(const ClassName& lhs);
friend ClassName operator^(const ClassName& lhs, const ClassName& rhs);
friend ClassName operator>>(const ClassName& lhs, const int len);
friend ClassName operator<<(const ClassName& lhs, const int len);
private:
int data_;
};
ClassName operator&(const ClassName& lhs, const ClassName& rhs)
{
ClassName tmp(lhs.data_ & rhs.data_);
return tmp;
}
ClassName operator|(const ClassName& lhs, const ClassName& rhs)
{
ClassName tmp(lhs.data_ | rhs.data_);
return tmp;
}
ClassName operator~(const ClassName& lhs)
{
ClassName tmp(~lhs.data_);
return tmp;
}
ClassName operator^(const ClassName& lhs, const ClassName& rhs)
{
ClassName tmp(lhs.data_ ^ rhs.data_);
return tmp;
}
ClassName operator>>(const ClassName& lhs, const int len)
{
ClassName tmp(lhs.data_ >> len);
return tmp;
}
ClassName operator<<(const ClassName& lhs, const int len)
{
ClassName tmp(lhs.data_ << len);
return tmp;
}
2.3.2.4 逻辑操作符
&&、||、!
class ClassName
{
public:
ClassName() : data_(0) {}
ClassName(const int data) : data_(data) {}
friend bool operator&&(const ClassName& lhs, const ClassName& rhs);
friend bool operator||(const ClassName& lhs, const ClassName& rhs);
friend bool operator!(const ClassName& lhs);
private:
int data_;
};
bool operator&&(const ClassName& lhs, const ClassName& rhs)
{
return lhs.data_ && rhs.data_;
}
bool operator||(const ClassName& lhs, const ClassName& rhs)
{
return !(lhs && rhs);
}
bool operator!(const ClassName& lhs)
{
return !lhs.data_;
}
2.3.2.5 输入输出操作符
>>、<<
class ClassName
{
public:
ClassName() : data_(0) {}
ClassName(const int data) : data_(data) {}
friend std::ostream& operator<<(std::ostream& os, const ClassName& lhs);
friend std::istream& operator>>(std::istream& is, ClassName& lhs);
private:
int data_;
};
std::ostream& operator<<(std::ostream& os, const ClassName& lhs)
{
os << lhs.data_;
return os;
}
std::istream& operator>>(std::istream& is, ClassName& lhs)
{
int data;
is >> data;
if (is)
{
lhs.data_ = data;
}
else
{
lhs = ClassName();
}
return is;
}
三、引用
C++ Primer(第五版)