这篇文章不是讨论(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++中,有两种类型的自增运算符:前置++(++a)和后置++(a++)。这两种运算符的主要区别在于它们对变量的值的影响顺序。前置++会先将变量的值加1,然后返回新的值,而后置++会先返回变量的原值,然后将变量的值...
前置自增 ++i 与后置自增 i++ 的区别及应用
这里,我们将深入探讨前置`++`、后置`++`、前置`--`和后置`--`这四个增量和减量运算符的重载,并通过实例分析如何在`main.cpp`文件中实现它们。`README.txt`文件可能包含有关代码的额外说明和指导。 前置`++`和后置...
在编程语言中,`前置++` 和 `后置++` 是两种不同的自增运算符,主要用于整型或可被整除的类型变量。这两种运算符的主要区别在于它们在表达式中的处理方式,以及何时更新变量的值。 前置 `++` 运算符(如 `++i`)会...
请定义一个Point类,将前置++和后置++运算符重载为成员函数,实现成员变量m_x和m_y的加一操作 同时重载流插入运算符,实现对Point类对象的格式化输出。例如 Point p; cout; 输出结果为: (0,0) 请根据给定的main...
在这个项目中,我们需要创建一个CVector3D类,该类应该包含特定的功能和运算符重载。以下是关于这个任务的详细解释: 首先,我们需要为CVector3D类定义三种构造函数: 1. 默认构造函数:不接受任何参数,创建一个...
在Android开发中,手持身份证蒙版引导、身份证正反面框引导以及相机的前置和后置切换是常见的功能需求,特别是在涉及到用户身份验证或者线上业务办理的场景中。以下将详细解析这些知识点。 1. **Android手持身份证...
在Android平台上,调用和编程控制前置和后置摄像头是一项重要的功能,尤其对于那些需要进行视频拍摄、图像捕捉或者人脸识别的应用来说。本文将详细介绍如何在Android应用中实现这一功能。 首先,Android系统提供了...
然而,一个全面的服务描述不仅包括输入和输出,还涉及到前置条件和后置条件,它们定义了服务执行前后的状态或约束。 前置条件是服务执行前必须满足的条件,确保服务能够正确、安全地运行。而后置条件则是服务执行后...
`Spring的前置后置通知jar包`可能包含了一些预构建的AOP通知实现,例如日志库,它们可以方便地集成到Spring应用中,提供对方法执行前后的拦截能力。 总的来说,Spring的AOP机制提供了强大的灵活性,使得我们可以将...
本文将深入探讨Spring AOP中的前置通知和后置通知,以及它们在实际开发中的应用。 前置通知是指在目标方法执行之前执行的通知。在Spring AOP中,这通过`@Before`注解实现。这个注解用于定义一个切点表达式,匹配到...
本教程将深入探讨Spring AOP中的四种通知类型:前置通知、后置通知、环绕通知以及异常通知,并通过实际案例展示如何创建自定义切入点。 1. **前置通知**: 前置通知在目标方法执行前触发,通常用于执行验证或准备...
Android设备通常有两个摄像头,一个用于前置,一个用于后置,它们的ID分别是0和1。 ```java int numberOfCameras = Camera.getNumberOfCameras(); for (int i = 0; i ; i++) { Camera.CameraInfo cameraInfo = new...
使用场景及目标:本篇文章主要服务于那些想要深入了解并掌握前置递增和后置递增之间区别的读者,在日常编写涉及整型或者其他数值型变量的程序逻辑时能够做出更加优化的选择。 阅读建议:在阅读时注意结合文内的代码...
本文将探讨一个在编程中经常被忽视但对效率有一定影响的细节:前置递增`++i`与后置递增`i++`的区别。尽管现代游戏硬件配置已经显著提升,但优化代码仍然是一个良好的编程习惯。 前置递增`++i`的操作是先增加变量`i`...
在Spring AOP中,有三种主要的通知类型:前置通知、后置通知和环绕通知。下面将详细解释这三种通知,并通过简单的代码示例进行演示。 **1. 前置通知(Before Advice)** 前置通知在目标方法被调用之前执行,但无法...
本示例主要介绍如何在uni-app中实现调用摄像头进行扫码功能,同时支持前置和后置摄像头切换。这个功能在很多场景下都非常实用,比如商品扫码、二维码登录等。 首先,你需要在你的uni-app项目中引入扫码相关的插件。...