好奇的探索者,理性的思考者,踏实的行动者。
Table of Contents:
运算符重载(Operator Overloading):同一个运算符可以有不同的功能
运算符重载使得程序的书写更加人性化,易于阅读
比如:虚数的相加
//声明
complex operator+(const complex &A) const;//实现
const{
complex complex::operator+(const complex &A)
return complex(this->m_real + A.m_real, this->m_imag + A.m_imag);
}//调用:c3 = c1 + c2
注意:要在类中声明为友元函数
class complex{
public:
complex();
complex(double real, double imag);
public:
void display() const;//声明为友元函数
friend complex operator+(const complex &A, const complex &B);
private:
double m_real;
double m_imag;
};
complex operator+(const complex &A, const complex &B);//当执行c3 = c1 + c2;语句时,编译器检测到+号两边都是 complex 对象,就会转换为类似下面的函数调用:
//c3 = operator+(c1, c2);
1. 并不是所有的运算符都可以重载
长度运算符sizeof
、条件运算符: ?
、成员选择符.
和域解析运算符::
不能被重载。
2. 重载不能改变运算符的优先级和结合性
3. 重载不会改变运算符的用法,原有有几个操作数、操作数在左边还是在右边,这些都不会改变。例如~
号右边只有一个操作数,+
号总是出现在两个操作数之间,重载后也必须如此
4. 运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的。
将运算符重载函数作为类的成员函数时,二元运算符的参数只有一个,一元运算符不需要参数。之所以少一个参数,是因为这个参数是隐含的。
5. 箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载
6. 将运算符重载函数作为全局函数时,二元操作符就需要两个参数,一元操作符需要一个参数,而且其中必须有一个参数是对象,好让编译器区分这是程序员自定义的运算符,防止程序员修改用于内置类型的运算符的性质。
//绝对禁止的
int operator + (int a,int b){
return (a-b); }
四则运算符(+、-、*、/、+=、-=、*=、/=)
和关系运算符(>、<、<=、>=、==、!=)
都是数学运算符,它们在实际开发中非常常见,被重载的几率也很高,并且有着相似的重载格式。
我们以全局函数的形式重载了 +、-、*、/、==、!=
,以成员函数的形式重载了 +=、-=、*=、/=
,而且应该坚持这样做,不能一股脑都写作成员函数或者全局函数
转换构造函数
编译器在检测到 Complex 和 double(小数默认为 double 类型)相加时,会先尝试将 double 转换为 Complex,或者反过来将 Complex 转换为 double(只有类型相同的数据才能进行 + 运算),
如果都转换失败,或者都转换成功(产生了二义性),才报错。
编译器会先通过构造函数Complex(double real);将 double 转换为 Complex,再调用重载过的 + 进行计算。
在作为普通构造函数的同时,还能将 double 类型转换为 Complex 类型,集合了“构造函数”和“类型转换”的功能,所以被称为「转换构造函数」。换句话说,转换构造函数用来将其它类型(可以是 bool、int、double 等基本类型,也可以是数组、指针、结构体、类等构造类型)转换为当前类类型。
以全局函数的形式重载 +
我们定义的operator+是一个全局函数(一个友元函数),而不是成员函数,这样做是为了保证 + 运算符的操作数(符号两边的运算符不相同时)能够被对称的处理;换句话说,小数(double 类型)在 + 左边和右边都是正确的,而不是调换个位置表现就不一样了。
以成员函数的形式重载 +=
我们首先要明白,运算符重载的初衷是给类添加新的功能,方便类的运算,它作为类的成员函数是理所应当的,是首选的。
调用+=时本身就确定左侧的为当前对象。
在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了重载
cout 是 ostream 类的对象,cin 是 istream 类的对象,要想达到这个目标,就必须以全局函数(友元函数)的形式重载<<
和>>
,否则就要修改标准库中的类,这显然不是我们所期望的。
istream & operator>>(istream &in, complex &A){
in >> A.m_real >> A.m_imag;
return in; }
类中声明友员
//加上friend声明 friend istream & operator>>(istream & in , complex &a);
必须以成员函数的形式进行重载。该重载函数在类中的声明格式如下:
返回值类型 & operator[ ] (参数);
或者:
const 返回值类型 & operator[ ] (参数) const;
使用第一种声明方式,[ ]不仅可以访问元素,还可以修改元素。使用第二种声明方式,[ ]只能访问而不能修改元素。
在实际开发中,我们应该同时提供以上两种形式,这样做是为了适应 const 对象,因为通过 const 对象只能调用 const 成员函数,如果不提供第二种形式,那么将无法访问 const 对象的任何元素。
对于常对象,编译器不管实际上有没有修改对象,只要是调用了非 const 的成员函数,编译器就认为会修改对象(至少有这种风险)。
int& Array::operator[](int i){
return m_p[i]; }
自增++和自减--都是一元运算符,它的前置形式和后置形式都可以被重载。.
由于编译器必须能够识别出前缀自增与后缀自增,人为规定用 operator++() 和 operator–() 重载前置运算符,用 operator++(int)
和 operator--(int)
重载后置运算符,在这里的 int 并没有什么实际的意义,仅仅是为了区分重载的是前置的形式还是后置的形式。
stopwatch stopwatch::run(){
++m_sec;60){
if(m_sec ==
m_min++;0;
m_sec =
}
return *this;
}
//++i,前置形式,先加加,后返回对象
stopwatch stopwatch::operator++(){
return run();
}//i++,后置形式,原始对应要进行一次复制,然后再进行++,最后再返回复制的对象
//在这个函数中参数n是没有任何意义的,它的存在只是为了区分是前置形式还是后置形式。
stopwatch stopwatch::operator++(int n){
stopwatch s = *this;
run();
return s; }
内存管理运算符 new、new[]、delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数。一般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内存时才会重载。
只能重载为成员函数。经过适当重载后,(类型名)对象
这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名()
,即变成对运算符函数的调用。
#include <iostream>
using namespace std;
class Complex
{
double real, imag;
public:0, double i = 0) :real(r), imag(i) {};
Complex(double r = //重载强制类型转换运算符 double
operator double() { return real; }
};
int main()
{1.2, 3.4);
Complex c(double)c << endl; //输出 1.2
cout << (2 + c; //等价于 double n = 2 + c. operator double()
double n = //输出 3.2
cout << n; }