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. Ionic实战 自动升级APP(Android版)

    Ionic 框架介绍 Ionic是一个基于Angularjs.可以使用HTML5构建混合移动应用的用户界面框架,它自称为是"本地与HTML5的结合".该框架提供了很多基本的移动用户 ...

  2. WIM镜像添加多语言支持

    起初的想法: intel 600p虽然速度一般,但pcie还是值得一试的.购买后发现原来的win7版本无法识别就找了KB2990941和KB3087873两个补丁,成功识别出了硬盘.期间通过dezor ...

  3. dialog

    function showDialog(){ var $by = $(window), obj = $('.dialog'), brsW = $by.width(), brsH = $by.heigh ...

  4. windows和linux中搭建python集成开发环境IDE——如何设置多个python环境

    本系列分为两篇: 1.[转]windows和linux中搭建python集成开发环境IDE 2.[转]linux和windows下安装python集成开发环境及其python包 3.windows和l ...

  5. 基于Picture Library创建的图片文档库中的上传多个文件功能(upload multiple files)报错怎么解决?

    复现过程 首先,我创建了一个基于Picture Library的图片文档库,名字是 Pic Lib 创建完毕后,我点击它的Upload 下拉菜单,点击Upload Picture按钮 在弹出的对话框中 ...

  6. Android操作HTTP实现与服务器通信(转)

    Android操作HTTP实现与服务器通信   本示例以Servlet为例,演示Android与Servlet的通信. 众所周知,Android与服务器通信通常采用HTTP通信方式和Socket通信方 ...

  7. 常用SQL[ORACLE]

        1.常用系统函数 2.常用sql语句 3.一些定义和关键字 4.需要注意点   1.常用系统函数 ↑ --decode decode(column,if_value,value,elseif_ ...

  8. ENode框架Conference案例分析系列之 - Quick Start

    前言 前一篇文章介绍了Conference案例的架构设计,本篇文章开始介绍Conference案例的代码实现.由于代码比较多,一开始就全部介绍所有细节,估计很多人接受不了,也理解不了.所以,我先进行一 ...

  9. 用rem实现WebApp自适应的优劣分析

    关于rem实现屏幕自适应布局的讨论还是比较多的,刚好我也看到使用rem实现自适应的web app,所以也来凑下热闹. 说起rem,免不了要联系到em.px,这里简单提提他们的定义和特点. 1. px: ...

  10. jsp的 javascript中 嵌套 html 注释

    看到公司的代码,我也是蛋疼了,各种乱. 还发现有很多的jsp的 javascript中 嵌套 html 注释, 这个可行? 我之前可是没用过. 后面查找各种资料发现,这个也是可行的,主要是为了兼容不支 ...