C++操作符重载 operator

本文最后更新于:2021年1月26日 上午

引图

重载详细解析😋

对运算符进行重载可以重新定义该运算符的含义,方便我们实现更多的功能。

例如:想要对自己穿件的虚数类进行相加,普通的‘+’无法使用,此时可以选择穿件类的成员函数实现虚数相加,或者是重载‘+’,是其可以实现两个虚数相加。

1
2
3
4
5
6
7
8
9
10
11
class Complex{
public:
Complex operator+(Complex another)
{
Complex temp(a+another.a,b+another.b);
return temp;
}
private:
int a;//实部
int b;//虚部
};

使用方式

1
2
3
4
Complex c1(1,2);
Complex c2(3,4);
Complex c3 = c1 + c2;
//等价 Complex c3 = c1.operator+(c2);

实际上,重载的运算符其实就是具有特殊名字的函数,也可以直接像正常函数那样去使用它们。

操作符重载规则

  1. 不允许用户自己定义新的运算符,只能够对已有的运算符进行重载。

  2. C++中绝大部分运算符都是可以重载的。

引图

不可以被重载的运算符:

. 成员选择符

.* 成员对象选择符

:: 域解析操作符

?: 条件操作符

sizeof 长度运算符

  1. 重载不能改变运算符的操作个数

  2. 重载不能改变运算符的优先级别

  3. 重载不能改变运算符的结合性

    如:‘=’是自右向左,重载后仍然还是自右向左

  4. 重载运算符的函数不能有默认参数

  5. 重载的运算符必须和用户自定义类型的对象一起使用,其参数至少有一个是类对象

因为系统不会允许用户去把两个int类型的‘+’运算改成‘-’运算。

  1. 用于类对象的运算符一般必须重载,但两个例外,‘=’和‘&’不必用户重载。

    ‘=’可以用于每一个类对象,可以使用它在同类之间相互赋值,因为系统早已经为每一个新声明的类重载了一个赋值运算符。而如果类中有指针或者需要涉及new或者malloc时需要我们手动重写去重载等号操作符。成员地址运算符‘&’也不必重载,它可以返回类对象在内存中的起始地址。

  2. 应当使重载运算符的功能类似于该运算符作用于标准运算符数据时那样的功能。(建议)

  3. 运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非友元又非类的成员函数的普通函数。

  4. 通常情况下,不应该重载逗号,取地址,逻辑与,逻辑或运算符。否则代码中使用这些的代码行为将会异于常态。——《C++ Primer》

  5. 当然,重载运算符相当于实现了一个新的函数,重载运算符函数可以通过其参数的不同来实现函数重载

递增递减运算符(前置与后置的特殊)

注意:定义递增与递减运算符的类应该同时定义前置版本与后置版本,且建议这些运算符被定义为类的成员

前置递增运算符

1
2
3
4
5
6
7
8
9
Class Comlpex{
public:
Complex& operator++()
{
++this->a;
++this->b;
return *this;
}
};

注意:为了与内置版本的运算符一致,前置运算符应当返回递增后对象的引用

后置递增运算符

因为前置与后置版本符号相同,就意味着重载的名字与运算对象的数量、类型都是相同的。所以为解决这个问题,后置版本要接受一个不被使用的int类型的形参。

1
2
3
4
5
6
7
8
9
10
Class Comlpex{
public:
Complex operator++(int)
{
Complex ret = *this;
++this->a;
++this->b;
return ret;
}
};

注意:

  1. 为了与内置版本的运算符保持一致,后置运算符应当返回对象的原值,返回的是一个值而非引用。(当然重载运算符返回值写啥都行,你怎么写就怎么用,无须过于死板)。

  2. 对于后置版本来说,在递增对象之前需要首先记录对象的状态。

显式的调用重载函数

1
2
3
Complex c1(2,3);
c1.operator++(); //前置版本
c1.operator++(0); //后置版本,传入的值仅仅便于编译器区分

