整理:i++ 与 ++i 的本质区别及效率

本文通过自定义迭代器的方式详细分析了C++中i++与++i操作符的区别,揭示了两者在执行过程中的本质差异,并提出了最佳实践建议。
C++ 中的 i++ 和 ++i 是一对十分简洁但最容易让人忽视的操作,我已经对它们忽视了十多年,
直到近日做一些迭代器时才有所体悟。在刚开始学习C++时虽然知道它们在加一操作上有一个
先后的区别,但很难记住这个先后顺序,而且觉得只要不连带赋值操作时它们是等效的,所以
也就没有花大力气去辨析它们,而是养成了单独使用它们的习惯,而且习惯性地只使用i++一个
操作符。后来经常在各处论坛看到人们讨论这两个操作符的区别时,尤其是当有人拿出编译器
生成的机器代码来对这两个操作符作辨析的较真劲时,觉得人们真的傻得有点可爱,也觉得在
C++标准中保留这两个另人困惑的操作符的做法有点奇怪。用机器代码来辨析时,与编译器多
少有点关联,本文尝试用操作符重载的方式来辨析这两个操作符,能够最大限度地降低对编译
器的依赖。

先看看基本区别:
i++ :先在i所在的表达式中使用i的当前值,后让i加1
++i :让i先加1,然后在i所在的表达式中使用i的新值 

++i 与 i++ 的本质区别是:

++i 操作除 i 之外不涉及新的(隐含的)操作数,而 i++ 则在 i 之外还涉及另一个新
的(隐含的)操作数

可以粗略这么理解:
++i相当于下列代码 
	i += 1; 
	return i; 
i++相当于下列代码 
	j = i; 
	i += 1; 
	return j;
当然如果编译器会将这些差别都优化掉,那么效率就都差不多了。

在开始辨析时,先提出三个习惯的改进建议:
1、优先使用 ++i:
  (a)单独使用时:++i;
  (b)作为循环控制变量使用时:for(int i=0; i!=100; ++i) ...
2、在局部范围内使用 ++i 时,若不出现逻辑错误,可尝试使用引用而不是赋值:
  int & j = ++ i;
3、杜绝两个以上的 i++ 或者 ++i 进行合成

前两点建议对单纯的整数变量 i 所带来的好处可以忽略不计,但只要有可能使用非整数的场合,
就有可能带来性能的改进。++i 和 i++ 是两个完全不同的操作符,如果要进行操作符重载的话,
分别对应 ++() 和 ++(int),具体点即:
++i     ===>     _T& operator ++ ()
i++     ===>     _T operator ++ (int)

下面定义一个简单的迭代器Iter<_T>来对这两个操作符进行辨析。迭代器通常被设计成一类可以
在某些场合部分地模拟指针的行为进行特殊数据操作的工具。Iter<_T>的代码如下:
template<typename _T>
class Iter {
public:
    _T* p;
    Iter(_T* __p) : p(__p) {}
    Iter(const Iter & __iter) : p(__iter.p)
    {
        cout << "copy() ";
    }
    Iter & operator = (const Iter & __iter)
    {
        p = __iter.p;
        return * this;
    }
    Iter & operator = (_T * __p)
    {
        p = __p;
        return * this;
    }
    Iter & operator ++ ()
    {
        cout << "++() ";
        ++ p;
        return * this;
    }
    Iter operator ++ (int)
    {
        cout << "++(int) ";
        Iter result(*this);
        ++ p;
        return result;
    }
    operator _T * () const
    {
        return p;
    }
};
说明:
Iter<_T>::Iter(_T *) :初始化构造器接受一个指针作为初始化参数;
Iter<_T>::Iter(const Iter &):复制构造器,里面输出一个 copy 字样用来指示调用了复制构造器;
Iter & Iter<_T>::operator = (const Iter &)
Iter & Iter<_T>::operator = (_T *) :两个赋值操作符,为了方便使用而引入;
Iter & Iter<_T>::operator ++ ():对应 ++i 的操作符,注意返回值为 Iter &;
Iter Iter<_T>::operator ++ (int):对应 i++ 的操作符,注意返回值为 Iter;
Iter<_T>::operator _T * () const:类型转换操作符,使 Iter<_T> 具有代替 _T * 的能力。
使用Iter<_T>进行以下测试:
int main()
{
    int array [] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    Iter<int> it(array);
    cout << "*it: " << *it << endl;
    Iter<int>& it2 = ++ it;                                            /*1*/
    cout << "*it: " << *it << ", *it2: " << *it2 << endl;
    Iter<int> it3 = it ++;                                              /*2*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << endl;
    Iter<int>& it4 = ++ it ++;                                       /*3*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 << endl;
    Iter<int> it5 = ++ it;                                              /*4*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 
        << ", *it5: " << *it5 << endl;
    ++ it5;                                                                 /*5*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 
        << ", *it5: " << *it5 << endl;
    Iter<int> it6 = ++ it ++;                                        /*6*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 
        << ", *it5: " << *it5 << ", *it6: " << *it6 << endl;
    ++ it;                                                                   /*7*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 
        << ", *it5: " << *it5 << ", *it6: " << *it6 << endl;
    it ++;                                                                   /*8*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 
        << ", *it5: " << *it5 << ", *it6: " << *it6 << endl;
    ++ it ++;                                                              /*9*/
    cout << "*it: " << *it << ", *it2: " << *it2 
        << ", *it3: " << *it3 << ", *it4: " << *it4 
        << ", *it5: " << *it5 << ", *it6: " << *it6 << endl;
    for(Iter<int> i=array; i!=array+10; ++i)                /*10*/
        cout << endl;
    for(Iter<int> i=array; i!=array+10; i++)                /*11*/
        cout << endl;
    return 0;
}
/*1*/之后输出的内容是:++() *it: 2, *it2: 2
/*2*/之后输出的内容是:++(int) copy() *it: 3, *it2: 3, *it3: 2
比较/*1*/和/*2*/容易看出 i++ 比 ++i 多调用了一个复制构造器。更仔细点分析容易知道,i++ 过程中先是分配了一个临时实
例,然后把这个临时实例复制出来,最后还要把这个临时实例销毁掉。这就是 ++i 与 i++ 的本质区别,也就是为什么提倡优
先使用 ++i 的根本原因。

