“c++类——运算符重载(一)”

(一)引言和概念:

C++预定义中的运算符的操作对象只局限于基本的内置数据类型。

但实际上,对于许多用户自定义类型(如类、结构等)也需要类似的运算操作。这时就必须在C ++ 中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。

运算符重载的实质是特殊的函数重载或函数多态。定义运算符重载函数,目的在于让c++编译器在遇到该运算符时能够用同名的运算符来完成不同的基本操作。要重载运算符,需要使用运算符重载函数的特殊函数形式。

运算符重载提供了C ++ 的可扩展性,也是C ++ 最吸引人的特性之一。

本文主要是理论知识,详细代码会在下一节展示。

(二)运算符重载的规则:

  • 运算符限制:

    可以重载绝大部分的运算符:

    可重载类型 具体运算符
    双目算数运算符 + (加),-(减),*(乘),/(除),% (取模)
    关系运算符 ==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于)
    逻辑运算符 ||(逻辑或),&&(逻辑与),!(逻辑非)
    单目运算符 + (正),-(负),*(指针),&(取地址)
    自增自减运算符 ++(自增),--(自减)
    位运算符 | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
    赋值运算符 =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
    空间申请与释放 new, delete, new[ ] , delete[]
    其他运算符 ()(函数调用),->(成员访问),,(逗号),[](下标)

    但不能重载下面的运算符:

    . 类成员选择运算符

    .* 成员指针运算符

    (这两个运算符不能被重载是为了保证访问成员的功能不被改变)

    :: 作用域运算符

    sizeof 长度运算符

    (这两个运算符的运算对象就是类型,而不是变量或者一般式,不具备重载的特征)

    ?: 条件表达式运算符

    # 预处理符号

    另外,只有“=”的重载函数不能被继承。

    在c++11中,typeid、const_cast、dynamic_cast、reinterpret_cast、static_cast的强制类型转换运算符也不能进行重载。

  • 运算符重载要保持原有的基本语义不变,不能改变用于内部对象时该运算符的含义。

  • 重载运算符限制在c++语言中已有的运算符范围内的允许重载的运算符之中,不允许创建新的运算符。

  • 被重载的运算符的优先级、结合性、操作数的个数和语法结构均不变。因此,不允许在重载的运算符函数中使用默认参数。

  • 编译器对运算符重载的函数选择,遵循函数重载的选择规则(函数契合优先级保持不变,因为运算符重载本质上是函数重载)。

  • 运算符重载函数只能和用户自定义类型的对象一起使用或者和内部类型的对象混用。即其操作对象至少有一个应该是类对象或类对象的引用。

  • 另外,虽然没有强制规定,但是重载运算符的时候其功能应当和原有功能类似。

(三)运算符重载的定义:

(friend) 返回类型 类名:: operator op(参数表) { 函数结构体 }

重载为友元函数在开头加friend关键字,否则无需添加。

返回类型可以是任何有效类型,但通常情况下是操作类的对象及其引用。

类名是在类外定义时需要添加的,如果是类内定义可以不用添加。

operator是关键字,指定其为运算符重载函数。

op为运算符,也是实际的函数名。

参数表为形参表,其参数的个数和重载的运算符操作数的个数有关。

(四)运算符重载的形式:

运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。

非成员函数通常是友元函数,因为大多数时候重载运算符需要访问类的私有数据。

当然也可以把一个运算符作为一个非成员、非友元函数重载。但是,这样的运算符函数访问类的私有和保护成员时,必须使用类的公有接口中提供的设置数据和读取数据的函数,调用这些函数时会降低性能——所以一般都会设置为友元函数。

  • 重载为成员函数:

    当运算符重载为类的成员函数,其参数个数比原来的操作数少一个(后置单目运算符除外),原因在于成员函数会使用this指针隐式访问类的一个对象,可以充当运算符函数最左边的操作数。

    因此,成员函数的运算符重载函数的左操作数一定是运算符类的一个类对象或者类对象的引用。

    1. 双目运算符重载为类的成员函数时,函数参数表只显式说明一个参数,该参数作为运算符右操作数参与运算。
    2. 前置单目运算符重载为类的成员函数时,没有形参,不需要显式说明。
    3. 后置单目运算符重载为类的成员函数时,函数需要一个整型形参。(不一定要使用这个形参,但是需要这个整型形参来区别前置和后置运算符)
  • 重载为友元函数:

    没有隐含的this指针,因此操作数的个数没有发生变化,所有操作数都需要通过参数表来指定和传递,参数从左到右和操作数一一对应。

  • 两种重载形式的联系和区别:

    本质上,是因为成员函数能够有this指针,友元函数没有this指针。

    两者的使用方法相同。但是实现方式不同,传递参数不同,应用场景也不同。

  • 两种重载形式的不同特点:

    • 成员函数:

      核心:如果左操作数只能是类对象的话一般使用重载成员函数。

      1. 一般情况下,单目运算符重载为类的成员函数。(唯一操作数刚好可以是this所指向的类对象)

      2. 双目运算符 "=" 、"()" 、"[]" 、"->" 只能被重载为类的成员函数。(左操作数一定是类的对象,而且c++对这四个运算符有强制规定)

      3. 类型转换函数只能被定义为一个类的成员函数而不能是友元函数。(因为转换的主体只能是本类的对象)

      4. 如一个运算符需要修改对象的状态,则重载为成员函数最好。

        (原因解释:此时若重载为友元函数,参数只能是引用参数,值传递无法影响原对象,指针会导致二义性,不如使用成员函数重载)

    • 友元函数:

      核心:如果成员函数*this要作为右操作数,,只能将operator作为友元函数。

      1. 一般情况下,双目运算符最好重载为类的友元函数。(因为左操作数一般不一定是类对象,而且运算符常常需要可交换性)
      2. 双目运算符<<和>>只能被重载为类的友元函数。(其左操作数通常不是该类对象)
      3. 当需要重载运算符具备可交换性时,选择重载为友元函数。
      4. 如果左右操作符数据类型不同,选择重载为友元函数。
      5. 如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,即需要对左边的操作数进行隐式的类型转换,该运算符重载函数必须作为友元函数实现。

(五)运算符重载的特殊情况:

关系运算符的重载是成对的,<和>、<=和>=、==和!=要成对进行重载。

&& ||和 , 运算符在重载时对算子的操作顺序无法保留(原有的短路求值属性消失了),不推荐对这三个运算符进行重载。


“c++类——运算符重载(一)”
http://example.com/2023/03/24/c++类——运算符重载(一)/
作者
07xiaohei
发布于
2023年3月24日
许可协议