C/C++程序设计
1
一、运算符重载的概念二、禁止重载的运算符三、运算符重载的规则四、单目运算符函数五、双目运算符函数
C/C++程序设计
2
一、运算符重载的概念系统的运算符主要分两大种类:一是单目运算符,另一是双目运算符。用不属于字符集的 @代表各种许可的运算符,运算符构成的表达式抽象的表现格式为:
1,@x 2,x@y
x和 y是运算符关联的操作数,其原先可以出现的数据类型是算术类型以及相关的指针类型如 char*,CType*等。仅当存在运算符函数,操作数 x或 y才可以是对象 。
能转换为运算符函数的普通函数或单参数的函数,此时称单目运算符函数 ;或双参数的函数,此时称双目运算符函数,
C/C++程序设计
3
原来普通的双参数 (非静态的成员函数隐含 this参数 )函数:
int CType::Add (int k) {return n+k; }
int Sub (CType a,int k) { return a.n-k;}
可以相应地改写为运算符函数 (其中 n是 CType类的 int
型数据成员 ):
int CType::operator+ (int k) { return n+k; }
int operator- (CType a,int k) { return a.n-k; }
关键字 operator是实现普通函数转换为运算符函数的语法中介,operator@是运算符函数名。
当 @分别对应 +-*/时加分别得到四个运算符函数,依次是 operator+加号运算符函数,operator-减号运算符函数、
operator*乘号运算符函数和 operator/除号运算符函数。
C/C++程序设计
4
运算符重载是简化对象运算的函数调用现象,通过定义函数名为 operator@的运算符函数,有关对象的函数调用可以简化为 x@y或 @x隐含调用的替代形式。
运算符函数 operator@可以存在多个版本,只要编译器根据名称细分的结果在函数调用点能够进行唯一的匹配。
例如,iostream类中 operator<<左移运算符函数就存在多个版本。
名称为 operator@的运算符函数可以是成员函数也可以是全局函数。基于解除 私有 封装的考虑,全局函数声明为类的友员函数。
如果类只有公共成员,则无需声明为友员函数。作为非虚的成员运算符函数和全局运算符函数的重载在编译阶段完成函数调用的确定,virtual关键字可以修饰作为成员的运算符函数。
C/C++程序设计
5
[例 ]普通函数和相应的运算符函数
#include<stdio.h>
struct CType
{ int n;
CType(int r=1) { n=r; }
int operator+ (int k) { return n+k; }
int Add(int k) { return n+k; }
};
int operator- (int k,CType a) { return k-a.n; }
int Sub (int k,CType a) { return k-a.n; }
void main ()
{ CType a (0),b (-7);
printf ("%d,%d,%d;",a+1,a.operator+ (1),a.Add (1));
printf ("%d,%d,%d\n",1-b,operator- (1,b),Sub (1,b));
} //输出,1,1,1;8,8,8
C/C++程序设计
6
含对象的表达式 a+1等价于显式调用 a.operator+(1),
a+1是 CType::operator+(int)的隐含调用,隐含调用方便了对象的操作。
类似地隐含调用 1-b等价于 operator-(1,b)的显式调用。
1+a不同于 a+1,1+a要求匹配 operator+(int,CType)型的全局运算符函数,而 b-1要求匹配 operator-(CType,int)型的全局函数或 CType::operator-(int)型的成员函数。
由于上例题未提供相应的函数,1+a和 b-1此情形下是错误的表达式。
C/C++程序设计
7
二、禁止重载的运算符只有区区五个运算符不可以赋予运算符的函数实现,它们是:
,?,*?,,? sizeof:
圆点访问成员运算符”,”,对象访问成员指针运算符”,*”,
这两个运算符分别是箭头访问成员运算符 ->和对象指针访问成员指针运算符 ->*的翻版为对象访问成员保留一条安全的入口。
作用域分别符,:和 sizeof运算符都是编译阶段发挥作用。
sizeof运算符的入口参数本身可以是各种类型名。
另一个是三目条件运算符?:,这个运算符本身是 if~else
嵌套结构的合理重载。
C/C++程序设计
8
三、运算符重载的规则存在两种运算符函数的调用格式,一种是将运算符函数当作函数名为 operator@的显式调用格式,另一种是隐含调用格式,最终编译器在内部转换为显式调用格式。
操作数 x,y是算术或指针类型,表达式 x@y,@x是常规的算术或指针运算,不涉及运算符函数。 x或 y是对象,x@y
隐含调用相应的双目运算符函数; x是对象,@x 隐含调用相应的单目运算符函数;若不存在相应的运算符函数,隐含调用导致错误。
对于一个特定的类,系统提供等号运算符函数
operator= 和取地址运算符函数 operator&供程序调用,即对于该类的对象 x,y可以进行运算,x=y,&x。
C/C++程序设计
9
对于对象表达式的前台隐含操作,必须存在一个无歧义的运算符函数作为背景支持。如果无相应的运算符函数或类型转换函数,则有关对象的隐含调用导致错误。
例如,x+=y是毫无根基的运算,除非存在相关的
operator+= 运算符函数。语句 [CType* p;]定义的指针 p不是对象而是一个常规的指针,对于变量指针合适的运算符也可作用于对象之指针。
运算符始终遵循内部类型所规定的优先原则、结合性。
即对于出现在表达式中的运算符函数隐含调用如 x@y,
@x,优先级高的运算符函数优先被编译器隐含调用,同等级别的运算符函数根据结合性进行分解处理。
C/C++程序设计
10
非静态的成员运算符函数的第一个参量对应隐含 this的当前类的类型。
类型转换运算符,函数调用运算符 ()、数组下标索引运算符 [ ]、箭头运算符 -> 函数和等号运算符函数只作为非静态的成员函数,其余的运算符可以和 operator紧贴在一起构成全局函数。
等号运算符函数 operator = 不为派生类继承。
不能凭空捏造 C++语言中子虚乌有的运算符如
FORTRAN语句的乘幂运算符 **。
运算符函数不允许用户提交缺省的默认值,函数调用运算符 operator()例外。
C/C++程序设计
11
双目或单目全局运算符函数形参类型至少存在一个用户声明的类型,枚举类只能有全局运算符函数。
例如对于类声明 [struct CType {};],CType或 CType&
是用户声明的类型。 CType* 类型为常规的指针类型,不用于构成运算符函数的对象类型。
void operator*(CType&,int){} 是正确的函数定义,而
void operator-(CType*,int){}与 void operator+(char,int){}
是错误的。
可以在声明的类上对运算符函数提交任意的语义实现。
但出于与内置类型表达式的接口考虑,最好按照运算符固有含义进行运算符函数定义。
C/C++程序设计
12
四、单目运算符函数单目运算符中存在格外的运算符,这就是成为
C++由来的后置运算符。这个运算符从后面作用于操作数,其运算符重载有其特殊的格式。
对于语句 [CType obj;]定义的对象 obj,单目运算符函数隐含调用的格式为 @obj。
C/C++程序设计
13
1,单目成员运算符函数声明时不带参量,编译器隐含补充
this参量。声明格式为,
ret_type operator@();
返回类型 单目运算符函数名 ();
在类外定义的格式为,
ret_type CType::operator@ () {语句序列 ;}
隐含调用 @obj 转换为显式调用 obj.operator@()。
非静态的单目成员运算符函数隐含的形参 this在调用点由 &obj赋予具体的值。
C/C++程序设计
14
2,单目全局运算符函数仅带一个用户声明的类型,声明格式为,
ret_type operator@ (CType& r);
在实现文件的定义格式为:
ret_type operator@ ( CType& r)
{显含形参 r的语句序列; }
隐含调用 @obj 转换为显式调用 operator@(obj)。单目全局运算符函数形参 r在调用点由 obj赋予具体的值,若需要访问 CType类的私有成员则声明为该类的友元函数。
简洁的 @obj形式既可调用 obj.operator@()成员版本也可调用 operator@(obj)全局版本。为避免重载的歧义性,只提交一种版本,或者是成员版本,或者是全局版本。
C/C++程序设计
15
[例 ] 重载逻辑非运算符 !负号运算符 -和前置运算符 ++
static int m=1;
#include<stdio.h>
class CType
{ int n;
friend CType& operator++(CType& q);
friend void main();
friend CType operator-(const CType& r);
public,CType(int r) { n=r; }
int operator!();
};
int CType::operator! ( ){ printf ("%d!,",m++); return !n;}
C/C++程序设计
16
CType operator- (const CType& r)
{ printf ("%d-,",m++); return CType (-r.n); }
CType& operator++ (CType& q)
{ printf ("%d++,",m++); ++q.n; return q; }
void main()
{ CType a (2),b(-1);
a=!-++b; printf ("a=%d\n",a.n);
a= (operator- (operator++ (b))).operator! ( );
printf ("a=%d",a.n);
} //a=1先调用构造函数 CType (int)即 a=1相当于 a=CType(1)
++b等价于 operator++(b),
-++b等价于 operator-(operator++(b))等。
C/C++程序设计
17
五、双目运算符函数双目运算符函数如果是非静态的成员函数则声明的时候仅带一个参量,编译器隐含的补充一个 this参量。
如果声明为全局函数,则双目运算符函数带两个参量,
其中一个参量的类型必须是一个用户声明的类型。
C/C++程序设计
18
1,双目成员运算符函数作为成员的双目运算符函数的声明格式为,
ret_type operator@ (type);
返回类型 双目运算符函数名 (数据类型 );
在类外定义的格式为,
ret_type CType::operator@( type arg)
{ 语句序列 ; }
对于定义语句 [CType obj;]设定的对象 obj和 type型表达式 var,隐含调用的格式为,
obj@var
隐含调用 obj@var转换为 obj.operator@(var)的显式调用形式。 非静态的双目成员运算符函数隐含的形参 this在调用点由 &obj赋予具体的值,实参 var负责形参 arg 的初始化。
C/C++程序设计
19
2,双目全局运算符函数双目全局运算符函数由于接口的需要花样增多一倍,相应的声明格式为,
ret_type operator@ (CType& r,type arg);
//此格式可以从成员版本变换而来返回类型 双目运算符函数名 (当前类名,其它类名 );
ret_type operator@ ( type arg,CType& r);
返回类型 双目运算符函数名 (其它类名,当前类名 );
相应的隐含调用的格式为,
obj@var var @obj
C/C++程序设计
20
obj@var转换为显式调用 operator@(obj,var) 即启动
operator@ ( CType& r,type arg)函数,var@obj转换为显式调用 operator@ (var,obj)
即启动 operator@( type arg,CType& r)
函数,形参 r由对象名 obj初始化,实参 var赋予形参 arg 具体的初值。
obj@var可以调用成员版本 obj.operator@ (var)也可调用全局版本 operator@ (obj,var)。
两者本质上应是等价的,因此酌情提交一个版本,以免导致歧义。
C/C++程序设计
21
[例 ] 重载加减乘除运算符实现结构变量的四则运算
#include<stdio.h>
struct CA
{ CA operator+ ( CA b); CA (int r=1){ n=r; }; int n; };
static int m=1;
CA CA::operator+( CA a)
{printf ("%d+,",m++); return CA (n+a.n); }
CA operator- (CA a,const CA& r)
{ printf ("%d-,",m++); return CA (a.n-r.n); }
CA operator*(CA a,CA b)
{printf ("%d*,",m++); return CA (a.n*b.n); }
CA operator/ (const CA& r,const CA& q)
{ printf ("%d/,",m++); return CA (r.n/q.n); }
C/C++程序设计
22
void main ()
{ CA a,b(2),c(6);
a=(a+b)*(c/b)-b;
printf ("a=%d; ",a.n);
a=operator- ((operator*(a.operator+ (b),
operator/ (c,b))),b);
printf ("a=%d; ",a.n);
}
//输出,1/,2+,3*,4-,a=7; 5/,6+,7*,8-,a=25;
C/C++程序设计
23
对于对象的引用返回,引用是已经建立的对象的别名,
返回的是不独立的对象,此时编译器不需要额外建立临时对象。
对于对象的数值返回,返回一个局部或临时的独立对象,输送给主控程序。
主控程序会尽早释放临时对象占有的内存。
算术和指针类型的数值返回其函数调用为右值表达式,
对象类型的数值返回其函数调用可以为左值,但由于返回的临时对象的生存期不由程序员控制,因此返回数值对象的函数调用不宜作为左值。
返回引用的函数调用则可稳健地作为左值参入运算。
C/C++程序设计
24
例如,对象表达式,a=(a+b)*(c/b)-b;
编译器先求出对象表达式 a+b,c/b的值,结果分别存放在临时对象 q,t中。接着求 q*t的值,结果存放在临时对象 s
中,再求 s-b的值并把结果存入对象 a中。
独立的对象操作自身内存单元,附属的引用操作相关对象的内存单元。
例如,对于前面单目运算例题,隐含调用 ++b等价于
operator++(b),该 operator++函数是引用形参 r,引用 r操作 b对象的内存单元。
而 - ++b等价于 operator-(operator++(b)),这个结果是临时对象。
指针或算术表达式的结果有两种:
一种结果为右值,对于操作出右值结果的运算符如 +-*/
运算符,在转换为运算符函数时建议返回数值对象。
C/C++程序设计
25
另一种结果为左值,如赋值运算符和复合赋值运算符
+=,-=,*=,/=等。对于这些运算符函数建议处理为对象的引用返回。
入口形参优先采用引用类型而不是对象的数值类型,内置类型的形参可以是数值形参或引用形参。
数值形参具有信息的单向安全性,对于对象的引用形参常加上 const修饰以模拟数值形参的单向作用,返回时也可加 const限制。
对于运算符的返回类型最好根据运算符的结果进行返回,即取地址运算符 &返回地址表达式,逻辑非运算符 !返回
bool值或 int数,前置运算符 ++返回入口类型的引用等。
C/C++程序设计
26