/*3*/之后输出的内容是:++(int) copy() ++() *it: 4, *it2: 4, *it3: 2, *it4: 4
/*3*/的写法是不提倡的,因为这个过程开始引入了极难察觉的复杂性。从中可以看出是先执行了 i++,而后再复制,再后就是
 ++i 。这个过程难以从表达式 ++ i ++ 中获得感性认识。按常人的理解,很容易解读为先执行了 ++ i,而后执行了 i++。另一
个容易引入错误的是执行了这个表达式之后,it4 的内容开始变得模糊起来了,表面上看起来是对 it 的引用,但其实它具有嬗
变性,也就是说它并不总是 it 的引用,这会在接下来的过程中看到它的变化。这也就是为什么要杜绝这类表达式的根本原因。

/*4*/之后输出的内容是:++() copy() *it: 5, *it2: 5, *it3: 2, *it4: 5, *it5: 5
/*4*/与/*1*/所不同的是在/*1*/处没有构造新的实例,it2是it的引用,而/*4*/则构造了新的实例,并调用了复制构造器。这里的
另一个潜在的影响是对/*3*/的复合操作的,它潜在地改变了 it4 的引用,从此 it4 不再是 it 的引用,而是变成了 it5 的引用。

/*5*/之后输出的内容是:++() *it: 5, *it2: 5, *it3: 2, *it4: 6, *it5: 6
/*5*/的作用是对 it5 进行 ++i 操作,更清楚地确认 it4 的确变成了 it5 的引用,而 it2 仍然是 it 的引用,/*3*/中的复合表达式的
不良影响在这里最终体现出来了。

/*6*/之后输出的内容是:++(int) copy() ++() copy() *it: 6, *it2: 6, *it3: 2, *it4: 6, *it5: 6, *it6: 6
/*6*/也是要杜绝的。但/*6*/与/*3*/相比更加安全些,因为在构造 it6 时使用了复制构造器,从此以后 it6 与 it 再无瓜葛。要杜绝
的原因与/*3*/是相当的,因为即使比/*3*/安全,但它所复制的内容仍然具有一定程度上的嬗变性,难以相信这条语句结束后 it6 
的内容与 it 的内容是一致的。

/*7*/之后输出的内容是:++() *it: 7, *it2: 7, *it3: 2, *it4: 6, *it5: 6, *it6: 6
/*8*/之后输出的内容是:++(int) copy() *it: 8, *it2: 8, *it3: 2, *it4: 6, *it5: 6, *it6: 6
/*7*/与/*8*/形成对比,类似于/*1*/和/*2*/。但值得注意的是在/*8*/中即使没有赋值操作,也需要构造一个临时变量,并释放这个
临时变量。

/*9*/之后输出的内容是:++(int) copy() ++() *it: 9, *it2: 9, *it3: 2, *it4: 6, *it5: 6, *it6: 6
/*9*/虽然也是要杜绝的,但比/*6*/相比安全性更好一些,因为这里没有赋值操作。一般情况下都能保证 it 被加了两次。

