C++基础知识之"运算符重载"

fengjingtu

运算符重载概念

什么是运算符重载

所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之实现新功能,因此,一个函数名就可以用来代表不同功能的函数,也就是”一名多用”。

运算符也可以重载。实际上,我们已经在不知不觉之中使用了运算符重载。例如,大 家都已习惯于用加法运算符”+”对整数、单精度数和双精度数进行加法运算,如5+8, 5.8 +3.67等,其实计算机对整数、单精度数和双精度数的加法操作过程是很不相同的, 但由于C++已经对运算符”+”进行了重载,所以就能适用于int, float, double类型的运算。

又如”<<“是C++的位运算中的位移运算符(左移),但在输出操作中又是与流对象cout 配合使用的流插入运算符,”>>“也是位移运算符(右移),但在输入操作中又是与流对象 cin 配合使用的流提取运算符。这就是运算符重载(operator overloading)。

现在要讨论的问题是:用户能否根据自己的需要对C++已提供的运算符进行重载,赋予它们新的含义,使之一名多用。

运算符重载使得用户自定义的数据以一种更简洁的方式工作。

技术推演

为什么会使用运算符重载机制?

用复数举例:

我们定义一个负数类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Complex {
public:
int a;
int b;
Complex(int a ,int b)
{
this->a = a;
this->b = b;
}
void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};

我们希望,在定义一个新的复数对象的时候,可以通过两个复数相加得到,这是,最开始的想法是定义一个普通的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
Complex myAdd(Complex &c1, Complex &c2)
{
Complex tmp(c1.a+ c2.a, c1.b + c2.b);
return tmp;
}

void test()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = myAdd(c1, c2);
c3.printCom();
}

这种方法是可行的,但是我想到如果可以直接定义那是不是更加合理和简便。

1
Complex c3=c1+c2;

如果我们希望实现这种形式上的运算,就需要使用运算符重载。如下

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
class Complex
{
public:
int a;
int b;
friend Complex operator+(Complex &c1, Complex &c2);
public:
Complex(int a=0, int b=0)
{
this->a = a;
this->b = b;
}

public:
void printCom()
{
cout<<a<<" + "<<b<<"i "<<endl;
}

private:
};
Complex operator+(Complex &c1, Complex &c2)
{
Complex tmp(c1.a+ c2.a, c1.b + c2.b);
return tmp;
}

void test()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2; //思考C++编译器如何支持操作符重载机制的 (根据类型)
c3.printCom();
cout<<"hello..."<<endl;
system("pause");
return ;
}

运算符重载的限制

并不是所有的运算符都可以重载,可以重载的运算符包括:

+ - * / % ^
& \ ~ ! = <
> += -= /= % ^=
&= \ = << >> >>= <<=
== != <= >= && \ \
++ ->* -> []
() new delete new[] delete[]

不能重载的运算符包括

. ..
..
.*
?: sizeof

重载运算符函数可以对运算符做出新的解释,但原有基本语义不变:

不改变运算符的优先级

不改变运算符的结合性

不改变运算符所需要的操作数

不能穿件新的运算符

运算符重载编程基础

运算符函数是一种特殊的成员函数或者友元函数

成员函数的语法形式是:

类型 类名::operator op(参数表)

我们需要注意返回值,关键字,函数名和操作数。

一个运算符被重载后,原有意义没有失去,只是定义了相对一个特性类的一个新的运算符。

举个例子说明我们可能会如何定义一个全局函数来重载+运算符:

1
Complex operator+(Complex &c1, Complex &c2)

如果是在类内部定义的成员函数可能如下:

1
Complex operator+(Complex &c2)
运算符重载的两种方法

用成员或友元函数重载运算符:

运算符可以重载为成员函数或友元函数,关键区别在于成员函数具有this指针,友元函数没有this指针。不管是成员函数还是友元函数重载,运算符的使用方法相同,但传递参数的方式,实现代码,应用场合不相同。

重载二元运算符

使用时候的形式一般为:objectL op objectR。

成员函数:objectL .operator op (objectR);这里左操作数由objectL 通过指针传递,右操作数由参数传递。

友元函数:operator op(objectL ,objectR);左右操作数都由参数传递。

重载一元运算符

使用时候的形式一般为:object op 或者 op object。

成员函数:objectL .operator op ();这里操作数由objectL 通过指针传递

友元函数:operator op(object);操作数都由参数传递。

运算符重载函数名定义

首先承认操作符重载是一个函数定义函数名:operator++

分析函数参数 根据左右操作数的个数,operator++(Complex &c1)

分析函数返回值èComplex& operator++(Complex &c1) 返回它自身

C++ 中通过一个展位参数来区分前置运算和后置运算

设 A Aobject

运算符++ 和 – 有两种方式:

