C++运算符重载

基本知识

重载的运算符是具有特殊名字的函数,他们的名字由关键字operator和其后要定义的运算符号共同组成。

运算符可以重载为成员函数和非成员函数。当一个重载的运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显式)参数比运算对象的数量少一个。

调用重载运算符函数

//非成员函数的等价调用
data1 + data2;//normal expression
operator+(data1,data2); // equal function call
//成员函数的等价调用
data1 += data2;//normal expression
data1.operator+(data2); // equal function call

可重载的运算符:+ - * / % ˆ & | ~ ! = < > += -= = /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , -> -> ( ) [ ]

无法被重载的运算符有: :: .* . ?:

Restrictions

  • The operators :: (scope resolution), . (member access), .* (member access through pointer to member), and ?: (ternary conditional) cannot be overloaded
  • New operators such as **, <>, or &| cannot be created
  • The overloads of operators &&, ||, and , (comma) lose their special properties: short-circuit evaluation and sequencing.
  • The overload of operator -> must either return a raw pointer or return an object (by reference or by value), for which operator -> is in turn overloaded.
  • It is not possible to change the precedence, grouping, or number of operands of operators.

选择作为成员或非成员的依据(引用C++ Primer)

  1. 赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员。
  2. 复合赋值运算符一般来说应该是成员,但并非必须。
  3. 改变对象状态的运算符或者与给定类型密切相关的运算符,如++、--和解引用运算符,通常应为成员。
  4. 具有对称性的运算符可能转换任意一端的运算对象,例如算数、相等性、关系和位运算等,通常应为普通的非成员函数。便于自动类型转换
  5. IO运算符必须是非成员函数(友元)。目的是为了与iostream标准库的输入输出兼容,否则左值为类的对象。

运算符实例类

class Sales_data{
public:
Sales_data(): units_sold(0), revenue(0.0) {}
Sales_data(const std::string &s):
bookNo(s), units_sold(0), revenue(0.0) {}
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
std::string isbn() const {
return bookNo;
}
Sales_data& combine(const Sales_data&);
double avg_price() const; //运算符重载函数申明
friend ostream& operator<<(ostream& os,const Sales_data& item);
friend istream& operator>>(istream& is,Sales_data& item); private:
string bookNo;
unsigned units_sold;
double revenue;
};
inline double Sales_data:: avg_price() const{
if(units_sold)
return revenue / units_sold;
else
return 0;
}

输入和输出运算符

重载输出运算符<<

通常情况下,第一个形参是一个非const的ostream对象的引用。ostream是非常量是因为写入流会改变其状态;而该形参是引用类型是因为无法直接复制一个ostream对象。

第二个形参一般是const&。因为我们希望避免复制实参,并且被打印的内用一般不会改变其内容。

为了与其他运算符保持一致,operator<<一般返回它的ostream形参。

ostream& operator<<(ostream& os,const Sales_data& item)
{
os<<item.isbn()<<" "<<item.units_sold<<" "
<<item.revenue<<" "<<item.avg_price();
return os;
}

输出运算符尽量减少格式化操作

输出运算符主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符。

输入输出运算符必须是非成员函数,一般申明为友元

与iostream标准库兼容。

重载输入运算符>>

通常,第一个形参是流的引用,第二个是将要读到的(非const)对象引用。函数返回给定流的引用。

istream& operator>>(istream& is,Sales_data& item)
{
double price;
is>> item.bookNo >> item.units_sold >> price;
if(is) //检查输入是否成功
item.revenue = item.units_sold * price;
else
item = Sales_data(); //输入失败,对象被赋予默认的状态
}

当读取操作发生错误时,输入运算符应该负责从错误中恢复。

算术和关系运算符

通常情况下,我们把算术和关系运算符定义成非成员函数以允许对左侧或者右侧的运算对象进行转换。因为这些运算符一般不需要改变运算对象的状态,所以形参都是const&。

Sales_data operator+(const Sales_data& lhs,const Sales_data& rhs)
{
Sales_data sum = lhs;
sum += rhs;
return sum;
}

如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合运算符来实现算术运算符。

相等运算符

用来检验两个对象是否相等。

inline bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
return !(lhs == rhs);
}

