[C++] 操作符重载

1 定义

重载操作符(Overloaded Operator) 是有特殊名称的函数,形式为: operatorX , 例如:

Sales_item operator+(const Sales_item&, const Sales_item&);

可以重载的操作符有42个:

+ - * / % ^
& | ~ ! , =
< > <= >= ++ – (自減)
<< >> == != && ||
+= -= /= %= ^= &=
|= *= <<= >>= [] ()
-> ->* new new [] delete delete []

不可重载的操作符有4个:

:: .* . :?

操作符对于內建类型的含义不可改变,重载操作符必须有一个操作数为类类型

// 错误: 不允许重定义应用于int类型的操作符
int operator+(int, int);

函数调用操作符 ( ) 除外,操作符有多少个操作数,重载操作符就要有多少个参数。

2 输入和输出操作符

2.1 重载输出操作符

重载输出操作符的一般写法:

// general skeleton of the overloaded output operator
ostream& operator<< (ostream& os, const ClassType &object)
{
    // any special logic to prepare object

    // actual output of members
    os << // ... ;

    // return ostream object
    return os;
}

第一个参数是 ostream&, 因为 ostream 对象不可复制,所以参数os为引用。因为写入会改变参数状态,所以为 non-const
第二个参数是类类型的 const reference, 使用引用是为了避免复制,使用const是因为打印对象一般不应该改变该对象,而且,使用const参数可以使用一种定义同时接收 const 和非const对象,这样比较灵活。
返回类型是 ostream&, 它的值一般是输出操作符操作的对象。

例如,Sales_item 输出操作符可以这样写:

ostream& operator << (ostream& out, const Sales_item& s)
{
    out << s.isbn << "\t" << s.units_sold 
        << "\t" << s.revenue << "\t" << s.avg_price();
    return out;
}

一般而言,输出操作符应该仅打印出对象的内容即可,不应该打印出newline, 让用户控制输出的细节。

2.2 重载输入操作符

重载输入操作符的一般写法和重载输出操作符相似:

istream& operator>> (istream& in, ClassType &object)
{
    // any special logic to prepare object

    // actual output of members
    in >> // ... ;

    // return ostream object
    return in;
}

因为重载输入操作符的目的是将数据读到对象里去,因为类的对象会发生改变,所以第二个参数必须为 non-const ,

输入和输出操作符有一个重要的差别: 输入操作符必须处理可能的错误及end-of-file

Sales_item 操作符:

istream& operator>>(istream& in, Sales_item& s)
{
    double price;
    in >> s.isbn >> s.units_sold >> price;
    // check that the inputs succeeded
    if (in)  // 测试流,如果输入成功
        s.revenue = s.units_sold * price;
    else
        s = Sales_item(); // 如果输入失败,则使用默认构造函数重置整个对象为默认状态
    return in;
}

设计输入操作符时,必须考虑错误恢复。

IO 操作符不能是类的成员函数,原因是,如果是成员函数,那么操作符的左操作数必须为类的对象:

// 错误!, 和正常使用输出操作符的方式相反!
// if operator << is a member of Sales_item
Sales_item item;
item << cout;

因为IO操作符操作的数据一般是类的private对象,所以IO操作符一般必须是类的友元函数。

3 算术和关系操作符

通常定义算术和关系操作符为非成员函数。

+ 操作符重载的例子:

// assumes that both objects refer to the same isbn
Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs)
{
    Sales_item ret(lhs);  // copy lhs into a local object that we'll return
    ret += rhs;           // add in the contents of rhs
    return ret;           // return ret by value
}

第一个参数和第二个参数都为const reference ,返回一个新的对象,为与内建操作符一致,加操作返回一个右值 rvalue。
算术操作符一般返回两个操作数运算结果,存在一个局部变量中,不能返回引用。

一般重载== 操作符,以比较两个对象的数据是否相同,如果重载了==, 也应该重载 !=, 这两个操作符的定义都可以调用对方的实现,例如如下的代码:

inline bool operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    // must be made a friend of Sales_item
    return lhs.units_sold == rhs.units_sold &&
    lhs.revenue == rhs.revenue &&
    lhs.same_isbn(rhs);
}
inline bool operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs); // != defined in terms of operator==
}

如果定义了==!= 一般也会定义 operator< , 是否应该实现根据具体情况而定。

4 赋值操作符

  • 一个类中如果没有用户自己定义的赋值操作符,编译器会自动帮忙合成一个。
  • 赋值操作符可重载:
// illustration of assignment operators for class string
class string {
public:
   string& operator=(const string &);      // s1 = s2;
   string& operator=(const char *);        // s1 = "str";
   string& operator=(char);                // s1 = 'c';
   // ....
};
// 用法:
string car ("Volks");
car = "Studebaker"; // string = const char*
string model;
model = 'T'; // string = char
  • 赋值操作符必须为类的成员函数。
  • 赋值操作符必须返回 *this 的引用:
// assumes that both objects refer to the same isbn
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
   units_sold += rhs.units_sold;
   revenue += rhs.revenue;
   return *this;
}

5 下标操作符 operator[]

