当前位置: 首页 > news >正文

[Oh My C++ Diary]operator++()和operator++(int)的区别

很久以前(八十年代),没有办法区分++和–操作符的前缀与后缀调用。这个问题遭到程序员的报怨,于是C++语言得到了扩展,允许重载increment 和 decrement操作符的两种形式。

然而有一个句法上的问题,重载函数间的区别决定于它们的参数类型上的差异,但是不论是increment或decrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数:

class UPInt 
{ 
// "unlimited precision int" 
public:  
UPInt& operator++(); // ++ 前缀  
const UPInt operator++(int); // ++ 后缀  
UPInt& operator--(); // -- 前缀  
const UPInt operator--(int); // -- 后缀  
UPInt& operator+=(int); // += 操作符,UPInts  // 与ints 相运算  
... 
};

UPInt i;

++i; // 调用 i.operator++(); 
i++; // 调用 i.operator++(0);
--i; // 调用 i.operator--(); 
i--; // 调用 i.operator--(0);

这个规范有一些古怪,不过你会习惯的。而尤其要注意的是这些操作符前缀与后缀形式返回值类型是不同的。前缀形式返回一个引用,后缀形式返回一个const类型。下面我们将讨论++操作符的前缀与后缀形式,这些说明也同样使用与–操作符。

从你开始做C程序员那天开始,你就记住increment的前缀形式有时叫做“增加然后取回”,后缀形式叫做“取回然后增加”。这两句话非常重要,因为它们是increment前缀与后缀的形式上的规范。

// 前缀形式:增加然后取回值

UPInt& UPInt::operator++()
{
 *this += 1; // 增加
 return *this; // 取回值
}

// postfix form: fetch and increment

const UPInt UPInt::operator++(int)
{
 UPInt oldValue = *this; // 取回值
 ++(*this); // 增加
 return oldValue; // 返回被取回的值
}

后缀操作符函数没有使用它的参数。它的参数只是用来区分前缀与后缀函数调用。如果你没有在函数里使用参数,许多编译器会显示警告信息,很令人讨厌。为了避免这些警告信息,一种经常使用的方法时省略掉你不想使用的参数名称;如上所示。

很明显一个后缀increment必须返回一个对象(它返回的是增加前的值),但是为什么是const对象呢?假设不是const对象,下面的代码就是正确的:

UPInt i;
i++++; // 两次increment后缀
// 运算

这组代码与下面的代码相同:

i.operator++(0).operator++(0);

很明显,第一个调用的operator++函数返回的对象调用了第二个operator++函数。

有两个理由导致我们应该厌恶上述这种做法,第一是与内置类型行为不一致。当设计一个类遇到问题时,一个好的准则是使该类的行为与int类型一致。而int类型不允许连续进行两次后缀increment:

int i;
i++++; // 错误!

第二个原因是使用两次后缀increment所产生的结果与调用者期望的不一致。如上所示,第二次调用operator++改变的值是第一次调用返回对象的值,而不是原始对象的值。因此如果:

i++++;

是合法的,i将仅仅增加了一次。这与人的直觉相违背,使人迷惑(对于int类型和UPInt都是一样),所以最好禁止这么做。

C++禁止int类型这么做,同时你也必须禁止你自己写的类有这样的行为。最容易的方法是让后缀increment 返回const对象。当编译器遇到这样的代码:

i++++; // same as i.operator++(0).operator++(0);

它发现从第一个operator++函数返回的const对象又调用operator++函数,然而这个函数是一个non-const成员函数,所以const对象不能调用这个函数。如果你原来想过让一个函数返回const对象没有任何意义,现在你就知道有时还是有用的,后缀increment和decrement就是例子。(更多的例子参见Effective C++ 条款21)

如果你很关心效率问题,当你第一次看到后缀increment函数时, 你可能觉得有些问题。这个函数必须建立一个临时对象以做为它的返回值,(参见条款19),上述实现代码建立了一个显示的临时对象(oldValue),这个临时对象必须被构造并在最后被结构。前缀increment函数没有这样的临时对象。由此得出一个令人惊讶的结论,如果仅为了提高代码效率,UPInt的调用者应该尽量使用前缀increment,少用后缀increment,除非确实需要使用后缀increment。让我们明确一下,当处理用户定义的类型时,尽可能地使用前缀increment,因为它的效率较高。

我们再观察一下后缀与前缀increment 操作符。它们除了返回值不同外,所完成的功能是一样的,即值加一。简而言之,它们被认为功能一样。那么你如何确保后缀increment和前缀increment的行为一致呢?当不同的程序员去维护和升级代码时,有什么能保证它们不会产生差异?除非你遵守上述代码里的原则,这才能得到确保。这个原则是后缀increment和decrement应该根据它们的前缀形式来实现。你仅仅需要维护前缀版本,因为后缀形式自动与前缀形式的行为一致。

正如你所看到的,掌握前缀和后缀increment和decrement是容易的。一旦了解了他们正确的返回值类型以及后缀操作符应该以前缀操作符为基础来实现的规则,就足够了。

看到一个记忆方法:

方便记忆:
  i++:++后面还要接东西就operator++(int);
  ++i:加号后面有i啦不用加东西了operator++();

相关文章:

  • CSS3: nth-child使用总结
  • js setattribute批量设置css样式
  • Asp类
  • 位运算符
  • media=screen
  • li中的图片和文字不对齐div+css,解决方法
  • js 动态加载事件
  • asp常用函数
  • asp多风格分页类
  • 启动APACHE的时候报错“0x6eec38dc指令引用的0x00000000内存,该内存不能为“read”
  • asp登录类
  • IIS 500错误
  • 找不到iis
  • 为什么2008会出现很多TMP文件
  • 如何解决对象不支持此属性或方法
  • 【面试系列】之二:关于js原型
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • Android 控件背景颜色处理
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • ESLint简单操作
  • HashMap ConcurrentHashMap
  • Hexo+码云+git快速搭建免费的静态Blog
  • HTML-表单
  • php面试题 汇集2
  • python学习笔记 - ThreadLocal
  • React 快速上手 - 07 前端路由 react-router
  • Vue.js源码(2):初探List Rendering
  • vue脚手架vue-cli
  • 服务器从安装到部署全过程(二)
  • 实现菜单下拉伸展折叠效果demo
  • 应用生命周期终极 DevOps 工具包
  • 用 Swift 编写面向协议的视图
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 06-01 点餐小程序前台界面搭建
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​水经微图Web1.5.0版即将上线
  • #pragam once 和 #ifndef 预编译头
  • (2)nginx 安装、启停
  • (30)数组元素和与数字和的绝对差
  • (39)STM32——FLASH闪存
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (转)C#调用WebService 基础
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)Unity3DUnity3D在android下调试
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • *Django中的Ajax 纯js的书写样式1
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .Net IE10 _doPostBack 未定义
  • .NET 将多个程序集合并成单一程序集的 4+3 种方法
  • .net/c# memcached 获取所有缓存键(keys)
  • .NET文档生成工具ADB使用图文教程