0%

运算符重载

类之间的运算

这里可以利用抽象代数的观点来看待这种处理,把运算扩充到任意的集合上,而不是只在数集上.运算可以对应于某个集合下元素之间的关系,可以在运算的世界中表达逻辑.在软件工程观点来看,让代码变得简洁

C++运算符重载:用同一个运算符完成不同的运算功能。

限制

基本原则

C++运算符重载的相关规定如下:
1.不能改变运算符的优先级。
2.不能改变运算符的结合性。
3.默认参数不能和重载的运算符一起使用,也就是说,在设计运算符重载成员函数时不能使用默认函数。
4.不能改变运算符的操作数的个数。
5.不能创建新的运算符,只有已有运算符可以被重载
6.运算符作用于C++内部提供的数据类型时,原来含义保持不变
7. 重载的运算符至少有一个操作数是自定义类型

C++中可被重载的运算符:

可被重载

不可被重载的运算符

  • sizeof
  • .: 成员运算符
  • .* 成员指针运算符
  • :: 作用域运算符
  • ?:: 条件运算符
  • typeid: 运行时信息运算符
  • const_cast/dynamic_cast/static_cast/reinterpret_cast 类型转换运算符
    上面这些并不是表达元素之间的运算关系,而是取成员和类型转换,这些由类的元数据决定,当一个类确定之后,这些的运算结果就应该是确定的

只可通过成员函数重载

  • = 赋值
  • () 函数调用
  • [] 下表
  • -> 通过指针访问类成员

格式

1
2
3
4
5
6
7
8
9
函数类型   	operator	重载运算符(形参表)
{
函数体;
}
//非成员函数
friend 函数类型 operator 重载运算符(形参表)
{
函数体;
}

原理

实际上都是编译器做了特定表达式/类型下运算符->函数的转换.最终都被编译器转换成函数

实例

重载自增自减运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
class MyClass2
{
int n;
public:
MyClass2(int i){ n = i; }
int operator ++(){ n++; return n; }
int operator ++(int){ n += 2; return n; }
void display()
{
cout << "n=" << n << endl;
}
};

int main()
{
MyClass2 A(5), B(5);
A++;
++B;
A.display();
B.display();
return 0;
}

重载双目运算符

成员函数

假设有一个类A,对于双目运算符op,如果重载运算符op使之能够实现表达式“obj1 op obj2”,其中obj1和obj2均为A类的对象。
若把op重载为A类的成员函数,该函数只有一个形参,形参的类型是obj2所属的类型。是把op前面的看成对象,op右边的看出函数参数

1
obj1.operator op(obj2)

友元函数

假设有一个类A,对于双目运算符op,如果重载运算符op使之能够实现表达式“obj1 op obj2”,其中obj1和obj2均为A类的对象。
若把op重载为A类的友元函数,该函数有两个形参,经过重载之后,表达式“obj1 op obj2”解释为:

1
obj1 op(obj1,obj2)

重载赋值运算符

哈哈,这里不就是我们熟悉的拷贝构造函数和赋值运算符.找到理论和实际的结合点了

重载下标运算符

下标运算符“[ ]”通常用于获取数组的某个元素,重载下标运算符可以实现数组下标的越界检测等。下标运算符重载只能作为类的成员函数,不能作为类的友元函数。

重载new delete换运算符

new和delete只能被重载为类的成员函数,不能重载为友元。而且,无论是否使用关键字static进行修饰,重载了的new和delete均为类的静态成员函数。
运算符new重载的一般格式如下:

1
2
3
4
5
void	*类名::operator	new(size_t,参数表);
void* operator new(size_t size,int x,int y,int z)
{……
}
void *类名“::operator delete(void*,参数表);

使用如下:

1
x*pX=new(1,2,3) X;

重载类型转换运算符

这个特性是和拷贝构造函数对应的,比如有一个类Stock可以根据传入的double来构造,

1
2
3
4
5
6
7
class Stock{
public:
Stock(double t){
value = t;
}
int value;
}

那么如果要将Stock转出double呢,就要用到下面的类型转换运算符.这两个函数是一一对应的
格式:

1
2
3
4
operator 类型名()
{
函数体;
}

重载函数调用运算符

函数调用运算符“()”只能说明成类的非静态成员函数,该函数具有以下的一般格式:

1
函数类型 类名::operator()(参数表)

重载移位运算符

按照输入流和输出流的概念,如果能有一种运算符可以让对象直接和输入流输出流交互,就不用再写printf(“%s”)这种冗余代码了,还要记类型的format.
于是我们想,把移位看出向输入流和输出流传递数据,正好有左移和右移.开口大的流向开口小的.于是会有下面的代码:

1
2
输出流 <<  obj
输入流 >> obj

幸运的是,C++大佬们设计出了这样的特性. 根据友元函数运算符重载的特点,对于双目操作符是将 obj1 op opj2转换成 op(obj1,obj2);
并且这样还可以支持任意一种自定义类型却不用修改std库中输入流和输出流的代码.这实际上也是友元函数重载解决的问题

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
#include<iostream>
using namespace std;
class Person {
friend ostream& operator<<(ostream& out, Person& p);

public:

Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}

//成员函数 实现不了 p << cout 不是我们想要的效果
//void operator<<(Person& p){
//}

private:
int m_A;
int m_B;
};

//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
//ostream是标准的输出流,全局只能有一个,所以用引用引用的方式
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}

void test() {

Person p1(10, 20);

cout << p1 << "hello world" << endl; //链式编程
}

int main() {

test();

return 0;
}


运算符重载的坑

意外的类型转换

在编程实践中,对于类型转换和拷贝构造函数有可能发生了意外的隐式转换,导致bug,所以最好是加上explict关键字,告诉编译器不要做隐式转换

重载双目运算符不满足交换律

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
#include<iostream>
using namespace std;
class MyClass2
{
int n;
public:
MyClass2(int i){ n = i; }
MyClass2 operator +(int a){ n+=a; return n; }
void display()
{
cout << "n=" << n << endl;
}
friend MyClass2 operator +(MyClass2 a, int b){
a.n += b;
return a;
}
};

int main()
{
MyClass2 A(5), B(5);
A = 5 + A;// 会报错
A.display();
B.display();
return 0;
}

例子中会无法通过编译,因为双目运算符成员函数左边必须是对象,友元重载必须参数类型要匹配(按照前面双目运算符的等价函数调用来理解)。这就导致的加法不满足交换律.

过多的转换函数导致的二义性

假定Stock有形参为double的拷贝构造函数,并且重载了Stock类之间的加法

1
2
3
4
Stock a(1.0);
double b = 2.0;
Stock total;
total = a + b;// 将b转成Stock,再相加

如果此时还又定义了Stock到double的转换函数,那么最后一行将不知道是将a转成double还是将b转成Stock,导致歧义

测试代码见

我的github

引用

  1. C++ Prime Plus
  2. C++重载运算符实例

Welcome to my other publishing channels