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. *HDU 1028 母函数

    Ignatius and the Princess III Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K ...

  2. 开源免费的HTML5游戏引擎

    青瓷引擎的成长 青瓷引擎自2015年4月项目启动开始,7月首次亮相2015年ChinaJoy,便得到业界的极大关注,随后开启限量测试,收到数百个开发者团队的试用申请及反馈,期间经历了18个内测版本,完 ...

  3. CI框架,双层弹出框的样式实现

    在弹出的主页面上,写一个隐藏的悬浮的div 通过标记使他显示,通过计数器使他关闭 部分代码: <div id="common_msg"></div>//主页 ...

  4. 如何使用Goolge Timeline工具

    网上中文的资料版本比较老,找到一个新版本的英文介绍,翻一下,原文:https://developers.google.com/web/tools/chrome-devtools/profile/eva ...

  5. Microsoft .NET Framework 4.0.3版下载

    适用于 Microsoft .NET Framework 4 的更新 4.0.3,其中包含一系列新增功能,用于满足高端客户的功能需求和重要 .NET Framework 方案的需求. http://w ...

  6. ABP框架理论学习之Debugging

    返回总目录 所有的官方ABP nuget包都是支持GitLink的,这意味着你可以在项目中轻松地调试所有的以Abp为前缀的Nuget包. 要开启这项支持,"启用源服务器支持"选项应 ...

  7. 【腾讯Bugly干货分享】Redex初探与Interdex:Andorid冷启动优化

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583b9e3ee8992c2c2df6e6ac 导语 早在去年10月份,face ...

  8. java中文乱码解决之道(八)-----解决URL中文乱码问题

    我们主要通过两种形式提交向服务器发送请求:URL.表单.而表单形式一般都不会出现乱码问题,乱码问题主要是在URL上面.通过前面几篇博客的介绍我们知道URL向服务器发送请求编码过程实在是实在太混乱了.不 ...

  9. Appium的安装-MAC平台

    其实Appium的安装方式主要有两种: 1)自己安装配置nodejs的环境,然后通过npm进行appium的安装 2)直接下载官网提供的dmg进行安装,dmg里面已经有nodejs的环境和appium ...

  10. C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword)

    C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword) +BIT祝威+悄悄在此留下版了个权的信息说: C#申请一 ...