运算符重载概念
什么是运算符重载
所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之实现新功能,因此,一个函数名就可以用来代表不同功能的函数,也就是”一名多用”。
运算符也可以重载。实际上,我们已经在不知不觉之中使用了运算符重载。例如,大 家都已习惯于用加法运算符”+”对整数、单精度数和双精度数进行加法运算,如5+8, 5.8 +3.67等,其实计算机对整数、单精度数和双精度数的加法操作过程是很不相同的, 但由于C++已经对运算符”+”进行了重载,所以就能适用于int, float, double类型的运算。
又如”<<“是C++的位运算中的位移运算符(左移),但在输出操作中又是与流对象cout 配合使用的流插入运算符,”>>“也是位移运算符(右移),但在输入操作中又是与流对象 cin 配合使用的流提取运算符。这就是运算符重载(operator overloading)。
现在要讨论的问题是:用户能否根据自己的需要对C++已提供的运算符进行重载,赋予它们新的含义,使之一名多用。
运算符重载使得用户自定义的数据以一种更简洁的方式工作。
技术推演
为什么会使用运算符重载机制?
用复数举例:
我们定义一个负数类:
1 | class Complex { |
我们希望,在定义一个新的复数对象的时候,可以通过两个复数相加得到,这是,最开始的想法是定义一个普通的函数如下:
1 | Complex myAdd(Complex &c1, Complex &c2) |
这种方法是可行的,但是我想到如果可以直接定义那是不是更加合理和简便。
1 | Complex c3=c1+c2; |
如果我们希望实现这种形式上的运算,就需要使用运算符重载。如下
1 | class Complex |
运算符重载的限制
并不是所有的运算符都可以重载,可以重载的运算符包括:
+ | - | * | / | % | ^ | ||
---|---|---|---|---|---|---|---|
& | \ | ~ | ! | = | < | ||
> | += | -= | /= | % | ^= | ||
&= | \ | = | << | >> | >>= | <<= | |
== | != | <= | >= | && | \ | \ | |
++ | – | ->* | ‘ | -> | [] | ||
() | 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 | class Complex { |
通过友元函数重载 -
1 |
|
通过类成员函数重载前置++
1 | class Complex { |
通过友元函数重载重载前置–
1 | class Complex { |
通过类成员函数重载后置++
1 | class Complex { |
通过友元函数重载后置–
1 | class Complex { |
友元函数实现操作符重载的应用场景
1、友元函数和成员函数选择方法
当无法修改左操作数的类时,使用全局函数进行重载
=, [], ()和->操作符只能通过成员函数进行重载
2、用友元函数重载 << >>操作符
istream 和 ostream 是 C++ 的预定义流类
cin 是 istream 的对象,cout 是 ostream 的对象
运算符 << 由ostream 重载为插入操作,用于输出基本类型数据
运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据
用友员函数重载 << 和 >> ,输出和输入用户自定义的数据类型
1 | //用全局函数方法实现 << 操作符 |
3、友元函数重载操作符使用注意点
友员函数重载运算符常用于运算符的左右操作数类型不同的情况
例如:
1 | class Complex{ |
说明:
在第一个参数需要隐式转换的情形下,使用友员函数重载运算符是正确的选择
友员函数没有 this 指针,所需操作数都必须在参数表显式声明,很容易实现类型的隐式转换
C++中不能用友员函数重载的运算符有: = () [] ->
友元函数重载案例vector类:
1 |
|
运算符重载提高
运算符重载机制
C++编译器是如何支持操作数重载机制的。
重载复制运算符=
赋值运算符重载用于对象数据的复制
operator= 必须重载为成员函数
重载函数原型为: 类型 & 类名 :: operator= ( const 类名 & ) ;
案例:
1 |
1 //先释放旧的内存
2 返回一个引用
3 =操作符 从右向左
重载数组下标运算符[]
重载函数调用符()
为什么不要重载&&和||
理论知识:
1)&&和||是C++中非常特殊的操作符
2)&&和||内置实现了短路规则
3)操作符重载是靠函数重载来完成的
4)操作数作为函数参数传递
5)C++的函数参数都会被求值,无法实现短路规则
1 |
|
运算符重载的应用
实现了个数组类
添加《《》》
实现一个字符串类
智能指针类编写
总结
操作符重载是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 | , | 逗号运算符 | 自左至右 |