C++重载运算简介
本文基于《C++ Primer(第5版)》中14章和《More Effective C++》条款7,整理而成。
其实写这篇博客之前,内心还是很忐忑的,因为,博主的水平很有限,视野比较窄,要是在理解书的过程中有了偏差,给读到这篇博客的人以错误的认识,那罪过就大了。再次声明本文仅是简介,若是有错误的地方欢迎留言指出。
个人认为运算符最重要的是:使用与内置类型一致的含义。
一、基本概念
- 当运算作用于类类型的运算对象时,可以通过运算符重载重新定义该运算符的含义。
重载的运算符是具有特别名字的函数,它们的名字由关键字 operator和其后要定义的运算符号共同组成。其包括:返回类型、参数列表以及函数体。
重载运算符函数的参数数量与该运算符作用的运算对象一样多,如:一元运算符有一个参数,二元运算符有两个。这里值得注意的是,当运算符函数时成员函数时,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此,成员运算符函数(显式)参数数量比运算符的运算对象总数少一个,但实际上总数不变。
1、某些运算符不应该被重载
不能被重载的运算符有 :
. | .* | :: | ?: |
new | delete | sizeof | typeid |
static_cast | dynamic_cast | const_cast | reinterpret_cast |
能被重载但最好不要重载的运算符有:
(1)逻辑与&&,逻辑或||
逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时,才会计算右侧运算对象的值,这种策略称为短路求值(short-circuit evaluation)。
《More Effective C++》给出的例子,若重载operator &&,下面的这个式子:
if(expression1 && expression2) ...
会被编译器视为以下两者之一:
//假设operator&& 是个成员函数
if(expression1.operator&&(expression2)) ... //假设operator&& 是个全局函数
if(operator&&(expression1,expression2)) ...
即无法保留内置运算符的短路求值属性,两个运算对象总是被求值。
(2)逗号 ,
逗号操作符的求值顺序是从左往右依次求值,逗号运算符的真正的结果是右侧表达式的值。若是打算重载逗号运算符,就必须模仿这样的行为,但是,无法执行这些必要的模仿。求值顺序和返回结果同时满足才行。
2、选择成员函数或者非成员函数
当我们定义重载的运算符是,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数。(有关成员函数和非成员函数,见成员函数与非成员函数的抉择)。下面有些准则有助于我们选择:
(1)赋值(=)、下标([ ])、调用( ( ))、和成员访问箭头(->)运算符必须是成员;
(2)复合赋值运算符一般来说应该是成员,但并非是必须的;
(3)改变对象状态的运算符或者与给定类型密切相关的运算符,如,递增、递减和解引用运算符,通常是成员;
(4)具有对称性的运算符可能转换任意一段的运算符对象,如,算术、相等性、关系和位运算等,因此它们通常应该是普通的非成员函数。
二、各种重载
1、重载输出运算符<<和输入运算符>>
(1)重载输出运算符<<
通常情况下,输出运算符的第一个形参是一个非常量ostream对象的引用,之所以ostream是非常量是因为向流写入内容会改变其状态,而该形参是引用时因为我们无法直接复制一个ostream对象;第二个形参一般是一个常量的引用,是引用的原因是希望避免复制实参,而之所以该形参可以是常量是因为(通常)打印对象不会改变对象的内容。另外,为了与其他输出运算符保持一致,返回ostream形参。
ostream &operator<<(ostream &os,const Sales_data &item)
{
os<<item.isbn()<<" "<<item.avg_price();
return os;
}
(2)重载输入运算符>>
通常,输入运算符的第一个形参是运算符将要读取的流的引用,第二形参是将要读入的(非常量)对象的引用。该运算符通常会返回某个给定流的引用。第二个形参之所以必须是个非常量时因为输入运算符本身的目的就是将数据读入到这个对象中。
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();
}
return is;
}
输入时的可能发生以下错误:
当流含有错误类型的数据时读取操作可能失败;当读取操作达到文件末尾或者遇到输入流的其他错误是也会失败。
输入运算符应该负责从错误中恢复。
(3)输入输出运算符必须是非成员函数
若是类的成员,它们的左侧运算对象将是我们类的一个对象:
Sales_data data;
data<<cout; //如果operator<<是Sales_data的成员
假如输入输出运算符是某个类的成员,则它们也必须是istream或ostream的成员,然而,这个类属于标准库,并且我们无法给标准库中的类添加任何成员。
2、算术和关系运算符
通常,把算术和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换,因为这些运算符一般不需要改变运算对象的状态,所以形参都是常量的引用。
(1)算术运算符
算术运算符通常会计算它的两个运算对象并得到一个新值,这个值有别于任意一个运算对象,常常位于一个局部变量之内,操作完成之后返回该局部变量的副本作为其结果。
Sales_data operator+(const Sales_data &lhs,const Sales_data &rhs)
{
Sales_data sum=lhs;
sum+=rhs;
return sum;
}
(2)相等运算符
C++中的类通过定义相等运算符来检查两个对象是否相等,会对比对象的每一个数据成员,只有当所有的对应的成员都相等时,才认为两个对象相等。
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;
} bool operator !=(const Sales_data &lhs,const Sales_data &rhs)
{
return !(lhs==rhs);
}
3、赋值运算符
赋值运算符必须定义为成员函数
(1)拷贝赋值
拷贝赋值运算符接受一个与其所在类相同类型的参数:
class Foo
{
public:
Foo &operator=(const Foo&); //赋值运算符
//...
}
(2)移动赋值运算符
移动赋值运算符不抛出任何异常,将它标记为noexecpt,类似拷贝赋值运算符,移动赋值运算符必须正确处理自赋值:
StrVec &StrVec::operator=(StrVec &&rhs) noexecpt
{
if(this !=&rhs) //直接检测自赋值
{
free(); //释放已有元素
elements=rhs.elements; //从rhs接管资源
first_free=rhs.first_free;
cap=rhs.cap;
rhs.elements=rhs.first_free=rhs.cap=nullptr; //将rhs置于可析构状态
}
return *this;
}
(3)标准库vector类定义的第三种赋值运算符,该运算符接受花括号内的元素列表作为参数,如:
vector<string> v;
v={"a","an"};
运算符添加到StrVec类中时:
StrVec &StrVec::operator=(initiallizer_list<string> il)
{
//alloc_n_copy分配内存空间并从给定范围内拷贝元素
auto data=alloc_n_copy(il.begin(),il.end());
free(); //销毁对象中的元素并释放内存空间
elements=data.first; //更新数据成员使其指向新空间
first_free=cap=data.second;
return *this;
}
4、递增和递减运算符
(1)定义前置递增、递减运算符
它们首先调用check函数检验StrBolbPtr是否有效,若是,接着检查给定的索引值是否有效,若是check函数没有抛出异常,则运算符返回对象的引用。
class StrBlobPtr
{
public:
StrBlobPtr& operator++();
StrBlobPtr& operator--();
} StrBlobPtr& StrBlobPtr::operator++()
{
//若curr已经指向容器的尾后位置,则无法递增它
check(curr,"increment past end of StrBlobPtr");
++curr;
return *this;
} StrBlobPtr& StrBlobPtr::operator--()
{
--curr;
check(curr,"decrement past begin of StrBlobPtr");
return *this;
}
(2)定义后置递增、递减运算符
和前置比较后置版本接受一个额外的(不使用)int 类型的形参。
class StrBlobPtr
{
public:
StrBlobPtr& operator++(int);
StrBlobPtr& operator--(int);
} StrBlobPtr& StrBlobPtr::operator++(int)
{
StrBlobPtr ret=*this; //记下当前值
++*this; //需要前置++检查是否有效
return *ret; //返回之前记录的状态
}
另外,其他运算符见书《C++ Primer(第5版)》。
C++重载运算简介的更多相关文章
- C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符
重载前须知 重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成. 因为重载运算符时函数, 因此它包含返回值.参数列表和函数体. 对于重载运算符是成员函数时, 它的第一个 ...
- 高放的c++学习笔记之重载运算与类型转换
▲基本概念 (1)重载运算符是具有特殊名字的函数,它们的名字又operator和其后要定义的运算符号共同构成.. (2)对于一个运算符号来说它或者是类的成员,或者至少含有一个类类型的参数. (3)我们 ...
- C++ Primer 笔记——重载运算
1.对于二元运算符来说,左侧运算对象传递给第一个参数,而右侧运算对象传递给第二个参数.除了重载的函数调用运算符operator()之外,其他重载元素运算符不能含有默认实参. class test { ...
- C/C++基础----重载运算与类型转换
非成员版本 data1 + data2: operator+(data1, data2); 成员版本 data1 += data2: data1.operator+=(data2); 不建议的重载 逻 ...
- C++ Primer 5th 第14章 重载运算与类型转换
当运算符作用域类类型的对象时,可以通过运算符重载来重新定义该运算符的含义.重载运算符的意义在于我们和用户能够更简洁的书写和更方便的使用代码. 基本概念 重载的运算符是具有特殊名字的函数:函数名由关键词 ...
- c++ 重载运算与类型转换
1. 基础概念 重载的运算符是具有特殊名字的函数:(重载运算符函数,运算符函数.重载运算符) 依次包含返回类型,函数名(operator=),参数列表,函数体. 只有重载的函数调用运算符operato ...
- 【c++ Prime 学习笔记】第14章 重载运算与类型转换
14.1 基本概念 重载的运算符是特殊的函数:名字由关键字operator后接要定义的算符共同组成,也有返回类型.参数列表.函数体. 重载运算符函数的参数量与该算符作用的运算对象数量一样多 除重载调用 ...
- 再议Swift操作符重载
今天我们来谈一谈Swift中的操作 符重载,这一功能非常实用,但是也相当有风险.正所谓“能力越大责任越大”,这句话用来形容操作符重载最合适不过了.它可以令你的代码更加简洁,也可以让 一个函数调用变得又 ...
- 一起学习《C#高级编程》3--运算符重载
运算符的重载.C++的开发人员应该很熟悉这个概念,但这对Java 和 VB 开发人员确实全新的. 对于一些数值间的运算,如果通过方法来指定运算规则的话,不免会繁琐,这时就可以利用运算符的重载. 例: ...
随机推荐
- Python对象引用问题总结
对于对象引用问题,一直是一知半解的状态,现整理以备使用. 操作不可变对象进行加减运算时,会在内存中创建新的不可变实例,不会影响原来的引用>>> c=12>>> d= ...
- PHP使用Redis消息队列
1.redis安装 参考:菜鸟教程http://www.runoob.com/redis/redis-install.html 2.安装php的redis扩展 1)phpinfo()查看php版本信息 ...
- Katalon 学习笔记(一)
工具介绍: Katalon Studio是一个能提供一整套功能来实现Web,API和Mobile的全自动测试解决方案的自动化测试平台.Katalon Studio构建于开源Selenium和App ...
- redmine本地安装部署
1.railsinstaller-3.2.0.exe 下载地址 http://railsinstaller.org/en 安装railsinstaller 一直点next就可以了,安装完成之后C盘会 ...
- Selenium 入门到精通系列:五
Selenium 入门到精通系列 PS:显式等待.隐式等待.强制等待方法 例子 #!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2019 ...
- python基础之全局局部变量及函数参数
1.局部变量和全局变量 1.1局部变量 局部变量是在函数内部定义的变量,只能在定义函数的内部使用 函数执行结束后,函数内部的局部变量会被系统收回 不同函数可以定义相同名字的局部变量,但是各用个的互不影 ...
- EasyUI学习心得
因为要修改十几年前的一个项目界面,打9月份开始学习EasyUI,很多事情都要自己试过才知道,小问题会浪费很多时间.所以,就在此记录一下,随时更新. 一.引号 EasyUI的自定义关键字的识别,API文 ...
- Linear Equations in Linear Algebra
Linear System Vector Equations The Matrix Equation Solution Sets of Linear Systems Linear Indenpende ...
- 基于Kubernetes(k8s)的RabbitMQ 集群
目前,有很多种基于Kubernetes搭建RabbitMQ集群的解决方案.今天笔者今天将要讨论我们在Fuel CCP项目当中所采用的方式.这种方式加以转变也适用于搭建RabbitMQ集群的一般方法.所 ...
- php性能优化--opcache
一.OPcache是什么? OPcache通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能, 存储预编译字节码的好处就是 省去了每次加载和解析 PHP 脚本的开销. PHP 5 ...