代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//运行环境Code::Blocks 17.12
#include <iostream>
using namespace std;
class Complex{
public:
Complex(){}
Complex(int a,int b)
{
this->a = a;
this->b = b;
}
Complex(const Complex & another)
{
this->a = another.a;
this->b = another.b;
}
void PrintComplex()
{
cout<<"("<<this->a<<","<<this->b<<")"<<endl;
}
//前置递增
Complex & operator++()
{
++this->a;
++this->b;
return *this;
}
//后置递增
Complex operator++(int)
{
Complex rec = *this;
++this->a;
++this->b;
return rec;
}
private:
int a;//实部
int b;//虚部
};
int main()
{
Complex c1(1,3);
Complex c2(2,4);
c1.PrintComplex();//(1,3)
c2.PrintComplex();//(2,4)
++c1;
c1.PrintComplex();//(2,4)
Complex c3 = c2++;
c3.PrintComplex();//(2,4)
c2.PrintComplex();//(3,5)
return 0;
}

输入输出运算符

输出运算符 <<

一句正常的cout<<"hello world"<<"!";之中,**<< 运算符接受两个运算对象,左侧运算对象必须是一个ostream对象,右侧的运算对象是要打印的值。<<运算符的作用是将给定的值写到给定的ostream对象中,输出结果就是其左侧运算符对象。即计算的结果就是那个ostream对象**。

通常情况下,输出运算符的第一个形参是一个非常量的ostream对象的引用,(向流写入内容会改变其状态),第二个形参是一个常量的引用,是要打印的类类型。故重载函数为

1
ostream& operator<<(ostream &os,const MyClass &item);

通常输出时主要负责打印内容而非控制格式,所以尽量不要打印换行符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//类外的全局函数
ostream & operator<<(ostream &os,const Complexs &c)
{
os<<"("<<c.a<<" , "<<c.b<<")";
return os;
}

//使用测试
Complex c1(1,2);
Complex c2(2,3);

cout<<c1<<endl;
operator<<(operator<<(cout,c1),c2);

----------

//类内的成员函数
ostream& operator<<(ostream &os)
{
os<<"("<<this->a<<" , "<<this->b<<")";
return os;
}
//使用测试
Complex c1(1,2);
Complex c2(2,3);
c1<<cout; //看下面的解释
c2<<(c1<<cout);

输入输出运算符必须是非成员函数!

当<<重载函数是类的成员函数时,使用方式是对象.函数的形式,也就意味着左侧运算符对象是我们自定义类的对象,导致错乱,虽然可以使用,但不建议这么做。

所以当我们希望为类自定义IO运算符时,必须将其定义为非成员函数,而且IO运算符通常需要读写类的非公有成员,故IO运算符一般被声明为友元类。

输入运算符 >>

输出运算符与输入运算符同理,输入运算符的第一个形参是一个非常量的istream对象的引用,,第二个形参是将要读入到的对象的引用。

重载函数为

1
istream& operator>>(istream &is,MyClass &item);

注意:输入运算符必须处理输入可能失败的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
istream & operator>>(istream &is,Complexs &c)
{
int a,b;
cout<<"a:";
is>>a;
cout<<"b:";
is>>b;

if(is)//检查是否输入成功
{
c.a = a;
c.b = b;
}
else
c = Complexs();//输入失败,对象赋予默认状态
return is;
}

赋值操作符

普通情况下,系统会为每一个新声明的类去重载赋值运算符,其作用是逐个复制类的数据成员。然而如果涉及指针,简单的复制将会导致出错(即深拷贝与浅拷贝的问题),这个时候就应当程序员去重写重载赋值运算符的代码。

为了与内置类型的赋值保持一致,重载赋值运算符应当返回一个指向其左侧运算对象的引用。

1
MyClass& operator=(const MyClass & another);

()重载——定义仿函数

仿函数就是使一个类的使用看上去像一个函数,实际上就是类中实现了operator(),于是这个类就有了类似函数的行为。

重载()也叫重载函数调用运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
class absInt{
public:
int operator()(int abs)
{
return abs<0?-abs:abs;//返回数的绝对值
}
};

int main()
{
int a = -8;
absInt abs;
int b = abs(a);
cout<<b<<endl;//b为8
return 0;
}

上面代码中abs只是一个对象而非函数,但我们依旧可以调用该对象,这种对象称为函数对象

  • 仿函数非常的自由,没有固定写法。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!