相等运算符设计准则:

  • 如果一个类包含判断两个对象是否相等的操作,显然定义成operator而非一个普通命名函数。这使得用户不必记忆新的函数名,同时定义了运算符之后也更容易使用标准库容器和算法。
  • 如果类定义了operator==,则该运算符应该能判断一组给定对象中是否含有重复数据。
  • 通常情况下,==运算符应该具有传递性。
  • 当类定义了==,应当也为类定义!=。
  • ==运算符和!=运算符中的一个应该把工作委托给另一个。意味着其中一个是负责实际比较对象的工作,另一个负责调用。

关系运算符

定义了相等运算符的类也常常(但不总是)包含关系运算符。特别的是,因为关联容器和一些算法要用到<,所以定义operator<会比较有用。

通常情况下关系运算符应该

  1. 定义顺序关系,令其与关联容器对关键字的要求一致(与考试内容无关,详情见C++ Primer);并且
  2. 如果类同时有运算符时,则定义一种关系令其与保持一致,特别的两个对象!=时,那么一个对象应该<另外一个。

如果存在唯一一种逻辑可靠的<定义,则应该考虑为类定义<运算符。如果类同时存在运算符,当且仅当<的定义和产生的结果一致时才定义<

此处的实例不存在逻辑可靠的关系运算,故引用其他例子。

关系运算符一般定义为非成员函数。

Typically, once operator< is provided, the other relational operators are implemented in terms of operator<。

inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return rhs < lhs;}
inline bool operator<=(const X& lhs, const X& rhs){return !(lhs > rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !(lhs < rhs);}

赋值运算符

T& operator=(const T& other) // copy assignment
{
if (this != &other) { // self-assignment check expected
if (/* storage cannot be reused (e.g. different sizes) */)
{
delete[] mArray; // destroy storage in this
/* reset size to zero and mArray to null, in case allocation throws */
mArray = new int[/*size*/]; // create storage in this
}
/* copy data from other's storage to this storage */
}
return *this;
}

复合赋值运算

一般倾向于把包括复合赋值在内的所有赋值运算都定义在类的内部。为了与内置类型的复合赋值保持一致,一般返回左侧对象的引用。

Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}

下标运算符

表示容器的类通常可以通过元素在容器中的位置访问元素,一般会定义operator[]。

下标运算符必须是成员函数

一般会定义两个版本,一个返回普通引用,另一个是类的常成员并返回常量引用。

class  StrVec
{
public :
string& operator[](size_t n)
{ return elements[n];}
const string& operator[](size_t n) const
{ return elements[n];}
private :
string *elements;
};

递增和递减运算符

递增和递减运算符改变的是操作对象的状态,建议设定为成员函数。

应该同时定义前置和后置版本的递增和递减运算符

定义前置递增/递减运算符

template<class T>
class T{
public :
T& operator++();
T& operator--();
private:
int x;
};
template<class T>
T& T::operator++()
{
++x;// actual increment takes place here
return *this;
}
template<class T>
T& T::operator--()
{
--x;// actual decrement takes place here
return *this;
}

为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。

区分前置和后置运算符

后置版本接受一个额外的(不被使用)int类型的形参。

template<class T>
class T{
public :
T operator++(int);
T operator--(int);
private:
int x;
};
template<class T>
T T::operator++()
{
T tmp = *this; // copy
++(*this); // pre-increment
return *tmp; // return old value
}
template<class T>
T T::operator--()
{
T tmp = *this; // copy
--(*this); // pre-increment
return *tmp; // return old value
}

为了与内置版本保持一致,后置运算符应该返回对象的原值,返回的是一个值而非引用。

参考资料:

