本文基于《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++重载运算简介的更多相关文章

  1. C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符

    重载前须知 重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成. 因为重载运算符时函数, 因此它包含返回值.参数列表和函数体. 对于重载运算符是成员函数时, 它的第一个 ...

  2. 高放的c++学习笔记之重载运算与类型转换

    ▲基本概念 (1)重载运算符是具有特殊名字的函数,它们的名字又operator和其后要定义的运算符号共同构成.. (2)对于一个运算符号来说它或者是类的成员,或者至少含有一个类类型的参数. (3)我们 ...

  3. C++ Primer 笔记——重载运算

    1.对于二元运算符来说,左侧运算对象传递给第一个参数,而右侧运算对象传递给第二个参数.除了重载的函数调用运算符operator()之外,其他重载元素运算符不能含有默认实参. class test { ...

  4. C/C++基础----重载运算与类型转换

    非成员版本 data1 + data2: operator+(data1, data2); 成员版本 data1 += data2: data1.operator+=(data2); 不建议的重载 逻 ...

  5. C++ Primer 5th 第14章 重载运算与类型转换

    当运算符作用域类类型的对象时,可以通过运算符重载来重新定义该运算符的含义.重载运算符的意义在于我们和用户能够更简洁的书写和更方便的使用代码. 基本概念 重载的运算符是具有特殊名字的函数:函数名由关键词 ...

  6. c++ 重载运算与类型转换

    1. 基础概念 重载的运算符是具有特殊名字的函数:(重载运算符函数,运算符函数.重载运算符) 依次包含返回类型,函数名(operator=),参数列表,函数体. 只有重载的函数调用运算符operato ...

  7. 【c++ Prime 学习笔记】第14章 重载运算与类型转换

    14.1 基本概念 重载的运算符是特殊的函数:名字由关键字operator后接要定义的算符共同组成,也有返回类型.参数列表.函数体. 重载运算符函数的参数量与该算符作用的运算对象数量一样多 除重载调用 ...

  8. 再议Swift操作符重载

    今天我们来谈一谈Swift中的操作 符重载,这一功能非常实用,但是也相当有风险.正所谓“能力越大责任越大”,这句话用来形容操作符重载最合适不过了.它可以令你的代码更加简洁,也可以让 一个函数调用变得又 ...

  9. 一起学习《C#高级编程》3--运算符重载

    运算符的重载.C++的开发人员应该很熟悉这个概念,但这对Java 和 VB 开发人员确实全新的. 对于一些数值间的运算,如果通过方法来指定运算规则的话,不免会繁琐,这时就可以利用运算符的重载. 例: ...

随机推荐

  1. React中类定义组件constructor 和super

    刚开始学习React没多久,在老师的教程里看到了类组件的使用示例,但是和资料上有些冲突,而引发了一些疑问: 类组件中到底要不要定义构造函数constructor()? super()里边到底要不要传入 ...

  2. 「日常训练」Divisibility by Eight(Codeforces Round 306 Div.2 C)

    题意与分析 极简单的数论+思维题. 代码 #include <bits/stdc++.h> #define MP make_pair #define PB emplace_back #de ...

  3. hdu2899Strange fuction(解方程+二分)

    Strange fuction Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...

  4. mysql数据库基本操作命令

    1.登录命令 mysql -u root -p "password" 2.列出所有数据库 show databases; 3.使用数据库 use db_name 4.列出数据库中所 ...

  5. python3 小实践(一)——selenium获取的cookie传递

    from selenium import webdriver from time import sleep import requests import pickle #获取登录后的cookies c ...

  6. JVM之G1收集器

    Garbage-First,面向服务端的垃圾收集器. 并行与并发:充分利用多核环境减少停顿时间, 分代收集:不需要配合其它收集器 空间整合:整体上看属于标记整理算法,局部(region之间)数据复制算 ...

  7. POJ 3256 (简单的DFS)

    //题意是 K N, M; //有K个牛 N个牧场,M条路 ,有向的  //把K个牛放到任意的n个不同牧场中,问所有牛都可以到达的牧场数的总和  //这是一道简单的DFS题 //k 100 //n 1 ...

  8. 【system.folder】使用说明

    对象:system.folder 说明:提供一系列针对文件夹的操作 目录: 方法 返回 说明 system.folder.exists(folderPath) [True | False] 检测指定文 ...

  9. 【WXS数据类型】String

    属性: 名称 值类型 说明 [String].constructor [String] 返回值为“String”,表示类型的结构字符串 [String].length [Number] 返回该字符串的 ...

  10. C二维数组行为空,列不为空

    二维数组: 处理二维数组得函数有一处可能不太容易理解:数组的行可以在函数调用时传递,但是数组的列却只能被预置在函数内部. eg: #define COLS 4 int sum(int ar[][COL ...