第5章 运算符重载

运算符概述

        纯单目运算符,只能有一个操作数,包括:!、~、sizeof、new、delete 等

        纯双目运算符,只能有两个操作数,包括:[]、->、% 、 = 等

        三目运算符,有三个操作数,如“ ? : ”

        既是单目又是双目的运算符,包括: + 、 - 、 & 、 * 等

        多目运算符,如函数参数表 “()”。

        左值运算符是运算结果为左值的运算符,其表达式可出现在等号左边,如前置 ++、-- 以及赋值运算 = 、 += 、 *= 和 &= 等。右值运算符是运算结果为右值的运算符,如 + 、 - 、 >> 、 % 、后置 ++、-- 等。

        某些运算符要求第一个操作数为左值,如 ++、-- 、 = 、 += 、 &= 等。

运算符重载

C++预定义了简单类型的运算符重载,如3 + 5、3.2 + 5.3分别表示整数和浮点加法。

故C++规定运算符重载必须针对类的对象,即重载时至少有一个参数代表对象(类型如A、const A、A & 、const A & 、volatile A等) 。

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

运算符分类

根据能否重载及重载函数的类型,运算符分为:

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

若运算符为左值运算符,则重载后运算符函数最好返回非只读引用类型(左值)。

当运算符要求第一个参数为左值时,不能使用 const 说明第一个参数(含this),例如 ++、--、 = 、 += 等的第一个参数。

重载运算符函数可以声明为类的友元;

重载的普通运算符成员函数也可定义为虚函数;

重载的非成员函数被视为普通函数。

重载运算符函数一般不能缺省参数,只有任意目的运算符() 省略参数才有意义。

重载不改变运算符的优先级和结合性。

重载一般也不改变运算符的操作数个数。特殊的运算符->、++、--除外。

class A;
int operator = (int, A&);           //错误, 不能重载为普通函数
A& operator +=(A&, A&);             //正确,重载 += 为普通函数
A& operator ==(A* a, A b[]);        //A * 和 A[] 参数不代表对象
class A {
    friend int operator=(int, A&);              //错误,不存在普通函数operator=()
    static int operator()(A&, int);            //错误,不能为静态成员
    static int operator+(A&, int);              //错误,不能为静态成员
    friend A& operator += (A&, A&);             //正确, operator += ( ) 是什么函数?
    A& operator ++();                           //隐含参数this代表一个对象
};
#include 
using namespace std;
class A {
    int  x;
public:
    int getx() const { return x; }            //const A *const this,代表对象
    A(int x) { A::x = x; }                    //A *const this
};
int operator+(const A& x, int y)  //普通函数: 参数const A &x 代表一个对象
{
    return x.getx() + y;
}           //能否写成:  return x.x + y ? 不能 x 是私有成员
int operator+(int y, A x)           //普通函数: 参数 A x 代表一个对象
{
    return x.getx() + y;
}
//不能声明 int operator+(A[6], int);  A[6]不是单个对象
//不能声明 int operator+(A*, int);    A* 是对象指针,属于简单类型,不代表对象
void main(void)
{
    A  a(6);                                          //调用A(int)时,实参&a传递给隐含形参this
    cout << "a+7=" << a + 7;                          //调用int operator+(const A &, int)
    cout << "a+7=" << operator+(a, 7);  //调用int operator+(const A &, int)
    cout << "8+a=" << operator+(8, a);  //调用int operator+(int, A)
    cout << "8+a=" << 8 + a;                          //调用int operator+(int, A)
}

参数个数 

重载函数种类不同,参数表列出的参数个数也不同。

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

有的运算符既为单目又为双目,如*, +, -等。

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

()表示强制类型转换时为单参数;表示函数时可为任意个参数。

#include 
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)

后置与前置

        运算符++和--都会改变当前对象的值,重载时最好将参数定义为只读引用类型(左值)。

后置运算应重载为返回右值的双目运算符函数:

        如果重载为类的普通函数成员,则该函数只需定义一个int类型的参数(已包含一个不用const修饰的this参数);

        如果重载为普通函数(C函数),则最好声明非const引用类型和 int 类型的两个参数(无this参数)。

前置运算应重载为返回左值的单目运算符函数:

        前置运算结果应为左值,其返回类型应该定义为非只读类型的引用类型;左值运算结果可继续++或--运算。

        如果重载为普通函数(C函数),则最好声明非const引用类型一个参数(无this参数)。

class A {
    int  a;
    friend A& operator--(A& x) { x.a--; return x; }  //前置运算 (普通函数), 返回左值
    friend A operator--(A&, int);                    //后置运算(普通函数), 返回右值
public:
    A& operator++() { a++;   return *this; }         //前置运算(成员函数)
    A operator++(int) { return  A(a++); }            //后置运算(成员函数)
    A(int x) { a = x; }
};  //A m(3); (--m)-- 可以;因为--m左值,其后--要求左值操作数
A operator--(A& x, int) {        //x左值引用,实参被修改
    return  A(x.a--);            //先取x.a返回A(x.a)右值,再x.a--
} //A m(3); (m--)--不可;因为m--右值,其后--要求左值操作数