C++运算符重载的更多相关文章

  1. C++ 运算符重载时,将运算符两边对象交换问题.

    在C++进行运算符重载时, 一般来讲,运算符两边的对象的顺序是不能交换的. 比如下面的例子: #include <iostream> using namespace std; class ...

  2. C#高级编程笔记2016年10月12日 运算符重载

    1.运算符重载:运算符重重载的关键是在对象上不能总是只调用方法或属性,有时还需要做一些其他工作,例如,对数值进行相加.相乘或逻辑操作等.例如,语句if(a==b).对于类,这个语句在默认状态下会比较引 ...

  3. 标准C++之运算符重载和虚表指针

    1 -> *运算符重载 //autoptr.cpp     #include<iostream> #include<string> using namespace std ...

  4. python运算符重载

    python运算符重载就是在解释器使用对象内置操作前,拦截该操作,使用自己写的重载方法. 重载方法:__init__为构造函数,__sub__为减法表达式 class Number: def __in ...

  5. PoEduo - C++阶段班【Po学校】-Lesson03-5_运算符重载- 第7天

    PoEduo - Lesson03-5_运算符重载- 第7天 复习前面的知识点 空类会自动生成哪些默认函数 6个默认函数    1  构造  2  析构   3  赋值  4 拷贝构造  5 oper ...

  6. 不可或缺 Windows Native (24) - C++: 运算符重载, 自定义类型转换

    [源码下载] 不可或缺 Windows Native (24) - C++: 运算符重载, 自定义类型转换 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 运算符重载 自 ...

  7. 我的c++学习(8)运算符重载和友元

    运算符的重载,实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该运算符时就调用此函数来行使运算符功能.这个函数叫做运算符重载函数(常为类的成员函数). 方法与解释 ◆ 1.定义运 ...

  8. c/c++面试题(6)运算符重载详解

    1.操作符函数: 在特定条件下,编译器有能力把一个由操作数和操作符共同组成的表达式,解释为对 一个全局或成员函数的调用,该全局或成员函数被称为操作符函数.该全局或成员函数 被称为操作符函数.通过定义操 ...

  9. 实验12:Problem H: 整型数组运算符重载

    Home Web Board ProblemSet Standing Status Statistics   Problem H: 整型数组运算符重载 Problem H: 整型数组运算符重载 Tim ...

随机推荐

  1. 最小生成树 prime poj1287

    poj1287 裸最小生成树 代码 #include "map" #include "queue" #include "math.h" #i ...

  2. bat获取所有的参数

    bat默认只能获取到1-9个参数,分别用%1 %2 ... %9引用,如果传给bat的参数大于9个,就必须用shift. 工作需要,要写个bat脚本,获取所有的参数,再将所有的参数传给Java,代码如 ...

  3. BOM

    一.window对象1.全局作用域全局变量不能通过delete操作删除,而直接在window对象上定义的属性可以 var a = 1; delete a; console.log(a); window ...

  4. 工作中那些提高你效率的神器(第一篇)_Everything

    引言 无论是工作还是科研,我们都希望工作既快又好,然而大多数时候却迷失在繁杂的重复劳动中,久久无法摆脱繁杂的事情. 你是不是曾有这样一种想法:如果我有哆啦A梦的口袋,只要拿出神奇道具就可解当下棘手的问 ...

  5. mysql如何在一个字段后面加个字符?

    update city set 字段=concat(字段,'内容');

  6. gitlab使用有感之坚持

    当老师请大二的学弟教我们使用这个软件的时候, 当时就吓坏我了,感觉好高大上的样子,全英文的,还有什么阿里云的服务器用来代码管理:心情一下子就不好了,感觉自己懂得东西好少啊,比学弟懂得都少,跟不上时代的 ...

  7. PreparedStatement的应用

    package it.cast.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql ...

  8. ASP.NET Aries 开源开发框架:开发指南(一)

    前言: 上周开源了Aries开发框架后,好多朋友都Download了源码,在运行过程里,有一些共性的问题会问到. 所以本篇打算写一下简单的开发指南,照顾一下不是太看的懂源码的同学,同时也会讲解一下框架 ...

  9. .NET Core中遇到奇怪的线程死锁问题:内存与线程数不停地增长

    一个 asp.net core 站点,之前运行在Linux 服务器上,运行一段时间后有时站点会挂掉,在日志中记录很多“EMFILE too many open files”的错误: Microsoft ...

  10. 【性能为王】从PHP源码剖析array_keys和array_unique

    之前在[译]更快的方式实现PHP数组去重这篇文章里讨论了使用array_flip后再调用array_keys函数替换直接调用array_unique函数实现数组去重性能较好.由于原文没有给出源码分析和 ...