下标操作符必须定义为成员函数。
需要检索元素的容器类型的类应该提供重载下标操作符,应该要有两个版本的:const和 non-const。 一个返回 const reference, 一个返回 non-const reference。

6 成员访问操作符

->

必须为非静态成员函数,无参,返回值用于执行成员查询。
如果返回值是类类型的另一个对象,而不是指针,那么后续成员查找也由操作符 - >函数处理。 这被称为“下钻行为”。 语言将操作符 - >调用链接在一起,直到最后一个返回一个指针。
(If the return value is another object of class type, not a pointer, then the subsequent member lookup is also handled by an operator-> function. This is called the “drill-down behavior.” The language chains together the operator-> calls until the last one returns a pointer.)

struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print "333"
}

->*

这个和正常操作符+, -, / 一样可以被重载,这个操作符并没有特殊的地方。
This one is only tricky in that there is nothing special about it. The non-overloaded version requires an object of pointer to class type on the left-hand side and an object of pointer to member type on the right. But when you overload it, you can take any arguments you like and return anything you want. It doesn’t even have to be a nonstatic member.

In other words, this one is just a normal binary operator like +, -, and /.

.*.

这两个不能重载。
These cannot be overloaded. There is already a built-in meaning when the left-hand side is of class type. Perhaps it would make a little sense to be able to define them for a pointer on the left-hand side, but the language design committee decided that would be more confusing than useful.

Overloading ->, ->, ., and . can only fill in cases where an expression would be undefined, it can never change the meaning of an expression that would be valid with no overloading.

7 递增和递减操作符

递增和递减操作符有前置和后置两种(prefix, postfix)两种:

class CheckedPtr {
public:
    CheckedPtr(int* b, int* e):beg(b), end(e), curr(b) { } 
    // 重载前置递增操作符
    CheckedPtr& operator++() {
        if (curr >= end)
            throw std::out_of_range("increment past the end of CheckedPtr");
        ++curr;
        return *this;
    }
    // 重载前置递减操作符
    CheckedPtr& operator--() {
        if (curr <= beg)
            throw std::out_of_range("decrement past the beginning of CheckedPtr");
        --curr;
        return *this;
    }
    // 重载后置递增操作符:调用了前置递增操作符
    CheckedPtr operator++(int) {
        CheckedPtr ret(*this);
        ++*this;
        return ret;
    }
    // 重载后置递减操作符:调用了前置递减操作符
    CheckedPtr operator--(int) {
        CheckedPtr ret(*this);
        --*this;
        return ret;
    }
// 因为重载输出操作符想要访问私有成员,所以声明为friend
friend std::ostream& operator<<(std::ostream& os, const CheckedPtr& p);
private:
    int *beg;
    int *end;
    int *curr;
};
// 重载输出操作符
std::ostream& operator<<(std::ostream& os, const CheckedPtr& p) {  
    os << *(p.curr);
    return os;
}
int _tmain(int argc, char* argv[])
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
        CheckedPtr p1(arr, arr+3);
    p1++;
    p1++;
    cout << p1 << endl;

    system("pause");
    return 0;
}
// 结果输出 3

要重载的话,一般是4个操作符一起重载,为了区分前置和后置,后置重载时多加了一个int参数,编译器看到就知道是后置,将这个参数设0,这个参数一般不应使用,摆设而已。

后置操作符返回的是不是引用,这个和前置不同。
(此外虽然类CheckedPtr的变量三个都是指针,却并没有自定义的拷贝构造函数,不明白。)
这两种操作符可显示调用:

CheckedPtr parr(ia, ia + size);   // iapoints to an array of ints
parr.operator++(0);               // call postfix operator++
parr.operator++();                // call prefix operator++

某个项目中看到的一段代码,人家写了很多虚拟重载操作符。

class CLimitlessDataItem
{
public:
   virtual ~CLimitlessDataItem() {}
   virtual bool operator<( const CLimitlessDataItem& ) const
   {
      throw CApplicationException(g_exc_general_exception, "Unsupported method: <", __FILE__, __LINE__);
   }
   virtual bool operator>( const CLimitlessDataItem& ) const
   {
      throw CApplicationException(g_exc_general_exception, "Unsupported method: >", __FILE__, __LINE__);
   }
   virtual bool operator<=( const CLimitlessDataItem& ) const
   {
      throw CApplicationException(g_exc_general_exception, "Unsupported method: <=", __FILE__, __LINE__);
   }
   virtual bool operator>=( const CLimitlessDataItem& ) const
   {
      throw CApplicationException(g_exc_general_exception, "Unsupported method: >=", __FILE__, __LINE__);
   }
   virtual bool operator!=( const CLimitlessDataItem& ) const
   {
      throw CApplicationException(g_exc_general_exception, "Unsupported method: !=", __FILE__, __LINE__);
   }
   virtual bool operator==( const CLimitlessDataItem& ) const
   {
      throw CApplicationException(g_exc_general_exception, "Unsupported method: ==", __FILE__, __LINE__);
   }
};

[1] C++ Primer Chapter 14. Overloaded Operations and Conversions
[2] https://stackoverflow.com/questions/8777845/overloading-member-access-operators-c/8778050

猜你喜欢

转载自blog.csdn.net/ftell/article/details/80307663