前置方式: ++Aobject和–Aobject
成员函数重载: A::A operator++
调用 Aobject.operator++();
友元函数重载 friend A operator++(A&);
调用 operator++(Aobject);
后置方式: Aobject++和Aobject–
成员函数重载 A::A operator++(int)
调用 Aobject.operator++(0) ;这里的0为伪参数
友元函数 friend A operator++(A&,int);
调用 operator++(Aobject,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
class Complex {
public:
int a;
int b;
//通过类成员函数重载 +
Complex operator+(Complex &c1)
{
Complex tmp(this->a + c1.a,this->b + c1.b);
return tmp;
}
Complex(int a ,int b)
{
this->a = a;
this->b = b;
}
void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};


void test()
{
Complex c1(1, 2);
Complex c2(3, 4);
Complex c3 = c1.operator+(c2);
Complex c4 = c1 + c2;
c3.printCom();
c4.printCom();
}
通过友元函数重载 -
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

using namespace std;
class Complex {
public:
int a;
int b;
// 通过友元函数重载 -
friend Complex operator-(Complex &c1, Complex &c2);
Complex(int a ,int b)
{
this->a = a;
this->b = b;
}
void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};


Complex operator-(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b-c2.b);
return tmp;
}

void test()
{
Complex c1(1, 2);
Complex c2(3, 4);
Complex c3 = c2 - c1;
Complex c4 = operator-(c2, c1);
c3.printCom();
c4.printCom();
}
通过类成员函数重载前置++
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
class Complex {
public:
int a;
int b;
Complex(int a ,int b)
{
this->a = a;
this->b = b;
}
//通过类成员函数重载前置++
Complex& operator++()
{
this->a++;
this->b++;
return *this;
}
void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};
void test()
{
Complex c1(3, 4);
c1.operator++();
c1.printCom();
Complex c2 = ++c1;
c1.printCom();
c2.printCom();
++c2;
c2.printCom();
}
通过友元函数重载重载前置–
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
class Complex {
public:
int a;
int b;
friend Complex& operator--(Complex &c1);
Complex(int a ,int b)
{
this->a = a;
this->b = b;
}
void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};
// 通过友元函数重载前置--
Complex& operator--(Complex &c1)
{
c1.a--;
c1.b--;
return c1;
}


void test()
{
Complex c1(3, 4);
operator--(c1);
c1.printCom();
Complex c2 = --c1;
c1.printCom();
c2.printCom();
--c2;
c2.printCom();
}
通过类成员函数重载后置++
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
class Complex {
public:
int a;
int b;
//通过类成员函数重载后置++
Complex& operator++(int)
{
Complex tmp(this->a, this->b);
this->a++;
this->b++;
return tmp;
}

void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};


void test()
{
Complex c1(3, 4);
Complex c2 = c1++;
c1.printCom();
c2.printCom();
c2.operator++(0);
c2.printCom();
}
通过友元函数重载后置–
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
class Complex {
public:
int a;
int b;
friend Complex& operator--(Complex &c1, int);
Complex& operator++(int)
{
Complex tmp(this->a, this->b);
this->a++;
this->b++;
return tmp;
}

void printCom()
{
cout << a << "+" << b << "i" << endl;
}
};

//通过友元函数重载重载后置--
Complex& operator--(Complex &c1,int)
{
Complex tmp = c1;
c1.a--;
c1.b--;
return tmp;
}

void test()
{
Complex c1(3, 4);
Complex c2 = c1--;
c1.printCom();
c2.printCom();
operator--(c2);
c2.printCom();
}
友元函数实现操作符重载的应用场景

1、友元函数和成员函数选择方法

当无法修改左操作数的类时,使用全局函数进行重载

=, [], ()和->操作符只能通过成员函数进行重载

2、用友元函数重载 << >>操作符

istream 和 ostream 是 C++ 的预定义流类

cin 是 istream 的对象,cout 是 ostream 的对象

运算符 << 由ostream 重载为插入操作,用于输出基本类型数据

运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据

用友员函数重载 << 和 >> ,输出和输入用户自定义的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//用全局函数方法实现 << 操作符 
ostream& operator<<(ostream &out, Complex &c1)
{
//out<<"12345,生活真是苦"<<endl;
out<<c1.a<<" + "<<c1.b<<"i "<<endl;
return out;
}
//调用方法
cout<<c1;
//链式编程支持
cout<<c1<<"abcc";
//cout.operator<<(c1).operator<<("abcd");
//函数返回值充当左值 需要返回一个引用
b)类成员函数方法无法实现 << 操作符重载
//因拿到cout这个类的源码
//cout.operator<<(c1);

3、友元函数重载操作符使用注意点

友员函数重载运算符常用于运算符的左右操作数类型不同的情况

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Complex{
int real;
int Imag;
public:
complex (int a)
{
Real = a;
Image = 0;
}
complex(int a,int b)
{
Real = a;
Image = b;
}
Complex operator+(complex);
};

int f()
{
complex z(2,3);
complex k(3,4);
z=z+27;
z=27+z;//这句话,左操作数是27,不是complex对象,不能调用函数,此时应该使用友元函数。
}

说明:

在第一个参数需要隐式转换的情形下,使用友员函数重载运算符是正确的选择

友员函数没有 this 指针,所需操作数都必须在参数表显式声明,很容易实现类型的隐式转换

C++中不能用友员函数重载的运算符有: = () [] ->

友元函数重载案例vector类:

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
53
54
55
56
57
58
59
60
61
62
63
64