//重载双目–>,使其只有一个参数(单目),重载–>必须返回指针类型.
struct A { int a; A(int x) { a = x; } };
class  B {
        A  x;
public:
        A* operator ->() { return &x; };   //只有一个参数this, 故重载为单目
        B(int v) :x(v) {  }
} b(5);
void  main(void) {
        int i = b->a;                            //被解释为: i = (b.operator->( )) -> a, i = 5
        i = b.operator->()->a;                   //i = b.x.a = 5
        i = (*b.operator->()).a;    //i = (&*b.operator->( ))->a= b.operator ->( )->a
}

浅拷贝

        编译程序为每个类提供了缺省赋值运算符函数(浅拷贝),对类A而言,其成员函数原型为 A& operator=(const A&)。

        如果类自定义或重载了赋值运算函数,则优先调用类自定义或重载的赋值运算函数(不管是否取代型定义)。

        缺省赋值运算实现数据成员的浅拷贝复制,如果数据成员为指针类型,则不复制指针所指存储单元的内容。若类不包含指针,浅拷贝赋值不存在问题。

        如果函数参数要值参传递一个对象,当实参传值给形参时,若类A没有定义 A(const A&) 形式的构造函数,则值参传递也通过浅拷贝赋值实现。

         当类包含指针时,浅拷贝赋值可造成内存泄漏,并可导致页面保护错误或变量产生副作用:

第5章 运算符重载_第1张图片

#include 
#include 
using namespace std;
class STRING {
    char* s;
public:
    STRING(const char* c) { strcpy(s = new char[strlen(c) + 1], c); }
    STRING(const STRING& s);                            //深拷贝构造函数
    STRING(STRING&& s) noexcept;                        //移动构造函数
    virtual STRING& operator=(const STRING& s);         //深拷贝赋值函数
    virtual STRING& operator=(STRING&& s) noexcept;     //移动赋值函数
    virtual char& operator[](int x) { return s[x]; }
    virtual STRING operator+(const STRING&) const;
    virtual STRING& operator+=(const STRING& s) { return *this = *this + s; };
    virtual ~STRING() noexcept { if (s) { delete[]s; s = 0; }; };
};
STRING STRING::operator+(const STRING& c) const {
    char* t = new char[strlen(s) + strlen(c.s) + 1];
    STRING r(strcat(strcpy(t, s), c.s));   //strcpy、strcat返回t
    delete[] t;    return r;
}
STRING& STRING::operator=(const STRING& cs) {
    delete[] s;
    strcpy(s = new char[strlen(cs.s) + 1], cs.s);  return *this;
}
void main(void) {
    STRING  s1(“123”), s2(“abc”); s3(“hello”);
    (s1 = s1 + s2) = s2;  //重载“=”返回左值,可连续赋值否则不可
    //等价于 s1 = s1 + s2;  s1 = s2;   s1被连续赋值
    s1 += s3;
    s3[0] = 'T';   // s3[0] = 调用 char &operator[](int x) 返回左值
}

 注意

  • 应定义 T(const T&) 形式的深拷贝构造函数;
  • 应定义 T(T&&) noexcept 形式的移动构造函数;
  • 应定义 virtual T& operator = (const T&) 形式的深拷贝赋值运算符;
  • 应定义 virtual T& operator = (T&&) noexcept 形式的移动赋值运算符;
  • 应定义 virtual ~T() 形式的虚析构函数;
  • 在定义引用 T& p = *new T() 后,要用 delete & p 删除对象;
  • 在定义指针 T * p = new T() 后,要用 delete p 删除对象;

强制类型转换

        如定义了合适的类型转换函数,就可以完成操作数的类型转换;

        如定义了合适的构造函数,就可以构造符合类型要求的对象,构造函数也可以起到类型转换的作用。

        对象与不同类型的数据进行运算,可能出现在双目运算符的左边和右边,为此,可能需要定义多种运算符重载函数。

        只定义几种运算符重载函数是可能的,即限定操作数的类型为少数几种乃至一种。如果运算时对象类型不符合操作数的类型,则可以通过类型转换函数转换对象类型,或者通过构造函数构造出符合类型要求的对象。

//定义“复数 + 复数”、“复数 + 实数”、“复数 + 整数”、 “复数 - 复数”、“复数 - 实数”、“复数 - 整数”
//几种运算(还有复数同实数乘除运算, 等等):
class COMPLEX {
        double r, v;
public:
        COMPLEX(double r1, double v1);
        COMPLEX operator+(const COMPLEX& c) const;
        COMPLEX operator+(double d) const;
        COMPLEX operator+(int d) const;
        COMPLEX operator-(const COMPLEX& c) const;
        COMPLEX operator-(double d) const;
        COMPLEX operator-(int d) const;
        … … … …  //更多的 +、- 运算法重载函数
};

