目录
1.基本概念,原理,格式
我们想让普通的运算符,在更多地自定义的类中发挥应有的作用。理解更加的自然,调用也更加的自然
1.1 基本格式
Return_Type operator 运算符(参数);
1.2 类型,调用
-
可以在全局范围进行重载,成为全局函数
-
可以重载为类的成员函数,一个类的实例对象可以隐式也可以显式的调用这个被重载的运算符,但默认的第一个参数一定要是这个类的对象
-
可以重载为友元函数,更加灵活
1.2.1 全局重载
运算符重载为全局函数时,参数的个数等于运算符的目数(即操作数的个数)
class Complex{};
Complex operator + (const Complex & a, const Complex & b);
显式调用时,相当于要调用一个函数名为operator+()
的函数,两个参数要挨个传进去。
隐式调用时,只要保证+
两侧的参数符合参数列表就行。
Complex a(4, 4), b(1, 1), c;
c = a + b; //等价于 c = operator + (a,b)
1.2.2 类成员函数
运算符重载为成员函数时,参数的个数等于运算符的目数减一。
class Complex{
public:
Complex operator - (const Complex & c);//类成员函数
};
显式调用时,相当于a.operator-(const complex& c)
隐式调用时,运算符左边的操作数一定要是类的实例对象,右边的操作数要符合参数列表里的类型。
Complex a(4, 4), b(1, 1), c;
c = a + b; //等价于 c = a.operator+(b);
1.2.3 友元函数
因为成员函数会强制要求第一个参数的类型,即调用者的类型必须是重载的运算符所属的类。但我们在日常使用时可能会忽略,我们只想让他实现加法,比如说,让一个基本类型int去+一个自定义类型complex,我们下意识地写出1+a,但这可能会发生意外,如果系统无法找到自定义的类型转换函数,就需要友元函数来帮忙了。
插嘴:如果你定义了complex的一个构造函数,能从int类型构造出一个complex,他就可以是一个类型转换。相应的系统就可以将1转换为一个临时的complex对象,然后正确的调用complex的运算符重载。
friend一般用在输入、输出的重载中
2. = 的重载
2.1 关键事项:内存分配、返回类型、重载方式
2.1.1 赋值时的内存分配
赋值运算符应当是深拷贝,而不是浅拷贝,因此:
-
当我们在重载赋值运算符时,需要关注被赋值的对象内部可能存在的动态分配的空间是否需要重新分配
-
比如说在一个a中,动态分配了一个长度为3的数组,但是我要让a被赋值后,拿到一个长度为5的数组,我们就需要释放原有的内存,重新分配新的内存
2.1.2 赋值运算符应当返回什么类型?
在对运算符进行重载时,好的风格是应该尽量保留运算符原本的特性,这样其他人在使用这个运算符时才不容易产生困惑。赋值运算符是可以连用的,这个特性在重载后也应该保持。即下面的写法应该合法:
a = b = c;
因此返回值最好是一个引用
2.1.3 拷贝赋值、移动赋值
PhoneBook& PhoneBook::operator=(const PhoneBook& other){//拷贝赋值
if(this != &other){
sz = other.sz;
capacity = other.capacity;
delete [] arr;
arr = new PhoneNumber[capacity];
for(size_t i = 0; i<sz; i++){
arr[i] = other.arr[i];
}
}
return *this;
}
PhoneBook& PhoneBook::operator=(PhoneBook&& other){
//printf("调用移动赋值\n");
if(this != &other){
sz = other.sz;
capacity = other.capacity;
delete[] arr;
arr = other.arr;
//销毁临时对象
other.sz = 0;
other.capacity = 0;
other.arr = NULL;
}
return *this;
}
2.2 自赋值的判断
自己给自己赋值,其实是不需要进行额外的操作的
classA& classA::operator=(const classA& other){
if(this == &other){
return *this;
}
}
3. 输入输出运算符重载,友元
传入的输入流的对象一定要是引用!!!
class PhoneBook{
public:
friend std::ostream& operator<<(std::ostream&, PhoneBook&);
};
//friend仅在声明时使用,只使用一次!
std::ostream& operator<<(std::ostream& output, PhoneBook& pb){
return output;
}
4. 强制类型转换的重载,()运算符
类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数
(类型名)对象这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名(),即变成对运算符函数的调用。
需要注意的是强制类型转换的重载的语法和普遍的运算符重载稍有不同
#include <iostream>
using namespace std;
class Complex
{
double real, imag;
public:
Complex(double r = 0, double i = 0) :real(r), imag(i) {};
operator double() { return real; } //重载强制类型转换运算符 double
};
int main()
{
Complex c(1.2, 3.4);
cout << (double)c << endl; //输出 1.2
double n = 2 + c; //等价于 double n = 2 + c. operator double()
cout << n; //输出 3.2
}
//(double)c等价于c.operator double()。
5. ++, --
C++ 规定,在重载++或--时,允许写一个增加了无用 int 类型形参的版本,编译器处理++或--前置的表达式时,调用参数个数正常的重载函数;处理后置表达式时,调用多出一个参数的重载函数。来看下面的例子:
#include <iostream>
using namespace std;
class CDemo {
private:
int n;
public:
CDemo(int i=0):n(i) { }
CDemo & operator++(); //用于前置形式
CDemo operator++( int ); //用于后置形式
operator int ( ) { return n; }
friend CDemo & operator--(CDemo & );
friend CDemo operator--(CDemo & ,int);
};
CDemo & CDemo::operator++()
{//前置 ++
n ++;
return * this;
}
CDemo CDemo::operator++(int k )
{ //后置 ++
CDemo tmp(*this); //记录修改前的对象
n++;
return tmp; //返回修改前的对象
}
6. =delete, 禁止部分运算符的重载
class X {
public:
// ...
void operator=(const X&) = delete;
void operator&() = delete;
void operator,(const X&) = delete;
// ...
};
通过使用= delete
语法,可以显式地删除逗号运算符的某个重载。删除某个函数的重载意味着禁止使用该函数,任何试图调用这个被删除的函数的操作都会导致编译错误。
在这段代码中,void operator,(const X&)
表示删除了接受const X&
类型参数的逗号运算符重载。这意味着无法使用逗号运算符将const X
类型的对象连接到其他表达式中。任何试图调用这个被删除的逗号运算符重载的操作都会导致编译错误。
需要注意的是,删除一个运算符重载并不会影响其他重载形式的逗号运算符。其他重载形式仍然可以正常使用。