#include <iostream>
using namespace std;

//为vector类重载流插入运算符和提取运算符
class vector
{
public :
vector( int size =1 ) ;
~vector() ;
int & operator[]( int i ) ;
friend ostream & operator << ( ostream & output , vector & ) ;
friend istream & operator >> ( istream & input, vector & ) ;
private :
int * v ;
int len ;
};

vector::vector( int size )
{
if (size <= 0 || size > 100 )
{
cout << "The size of " << size << " is null !\n" ; abort() ;
}
v = new int[ size ] ; len = size ;
}

vector :: ~vector()
{
delete[] v ;
len = 0 ;
}

int &vector::operator[]( int i )
{
if( i >=0 && i < len ) return v[ i ] ;
cout << "The subscript " << i << " is outside !\n" ; abort() ;
}
ostream & operator << ( ostream & output, vector & ary )
{
for(int i = 0 ; i < ary.len ; i ++ )
output << ary[ i ] << " " ;
output << endl ;
return output ;
}
istream & operator >> ( istream & input, vector & ary )
{
for( int i = 0 ; i < ary.len ; i ++ )
input >> ary[ i ] ;
return input ;
}

void main()
{
int k ;
cout << "Input the length of vector A :\n" ;
cin >> k ;
vector A( k ) ;
cout << "Input the elements of vector A :\n" ;
cin >> A ;
cout << "Output the elements of vector A :\n" ;
cout << A ;
system("pause");
}

运算符重载提高

运算符重载机制

C++编译器是如何支持操作数重载机制的。

重载复制运算符=

赋值运算符重载用于对象数据的复制

operator= 必须重载为成员函数

重载函数原型为: 类型 & 类名 :: operator= ( const 类名 & ) ;

案例:

1
2


1 //先释放旧的内存

2 返回一个引用 

3 =操作符 从右向左
重载数组下标运算符[]
重载函数调用符()
为什么不要重载&&和||

理论知识:

1)&&和||是C++中非常特殊的操作符

2)&&和||内置实现了短路规则

3)操作符重载是靠函数重载来完成的

4)操作数作为函数参数传递

5)C++的函数参数都会被求值,无法实现短路规则

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
53
54
55
56
#include <cstdlib>
#include <iostream>

using namespace std;

class Test
{
int i;
public:
Test(int i)
{
this->i = i;
}

Test operator+ (const Test& obj)
{
Test ret(0);

cout<<"执行+号重载函数"<<endl;
ret.i = i + obj.i;
return ret;
}

bool operator&& (const Test& obj)
{
cout<<"执行&&重载函数"<<endl;
return i && obj.i;
}
};

// && 从左向右
void main()
{
int a1 = 0;
int a2 = 1;

cout<<"注意:&&操作符的结合顺序是从左向右"<<endl;

if( a1 && (a1 + a2) )
{
cout<<"有一个是假,则不在执行下一个表达式的计算"<<endl;
}

Test t1 = 0;
Test t2 = 1;

If ( t1 && (t1 + t2) )
{
//t1 && t1.operator(t2)
// t1.operator( t1.operator(t2) )
cout<<"两个函数都被执行了,而且是先执行了+"<<endl;
}

system("pause");
return ;
}

运算符重载的应用

实现了个数组类

添加《《》》

实现一个字符串类
智能指针类编写
总结

操作符重载是C++的强大特性之一
操作符重载的本质是通过函数扩展操作符的语义
operator关键字是操作符重载的关键
friend关键字可以对函数或类开发访问权限
操作符重载遵循函数重载的规则
操作符重载可以直接使用类的成员函数实现
=, [], ()和->操作符只能通过成员函数进行重载
++操作符通过一个int参数进行前置与后置的重载
C++中不要重载&&和||操作符

运算符和结合性
优先级 运算符 含义 要求运算对象的个数 结合方向
1 ()
[]
->
.
圆括号
下标运算符
指向结构体成员
结构体成员
自左至右
2
~
++

-
(类型)
*
&
sizeof
逻辑非
按位取反
自增
自减
负号
类型转换
指针运算符
取地址
长度运算符
1
(单目运算符)
自左至右
3 *
/
%
乘法运算符
除法运算符
取余运算符
2
(双目运算符)
自左至右
4 +
-
加法
减法
2
(双目运算符)
自左至右
5 <<
>>
左移
右移
2
(双目运算符)
自左至右
6 < <= > >= 关系运算符 2
(双目运算符)
自左至右
7 ==
!=
等于运算符
不等于运算符
2
(双目运算符)
自左至右
8 & 按位与运算符 2
(双目运算符)
自左至右
9 ^ 按位异或 2
(双目运算符)
自左至右
10 \ 按位或 2
(双目运算符)
自左至右
11 && 逻辑与 2
(双目运算符)
自左至右
12 \ \ 逻辑或 2
(双目运算符)
自左至右
13 ?: 条件运算符 3
(三目运算符)
自右至左
14 = += -= *= /= %= >>=
<<= &= ^= \
= 赋值运算符 2
(双目运算符)
自左至右
15 逗号运算符 自左至右