单参数构造函数的特殊性

        单参数的构造函数具备类型转换作用,必要时能自动将参数类型的值转换为要构造的类型。

        以下通过定义单参数构造函数简化重载(同时注意C++会自动将int转为double):

class COMPLEX {
        double r, v;
public:
        COMPLEX(double  r1);
        COMPLEX(double  r1, double v1) { r = r1; v = v1; }            
        COMPLEX operator-(const COMPLEX& c) const;            
};
//定义COMPLEX m(3),m + 2转换为m + 2.0转换为 m + COMPLEX(2.0)。

        单参数的构造函数相当于类型转换函数,单参数的 T::T(const A) 、 T::T(A&&) 、T::T(const A&) 等相当于A类到T类的强制转换函数。

        强制类型转换函数:operator 类型表达式()。由于转换后的类型就是函数的返回类型,所以强制类型转换函数不需要定义返回类型, 也不带输入参数(表示将 this 对象转换为其他类型)。

        同时定义 A::operator T() 和 T::T(const A&) 容易出现二义性错误。

        按照C++约定,强制类型转换的结果通常为右值,故最好不要将类型转换函数的返回值定义为左值,也不应该修改当前被转换的对象(参数表后用const说明this)。

        C++规定转换的类型表达式不包含() 和[], 只能使用引用、指针。如operator int A::** const& ()正确,而 operator int(*)* () 是错误的。

struct A {
    int  i;
    A(int v) { i = v; }
    virtual operator int() const { return  i; }  //类型转换返回右值
} a(5);
struct B {
    int  i, j;
    B(int x, int y) { i = x;  j = y; }
    operator int() const { return  i + j; }  //类型转换返回右值
    operator A() const { return A(i + j); }  //类型转换返回右值
} b(7, 9), c(a, b);
void  main(void) {           //分析下面各条语句的执行过程
    int  i = 1 + a;          //a.operator int() => 1 + int
    A  x = b;                //x(b):  b.operator A() => A(const &A) (编译器提供)
    A  y(a);                 //A(const A &) (编译器提供)
    x = b;                   //b.operator A() => A(int) => x.operator=() (编译器提供) => ~A()
    x = b + a;        //b.operator int() + a.operator int()=> A(int) => x.operator=()=> ~A()
    printf(“ % d % d”, a, (int)a);      //VFT地址    5
}
//若去掉A:: operator int() 中的virtual, printf ( ) 结果 ? 5   5
#include
using std::cout;
using std::endl;
class A
{
    int  a;
public:
    A(int x) { cout << "A(int x) called" << endl; a = x; }
    A(const A& x) { cout << "A(const A& x)  called" << endl; a = x.a; }
    A& operator = (const A& x) { cout << "A& operator =  called" << endl; a = x.a;  return *this; };
    operator int() const { cout << "operator int() const  called" << endl; return a + a; }
    ~A() {}
};
void  main()
{ 
    cout << "line 16:" << endl; A a(1), b(a);     //A(int), A(const A &)
    cout << "line 17:" << endl; A c = 1;          //A(1)
    cout << "line 18:" << endl; A d = a;          //A(a),不调用operator=() 这里是初始化不是赋值??
    cout << "line 19:" << endl; c = 1;            //A(1), operator=(), ~A()
    cout << "line 20:" << endl; d = a;            //operator=()
    cout << "line 21:" << endl; d = 1 + a;        //a.operator int(), A(int), operator=(), ~A()
}

Problem:

(1) 若没有构造函数 A::A(const A& x) ?

        调用编译器提供的默认的浅拷贝构造函数。

(2) 可以定义构造函数A::A(const A x) 吗?

        不能,报错:类的复制构造函数不能带有类型的参数

        这是因为如果用形参,编译器会生成一个临时的实参变量用于赋值,而它本身就要用到这个构造函数。假如通过了编译,也会造成死循环。

class A
{
        int  a;
public:
        A(int x) { a = x; }
        A& operator=(const A& x) { a = x.a;  return *this; };
        operator int() const { return a + a; }
        ~A() { }
};

void  main()
{
        A a(1), b(a);     //A(int), A(const A &)
        A c = 1;          //A(1)
        A d = a;          //A(a),不调用operator=()
        c = 1;            //A(1), operator=(), ~A()
        d = a;            //operator=()
        d = 1 + a;        //a.operator int(), A(int), operator=(), ~A()
}

重载new和delete

        运算符函数new和delete定义在头文件new.h中,new的参数就是要分配的内存的字节数。其函数原型为:

        extern void* operator new(unsigned bytes);

        extern void operator delete(void* ptr);

        在使用运算符new分配内存时,使用类型表达式而不是值表达式作为实参,编译程序会根据类型表达式计算内存大小并调用上述new函数。例如:new long[20] 。

        按上述函数原型重载,new和delete可重载为普通函数,也可重载为类的静态成员函数。

        OS的最小内存分配单位为节(16字节, 即使new char), 故重载new可先分得OS一大块内存,然后再分给需要单个字符的指针变量。

你可能感兴趣的:(C++从入门到入门,开发语言,c++)