/*10*/和/*11*/是在循环控制中使用 ++i 和 i++ 的对比,可以十分明显地看到 ++i 用在循环控制操作中的性能改进优势。

以上代码分析基于的编译器是 GCC 3.4.5。




<think>好的,我现在需要回答用户关于C/C++中前置自增(++i)和后置自增(i++)的区别及应用场景的问题。根据用户提供的引用资料和系统指令,我需要先整理这些信息,确保回答结构清晰,符合格式要求。 首先,用户提到的三个引用中,引用[1]对比了++i和i++的执行顺序、性能和应用场景,并提到前置++效率更高。引用[2]引用了Scott Meyers的《Effective C++》,强调理解对象的本质,并提供了代码示例。引用[3]讨论了运算符重载,特别是成员函数和友元函数的实现,以及参数传递的方式。 接下来,我需要将这些信息综合起来。前置和后置自增的主要区别在于返回值(前者返回自增后的值,后者返回原值)、性能差异(前置通常更高效,因为不需要临时对象)、以及应用场景(循环中推荐前置,表达式需要原值时用后置)。此外,运算符重载时,后置++需要一个int参数来区分,这可能导致额外的开销。 然后,根据系统指令,回答需要使用中文,行内公式用$...$,独立公式用$$...$$,并生成相关问题。需要确保LaTeX语法正确,比如在提到变量时使用$x$这样的格式。不过这个问题可能涉及代码较多,数学表达式较少,但如果有涉及算法或数学的部分,比如循环次数或时间复杂度,需要正确使用格式。 需要检查是否有必要加入数学表达式。例如,在讨论循环中的i++时,如果涉及到循环次数或变量变化,可能不需要复杂的数学公式,但如果有涉及性能分析的公式,比如时间复杂度,可能需要用LaTeX。不过根据引用内容,可能更多是代码和性能比较,因此数学表达式可能较少,但需注意如果有的话要正确使用。 然后,生成相关问题。用户提供的示例问题包括性能分析、优化方法、应用场景,因此我需要类似的问题,例如运算符重载的实现、性能差异的原因、在迭代器中的应用等。 最后,确保引用标记正确添加到对应的段落末尾,例如在提到Scott Meyers的观点时添加[^2],在讨论运算符重载时引用[^3]。 现在需要组织这些内容,分点回答,保持结构清晰。可能需要分为区别对比、应用场景、运算符重载实现、性能优化建议等部分。同时,代码示例需要正确展示,比如前置和后置++的重载方式,使用正确的语法高亮。 需要确保所有行内代码如i++用`i++`格式,独立代码块用```python或```cpp等。根据用户提供的示例,代码块可能使用Python,但这里的问题涉及C++,所以应使用```cpp。 最后,检查是否符合所有系统指令,包括生成的相关问题是否合理,引用是否正确,格式无误。</think>### C++中前置自增(++i)后置自增(i++)的区别及应用场景 #### 1. 核心区别对比 - **返回值**: - 前置`++i`:**先自增,后返回**自增后的值。 - 后置`i++`:**先返回原值,后自增**。 - **性能差异**: - 前置`++i`:无需创建临时对象,直接操作变量,效率更高[^1]。 - 后置`i++`:需生成原值的副本作为返回值,可能引入额外开销。 - **运算符重载**: - 后置`i++`需添加`int`类型参数以区分(如`operator++(int)`),而前置无参数[^3]。 #### 2. 应用场景 - **推荐使用前置`++i`**: - **循环控制**:如`for (int i=0; i<n; ++i)`,减少临时对象开销。 - **复杂表达式**:避免因副本操作导致逻辑错误或性能损失。 - **迭代器操作**:STL迭代器中优先使用`++iter`以提升效率。 - **必须使用后置`i++`**: - 需要保留自增前的值,如`arr[i++] = x;`(先写入`arr[i]`,再自增`i`)。 #### 3. 运算符重载实现示例 ```cpp class Counter { private: int value; public: // 前置++重载 Counter& operator++() { ++value; return *this; } // 后置++重载(需int参数) Counter operator++(int) { Counter temp = *this; // 创建副本 ++value; return temp; // 返回原值 } }; ``` 后置版本因需返回临时对象,可能成为性能瓶颈。 #### 4. 性能优化建议 - 在循环或高频调用场景中,**优先选择前置`++i`**以规避临时对象开销。 - 对于自定义类型(如迭代器),**重载时应遵循语义一致性**,确保前置版本高效。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值