一,运算符重载简介
一个运算符本质上是一个函数,因此,运算符重载其实就是函数的重载。
运算符重载的目的就是为系统已有的运算符添加特殊的功能。
运算符重载在C 的特性中并不算很特别,这次把它单独拿出来作为一个章节是想借助运算符重载的一些样例来回顾一下C 的一些语法特性,代码量比较多,但是都很经典。
这次被重载的函数,全都是运算符函数,运算符函数的定义和普通函数的定义类似,主要区别在函数名称上,函数名称包含operator关键字和运算符。
运算符重载代码示例:
Box类的声明:
代码语言:javascript复制class Box
{
private:
double length {1.0};
double width {1.0};
double height {1.0};
double volume {1.0};
public:
//重载"<"运算符
bool operator<(const Box& aBox) const;
}
"operator<"重载的具体实现:
代码语言:javascript复制bool Box::operator<(const Box& aBox) const
{
return this->volume() < aBox.volume();
}
"operator<"重载的两种使用方式:
代码语言:javascript复制Box box1, box2;
//不带operator关键字
if (box1 < box2)
std::cout << "box1 is less than box2" << std::endl;
//带operator关键字
if(box1.operator<(box2))
std::cout << "box1 is less than box2" << std::endl;
运算符重载分两种情况:成员函数的运算符重载 & 非成员函数的运算符重载
成员函数的运算符重载:
函数为类的方法。
运算符表达式的左侧必须为该类的对象。
成员函数形式的二元运算符重载:
代码语言:javascript复制return_type operator X(data_type val);
非成员函数的运算符重载:
函数独立于类,是全局函数,通常为类的友元函数。
运算符表达式的左侧可以是不同类的对象。
非成员函数形式的二元运算符重载:
代码语言:javascript复制return_type operator X(data_type1 val_1, data_type2 val_2);
运算符重载包含以下限制:
1.不能添加新的运算符,只能重载语法中已定义过的运算符。
2.对象成员访问运算符("."),作用域解析运算符("::"),条件运算符("?:"),sizeof运算符等运算符不能被重载。
3.除了函数调用运算符、new和delete运算符以外,其他运算符的参数或操作数的数量在重载时不能被改变。举个例子,一元运算符,比如" ",重载时只能用于一个操作数。
4. 运算符重载不能更改运算符本身的优先级和结合性。
二,常见的运算符重载
1.重载==运算符
比较Box的所有尺寸是否相同
代码语言:javascript复制bool Box::operator==(const Box& aBox) const
{
return width == aBox.width
&& length == aBox.length
&& height == aBox.height;
}
2.重载算术运算符
尺寸相加后返回新的对象
代码语言:javascript复制Box Box::operator (const Box& aBox) const
{
return Box{
length aBox.length,
width aBox.width,
height aBox.height
};
}
完整代码样例:实现复数的运算
代码语言:javascript复制#include <iostream>
using namespace std;
class Complex {
private:
float real;
float imag;
public:
Complex() : real(0), imag(0) {}
//重载 operator
Complex operator (const Complex& obj) {
Complex temp;
temp.real = real obj.real;
temp.imag = imag obj.imag;
return temp;
}
void setValue(float val1, float val2) {
real = val1;
imag = val2;
}
void output() {
if (imag < 0)
cout << "Output Complex number: " << real << imag << "i";
else
cout << "Output Complex number: " << real << " " << imag << "i";
}
};
int main() {
Complex complex1, complex2, result;
complex1.setValue(2.0, 3.0);
complex2.setValue(4.0, 5.0);
//complex1调用重载的operator
//complex2作为函数的参数传进来
result = complex1 complex2;
result.output();
return 0;
}
运行结果:
代码语言:javascript复制Output Complex number: 6 8i
3.重载 运算符
给每个尺寸加1,然后返回当前对象
代码语言:javascript复制Box& Box::operator ()
{
length;
width;
height;
return *this;
}
//新的语法
Box Box::operator (int)
{
auto copy{*this};
(*this);
return copy;
}
4.重载<<和>>运算符
operator<<,operator>>的第二个参数是对被读取或被写入的对象的引用,由于operator<<不会修改被读取的对象,因此可以用const引用修饰第二个参数。
代码形式如下:
代码语言:javascript复制std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
return is;
}
代码样例:
代码语言:javascript复制class CellClass
{
public:
void setValue(double Value);
double getValue() const;
private:
double mValue;
};
ostream& operator<<(ostream& ostr, const CellClass& cell)
{
ostr << cell.getValue();
return ostr;
}
istream& operator>>(istream& istr, CellClass& cell)
{
double value;
istr >> value;
cell.setValue(value);
return istr;
}
重载后的operator<<和operator>>可以应用于文件流,字符串流,cin/cout等。
5.重载下标运算符
自己实现一个Array类模板,这个Array可以写入或读取指定索引位置的元素,并且可以自动完成内存的分配。
代码语言:javascript复制#include <iostream>
using namespace std;
//声明
template <typename T>
class Array
{
public:
Array();
virtual ~Array();
//不允许值拷贝
Array<T>& operator=(const Array<T>& rhs) = delete;
Array(const Array<T>& src) = delete;
//根据索引查找元素
const T& getElementAt(size_t x) const;
//根据索引写入元素
void setElementAt(size_t x, const T& value);
size_t getSize() const;
private:
static const size_t kAllocSize = 4; //数组的默认大小
void resize(size_t newSize); //重新分配大小
T* mElements = nullptr;
size_t mSize = 0;
};
//具体实现
template <typename T>
Array<T>::Array()
{
mSize = kAllocSize;
mElements = new T[mSize]{}; //初始化一个空数组
}
template <typename T>
Array<T>::~Array()
{
delete[] mElements;
mElements = nullptr;
}
template <typename T>
void Array<T>::resize(size_t newSize)
{
//创建一个更大的数组
auto newArray = std::make_unique<T[]>(newSize);
for (size_t i = 0; i < mSize; i )
{
newArray[i] = mElements[i];
}
//删除旧数组
delete[] mElements;
mSize = newSize;
mElements = newArray.release();
}
template <typename T>
const T& Array<T>::getElementAt(size_t x) const
{
if (x >= mSize) {
throw std::out_of_range("");
}
return mElements[x];
}
template <typename T>
void Array<T>::setElementAt(size_t x, const T& value)
{
if (x >= mSize)
{
resize(x kAllocSize);
}
mElements[x] = value;
}
template <typename T>
size_t Array<T>::getSize() const
{
return mSize;
}
int main()
{
Array<int> myArray;
for (size_t i = 0; i < 10; i ) {
myArray.setElementAt(i, 100);
}
for (size_t i = 0; i < 10; i )
{
cout << myArray.getElementAt(i) << " ";
}
return 0;
}
重载后的代码如下:
由于该函数返回的是对x索引处元素的引用,因此,该函数即可以查询x索引处对应元素的值,又可以修改x索引处对应元素的值。
代码语言:javascript复制template <typename T>
T& Array<T>::operator[](size_t x)
{
if (x >= mSize)
{
resize(x kAllocSize);
}
return mElements[x];
}
使用运算符重载以后,不需要显式调用setElementAt()和getElementAt(),代码量大大减少。
代码语言:javascript复制#include <iostream>
using namespace std;
//声明
template <typename T>
class Array
{
public:
Array();
virtual ~Array();
//不允许值拷贝
Array<T>& operator=(const Array<T>& rhs) = delete;
Array(const Array<T>& src) = delete;
T& operator[](size_t x);
size_t getSize() const;
private:
static const size_t kAllocSize = 4; //数组的默认大小
void resize(size_t newSize); //重新分配大小
T* mElements = nullptr;
size_t mSize = 0;
};
//具体实现
template <typename T>
Array<T>::Array()
{
mSize = kAllocSize;
mElements = new T[mSize]{}; //初始化一个空数组
}
template <typename T>
Array<T>::~Array()
{
delete[] mElements;
mElements = nullptr;
}
template <typename T>
void Array<T>::resize(size_t newSize)
{
//创建一个更大的数组
auto newArray = std::make_unique<T[]>(newSize);
for (size_t i = 0; i < mSize; i )
{
newArray[i] = mElements[i];
}
//删除旧数组
delete[] mElements;
mSize = newSize;
mElements = newArray.release();
}
template <typename T>
T& Array<T>::operator[](size_t x)
{
if (x >= mSize)
{
resize(x kAllocSize);
}
return mElements[x];
}
template <typename T>
size_t Array<T>::getSize() const
{
return mSize;
}
int main()
{
Array<int> myArray;
for (size_t i = 0; i < 10; i ) {
myArray[i] = 100;
}
for (size_t i = 0; i < 10; i )
{
cout << myArray[i] << " ";
}
return 0;
}
6.重载函数调用运算符
如果在自定义的类中包含重载的函数调用运算符operator(),那么这个类的对象可以被当作函数指针。
包含函数调用运算符的类对象被称为函数对象,或仿函数,开发时可以像使用函数一样使用该对象。
重载的函数调用运算符在类中只能被定义为非静态的函数。
函数对象,可以被当作回调,传给其他函数调用。
一个类,可以包含任意数量的函数调用运算符的重载。
代码样例:
代码语言:javascript复制class FunctionObject
{
public:
//函数调用运算符重载
int operator() (int param);
int doSquare(int param);
};
//函数的具体实现
int FunctionObject::operator()(int param)
{
return param * param;
}
int FunctionObject::doSquare(int param)
{
return param * param;
}
具体使用方式如下:
代码语言:javascript复制int x = 3, res1, res2;
FunctionObject square;
//使用重载的运算符
res1 = square(x);
//使用正常的函数
res2 = square.doSquare(x);
完整代码如下:
代码语言:javascript复制#include <iostream>
using namespace std;
class FunctionObject
{
public:
//函数调用运算符重载
int operator() (int param);
int doSquare(int param);
};
//函数的具体实现
int FunctionObject::operator()(int param)
{
return param * param;
}
int FunctionObject::doSquare(int param)
{
return param * param;
}
int main()
{
int x = 3, res1, res2;
FunctionObject square;
//使用重载的运算符
res1 = square(x);
//使用正常的函数
res2 = square.doSquare(x);
std::cout << res1 << std::endl;
std::cout << res2 << std::endl;
return 0;
}
三,参考阅读
《C 17入门经典》
《C 高级编程》
《C Primer》
https://en.cppreference.com/w/cpp/language/operators
https://www.programiz.com/cpp-programming/operator-overloading