这篇文章不是讨论(i++)+(i++)+(i++)的计算结果,更不是讨论(i++)+(++i)+(i++)。
在C++教程中,我们都会学到:i++和++i是两个表达式,他们都会对i进行自增,但是呢,作为表达式,i++的值是自增之前的值,++i的值是自增之后的值。
本文在此基础上,进行一些稍微深入的讨论。
从操作符重载的角度,看i++和++i的区别,是一个比较好的切入点。
操作符重载
假设有一个类Age,描述年龄。该类重载了前置++和后置++两个操作符,以实现对年龄的自增。
class Age
{
public:
Age& operator++() //前置++
{
++i;
return *this;
}
const Age operator++(int) //后置++
{
Age tmp = *this;
++(*this); //利用前置++
return tmp;
}
Age& operator=(int i) //赋值操作
{
this->i = i;
return *this;
}
private:
int i;
};
从上述代码,我们可以看出前置++和后置++,有3点不同:
- 返回类型不同
- 形参不同
- 代码不同
- 效率不同
返回值类型的区别
前置++的返回类型是Age&,后置++的返回类型const Age。这意味着,前置++返回的是右值,后置++返回的是左值。
左值和右值,决定了前置++和后置++的用法。
int main()
{
Age a;
(a++)++; //编译错误
++(a++); //编译错误
a++ = 1; //编译错误
(++a)++; //OK
++(++a); //OK
++a = 1; //OK
}
a++的类型是const Age,自然不能对它进行前置++、后置++、赋值等操作。
++a的类型是Age&,当然不能对它进行前置++、后置++、赋值等操作。
a++的返回类型为什么要是const对象呢?
有两个原因:
- 如果不是const对象,a(++)++这样的表达式就可以通过编译。但是,其效果却违反了我们的直觉
。a其实只增加了1,因为第二次自增作用在一个临时对象上。
- 另外,对于内置类型,(i++)++这样的表达式是不能通过编译的。自定义类型的操作符重载,应该与内置类型保持行为一致
。
a++的返回类型如果改成非const对象,肯定能通过编译,但是我们最好不要这样做。
++a的返回类型为什么是引用呢?
这样做的原因应该就是:与内置类型的行为保持一致。前置++返回的总是被自增的对象本身。因此,++(++a)的效果就是a被自增两次。
形参的区别
前置++没有形参,而后置++有一个int形参,但是该形参也没有被用到。很奇怪,难道有什么特殊的用意?
其实也没有特殊的用意,只是为了绕过语法的限制
。
前置++与后置++的操作符重载函数,函数原型必须不同。否则就违反了“重载函数必须拥有不同的函数原型”的语法规定。
虽然前置++与后置++的返回类型不同,但是返回类型不属于函数原型。为了绕过语法限制,只好给后置++增加了一个int形参。
原因就是这么简单,真的没其他特殊用意。其实,给前置++增加形参也可以;增加一个double形参而不是int形参,也可以。只是,当时就这么决定了。
代码实现的区别
前置++的实现比较简单,自增之后,将*this返回即可。需要注意的是,一定要返回*this。
后置++的实现稍微麻烦一些。因为要返回自增之前的对象,所以先将对象拷贝一份,再进行自增,最后返回那个拷贝。
在Age的代码中,后置++利用了前置++来实现自增。这样做是为了避免“自增的代码”重复。
在本例中,自增的代码很简单,就是一行++i,没有必要这样做。但是在其它自增逻辑复杂的例子中,这么做还是很有必要的。
效率的区别
如果不需要返回自增之前的值,那么前置++和后置++的计算效果都一样。但是,我们仍然应该优先使用前置++,尤其是对于用户自定义类型的自增操作。
前置++的效率更高,理由是:后置++会生成临时对象。
从Age的后置++的代码实现也可以看出这一点。
const Age operator++(int) //后置++
{
Age tmp = *this;
++(*this); //利用前置++
return tmp;
}
很明显,tmp是一个临时对象,会造成一次构造函数和一次析构函数的额外开销。虽然,编译器在某些情况下可以优化掉这些开销。但是,我们最好不要依赖编译器的行为。
关于前置++和后置++,基本上就是这些内容。如果还有其他需要注意的地方,欢迎补充。
参考:
More Effective C++
, Item6
分享到:
相关推荐
本文主要写了前置++ -- 与后置++ --在程序中的应用,让C/C++学习者能够清楚了解前置++ -- 与后置++ --的用法
cpp代码-前置++,--,和后置++,--运算符重载
主要介绍了前置++和后置++ 的相关资料,并附示例代码,帮助大家学习参考,需要的朋友可以参考下
请定义一个Point类,将前置++和后置++运算符重载为成员函数,实现成员变量m_x和m_y的加一操作 同时重载流插入运算符,实现对Point类对象的格式化输出。例如 Point p; cout; 输出结果为: (0,0) 请根据给定的main...
cpp代码-写一个3D矢量类CVector3D,要求含三种构造函数,并有拷贝,点积,叉积,求模,单位化 等操作,并实现”+; -; =; ==; +=; -=; *; [ ]” 等运算符的重载。(用C++实现)
1.Android手持身份证蒙版引导 2.身份证正反面框引导 3.Android相机前置和后置切换(相机正反切换) 4.文案提示
但是,一个完整的服务的概要说明还包括前置条件和后置条件。为了能找到正确的匹配,匹配算法还需要对advertisements的前置条件和后置条件进行匹配。在论文中,我们解释了在原有基础上增加了web服务上下文中的前置...
android前置和后置摄像头的调用及编程 相当于摄像头驱动,非常实用于对视频处理、图像处理需求的人
正对于使用了C++的运算符重载和析构函数、构造函数以及日期类之间的相关计算,...主要使用的运算符重载包括了大于小于,大于等于,小于等于,等于,以及赋值符号,+=符号 , -=符号,以及前置++后置++前置--后置--符号
不过,作为一名从电子转行过来的人,大学四年被小内存搞怕的人,还是那么的喜欢优化自己的代码,提升效率。前置递增++i和后置递增i++的区别,主要有四个方面,大家可
spring框架的aop前置、后置、环绕、异常通知以及自定义切入点实例
开发的android摄像头小程序,实现前置后置切换功能
该程序涉及多种运算符的重载,包括>> << + - = += < <= > >= == != 前置++ 前置-- 后置++ 后置--
主板 音频 前置 后置 原理 排针含义 接法
不考虑返回值的话,前置自增(++i)通常要比后置自增(i++)效率更高,因为后置的自增自减需要对表达式的值i 进行一次拷贝,如果i 是迭代器或其他非数值类型,拷贝的代价是比较大的。 总之保持编程代码一直风格,...
spring的Aop中的前置通知,后置通知以及环绕通知简单代码
1 、通过函数来实现复数相加。然后通过运算符重载重载”+”,对比其异同点; 2、改写:将1中运算符重载作为非成员函数,和...输出分秒值(要求重载”++”,前置与后置两种情况。 5、将1中复数重载””,进行复数的输出。
1 、通过函数来实现复数相加。然后通过运算符重载重载”+”,对比其异同点; 2、改写:将1中运算符重载作为非成员函数,和...输出分秒值(要求重载”++”,前置与后置两种情况。 5、将1中复数重载””,进行复数的输出。