运算符重载

发布时间 2023-10-20 09:26:49作者: 西西弗斯Sisyphus

运算符重载

1 概述

C++规定运算符重载必须针对类的对象,即重载时至少有一个参数是对象,如A、const A、A &等等。没有对象就new一个出来

C++用operator加运算符进行。对于普通运算符成员函数,this隐含参数代表第一个操作数对象。运算符可分为:

不能重载:sizeof、.、.*、::、?:
只能重载为普通成员函数:=、->、()、[]
只能重载为普通函数:new、delete
其他运算符可以重载为类的普通成员函数和普通函数,但不能重载为类的静态成员函数

若运算符为左值运算符,则重载后运算符应尽量返回非只读引用类型。当运算符要求第一个参数为左值时,不能使用const说明第一个参数(含this)。此外,重载不改变运算符的优先级和结合性。

需要注意,并不是含有类的名称的东西都是对象,如:

A &operator ==(A *a,A b[]);    //错误,这俩都是指针类型变量,不是对象

2 运算符参数

重载函数的参数个数与重载函数种类有关:

重载为普通函数:参数个数=运算符目数
重载为普通成员:参数个数=运算符目数-1(有一个this指针)
重载为静态成员:参数个数=运算符目数(没有this)

有些特殊的运算符不满足上述关系:->重载为单目,前置++--为单目,后置++--为双目,函数()可重载为任意目。

关于()运算符的重载,不加改动地照搬课件上令人难绷的代码。

#include <string.h>
class SYMTAB;
struct SYMBOL {
    char *name;  int  value;  SYMBOL *next;   friend SYMTAB;
private:
    SYMBOL(char *s, int v, SYMBOL *n)  { /*...*/ };   ~SYMBOL( ) { /*…*/ }
} *s;
class SYMTAB {
    SYMBOL *head;
public:
    SYMTAB( ) { head = 0; };     ~SYMTAB( )  { /*...*/  } 
    SYMBOL *operator( )(char *s, int v, int w) { /*…*/};
} tab;
void main(void) { s = tab(“a”, 1, 2); }   //等价 s = tab.operator()(“a”, 1, 2)

这里对SYMBOL类中的括号运算符重载为了三目运算符。

下面介绍一些特殊运算符。

2.1 自增自减

上面已经介绍了自增自减运算符的双目、单目两种情况。除此之外,还要声明一点:后置运算应返回右值,前置运算应返回左值。这样更方便对象的连续运算。

应注意,自增自减最好返回对象类型而非int,这更便于计算:

A operator++(int){    //后置
    return A(x++);    //巧妙的返回
}
A &operator++(){      //前置,返回引用类型
    x++;
    return *this;
}

2.2 双目箭头运算符

上面已经介绍,原先箭头运算符是双目的,重载应使其变成单目(只有一个参数)。重载->必须返回指针类型

下面是课件源代码,清晰地解释了语句i = b->a的含义,不再解释。

struct A { int a; A(int x) { a = x; } };
class  B {
    A  x;
public:
    A *operator ->( ) { return &x; };   //只有一个参数this, 为单目
    B(int v):x(v) {  }           //构造x(5)
} b(5);  
void  main(void) {
    int i = b->a;
    //上面语句翻译为:
    i = b.operator->( ) ->a;     //i = b.x.a = 5
    i = (*b.operator->( )).a;
}

可以看出,->重载的最大作用是简便代码的编写。

2.3 赋值

事实上,编译器为定义的类构造了缺省的赋值重载(浅拷贝)。即

A &operator=(const A&);

而若类自定义了重载赋值运算符函数,则会优先调用自定义的函数。

对于浅拷贝复制,若数据成员为指针类型,则不复制指针所指存储单元的内容。这可能会导致内存的泄露。因此对于成员包含指针的类,应尽量构造深拷贝赋值运算符重载函数。

为防止这种内存的泄露,可采用的方法有:

  1. 深拷贝,即先将指针指向的内容释放,再进行赋值和分配;

  2. 移动赋值,对纯右值使用,浅拷贝后令其指向nullptr我也不明白为什么这样可以防止内存泄漏

代码过于繁琐,详见课本相关内容。

3 强制类型转换

对类进行的强制类型转换同样可以进行重载。

需要强调的是,合适地编写构造函数可以利用构造函数直接进行类型转换。

class COMPLEX{
    double r,v;
public:
    COMPLEX(double r1);{/*...*/}
    COMPLEX(double r1,double v1);{/*...*/}
    COMPLEX operator+(const COMPLEX &c)const;
}
COMPLEX m(3);
cout<<m+2;

通过这种构造方式,3自动转化为double型的3.0m+2则转换为m+2.0,然后构造匿名对象COMPLEX(2.0),最终转换为m+COMPLEX(2.0)

而若想更清晰地表示强制类型转换,可以采用强制类型转换函数的方式:

operator 类型表达式()

如:

operator int()const{return i+j;}    //转换为int型
operator A()const{return A(i+j);}   //转换为类A型

对于newdelete的重载,不再解释。