C++运算符重载
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)
- 赋值(=)、下标([])、调用(())和成员访问箭头(->)运算符必须是成员。
- 复合赋值运算符一般来说应该是成员,但并非必须。
- 改变对象状态的运算符或者与给定类型密切相关的运算符,如++、--和解引用运算符,通常应为成员。
- 具有对称性的运算符可能转换任意一端的运算对象,例如算数、相等性、关系和位运算等,通常应为普通的非成员函数。便于自动类型转换
- 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<会比较有用。
通常情况下关系运算符应该
- 定义顺序关系,令其与关联容器对关键字的要求一致(与考试内容无关,详情见C++ Primer);并且
- 如果类同时有运算符时,则定义一种关系令其与保持一致,特别的两个对象!=时,那么一个对象应该<另外一个。
如果存在唯一一种逻辑可靠的<定义,则应该考虑为类定义<运算符。如果类同时存在运算符,当且仅当<的定义和产生的结果一致时才定义<
此处的实例不存在逻辑可靠的关系运算,故引用其他例子。
关系运算符一般定义为非成员函数。
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
}
为了与内置版本保持一致,后置运算符应该返回对象的原值,返回的是一个值而非引用。
参考资料:
- http://stackoverflow.com/questions/4421706/operator-overloading/4421708#4421708
- http://en.cppreference.com/w/cpp/language/operators
C++运算符重载的更多相关文章
- C++ 运算符重载时,将运算符两边对象交换问题.
在C++进行运算符重载时, 一般来讲,运算符两边的对象的顺序是不能交换的. 比如下面的例子: #include <iostream> using namespace std; class ...
- C#高级编程笔记2016年10月12日 运算符重载
1.运算符重载:运算符重重载的关键是在对象上不能总是只调用方法或属性,有时还需要做一些其他工作,例如,对数值进行相加.相乘或逻辑操作等.例如,语句if(a==b).对于类,这个语句在默认状态下会比较引 ...
- 标准C++之运算符重载和虚表指针
1 -> *运算符重载 //autoptr.cpp #include<iostream> #include<string> using namespace std ...
- python运算符重载
python运算符重载就是在解释器使用对象内置操作前,拦截该操作,使用自己写的重载方法. 重载方法:__init__为构造函数,__sub__为减法表达式 class Number: def __in ...
- PoEduo - C++阶段班【Po学校】-Lesson03-5_运算符重载- 第7天
PoEduo - Lesson03-5_运算符重载- 第7天 复习前面的知识点 空类会自动生成哪些默认函数 6个默认函数 1 构造 2 析构 3 赋值 4 拷贝构造 5 oper ...
- 不可或缺 Windows Native (24) - C++: 运算符重载, 自定义类型转换
[源码下载] 不可或缺 Windows Native (24) - C++: 运算符重载, 自定义类型转换 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 运算符重载 自 ...
- 我的c++学习(8)运算符重载和友元
运算符的重载,实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该运算符时就调用此函数来行使运算符功能.这个函数叫做运算符重载函数(常为类的成员函数). 方法与解释 ◆ 1.定义运 ...
- c/c++面试题(6)运算符重载详解
1.操作符函数: 在特定条件下,编译器有能力把一个由操作数和操作符共同组成的表达式,解释为对 一个全局或成员函数的调用,该全局或成员函数被称为操作符函数.该全局或成员函数 被称为操作符函数.通过定义操 ...
- 实验12:Problem H: 整型数组运算符重载
Home Web Board ProblemSet Standing Status Statistics Problem H: 整型数组运算符重载 Problem H: 整型数组运算符重载 Tim ...
随机推荐
- Android app应用多语言切换功能实现
最近在做一个多语言切换的功能,类似于微信的语言切换,搜了下资料基本上都是以下这种: 1. 实现的效果 和微信类似,在设置界面打开切换语言的界面,选择语言后重启 HomeActivity,语言切换完成, ...
- springmvc入门的第一个小例子
今天我们探讨一下springmvc,由于是初学,所以简单的了解一下 springmvc的流程,后续会持续更新... 由一个小例子来简单的了解一下 springmvc springmvc是spring框 ...
- 使用PowerShell找出具体某个站点所使用的模板(Web Template)名称?
$web = get-spweb –identity http://servername/sites/site/web #得到站点的对象 $web.WebTemplate #得到WebTemplate ...
- 安装SQL Server2016正式版
安装SQL Server2016正式版 今天终于有时间安装SQL Server2016正式版,下载那个安装包都用了一个星期 安装包可以从这里下载: http://www.itellyou.cn/ ht ...
- Nova PhoneGap框架 第五章 文件结构
一个好的项目架构必然有着合理的文件结构,如果一个项目的文件组织混乱,那么可以断言一定是项目架构有问题. 合理的文件结构能够帮你更清晰的管理你的文件,并且当需要添加新的文件的时候,你的程序员很清楚应该加 ...
- LOMA280保险原理读书笔记
LOMA是国际金融保险管理学院(Life Office Management Association)的英文简称.国际金融保险管理学院是一个保险和金融服务机构的国际组织,它的创建目的是为了促进信息交流 ...
- [ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)
我们知道,在 MVC 中每个请求都会提交到 Controller 进行处理.Controller 是和请求密切相关的,它包含了对请求的逻辑处理,能对 Model 进行操作并选择 View 呈现给用户, ...
- 记一个简单的保护if 的sh脚本
真是坑爹,就下面的sh,竟然也写了很久! if [ `pwd` != '/usr/xx/bin/tomcat' ] then echo "rstall is not allowed in c ...
- xamarin 手机顶部状态栏
修改显示xamarin开发的App的手机顶部状态栏, 步骤一:在项目UWP上的“引用”里右键“添加引用”,选择->Universal Windows->Windows Mobile Ext ...
- UIColor 使用起来的坑
我们一般会用 UIColor 的RGB来生成颜色: [UIColor colorWithRed:220/255 green:220/255 blue:220/255 alpha:1.0];//这样生成 ...