`
jarfield
  • 浏览: 200502 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

前置++和后置++的区别

    博客分类:
  • C++
 
阅读更多

这篇文章不是讨论(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点不同:

  1. 返回类型不同
  2. 形参不同
  3. 代码不同
  4. 效率不同

返回值类型的区别

前置++的返回类型是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对象呢?

有两个原因:

  1. 如果不是const对象,a(++)++这样的表达式就可以通过编译。但是,其效果却违反了我们的直觉 。a其实只增加了1,因为第二次自增作用在一个临时对象上。
  2. 另外,对于内置类型,(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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics