题目

编写程序,设计具有内部数值状态的迭代计数器 Counter。完整实现前置自增 ++c 与后置自增 c++ 的重载。深入掌握两者的根本区别:①语法签名上,后置通过一个无名字的哑元整型参数 int 区分;②语义上,前置直接修改原对象并返回自身引用(Counter&),后置必须生成临时副本返回旧状态(按值返回 Counter);从汇编与对象生命周期层面证明在 C++ 循环中为什么必须优先推荐使用前置 ++it。

输入格式:无需外部输入。

输出格式:输出前置与后置自增在返回值、原对象状态以及临时副本构造上的对照结果。

数据范围与约定:哑元区分标记、返回值语义与迭代器前置递增零开销准则。

思路与算法

1. 后置重载的哑元 int
为了解决 ++c 和 c++ 在名字查找上的符号冲突,C++ 委员会规定:operator++() 代表前置;而 operator++(int) 代表后置(传入一个编译器生成的哑元 dummy 0)。

2. 性能劣势
后置 operator++(int) 必须先拷贝当前对象构造一个临时对象 Counter old = *this;,然后递增自己,最后把 old 按值返回。在复杂 STL 迭代器(如 map 迭代器)中,这意味着一次多余的拷贝构造与析构。

3. 前置返回引用的左值属性
前置自增返回引用,支持连续自增 ++++c;;而后置返回的是右值临时对象,不支持连续后置自增。

完整程序

#include <iostream>

class Counter {
private:
    int value_;

public:
    explicit Counter(int val = 0) : value_(val) {}

    // 1. 前置自增:无参数,直接就地修改,返回自身引用 (左值,零多余拷贝)
    Counter& operator++() {
        ++value_;
        std::cout << "  [前置 ++] 就地累加至 " << value_ << ",返回自身引用\n";
        return *this;
    }

    // 2. 后置自增:带哑元 int 参数,必须拷贝旧副本,按值返回 (产生临时对象)
    Counter operator++(int) {
        Counter temp = *this; // 必须保留并构造旧状态副本
        ++value_;
        std::cout << "  [后置 ++] 备份旧状态 " << temp.value_ 
                  << " 并递增至 " << value_ << ",返回旧副本\n";
        return temp;
    }

    int getValue() const { return value_; }
};

int main() {
    Counter c1(10);
    Counter c2(10);

    std::cout << "--- 1. 测试前置自增 ++c1 ---\n";
    Counter& ref1 = ++c1;
    std::cout << "前置调用返回值: " << ref1.getValue() << ", c1 实际值: " << c1.getValue() << "\n\n";

    std::cout << "--- 2. 测试后置自增 c2++ ---\n";
    Counter val2 = c2++;
    std::cout << "后置调用返回值: " << val2.getValue() << ", c2 实际值: " << c2.getValue() << "\n\n";

    std::cout << "结论:在 STL 迭代器遍历循环中,严格推荐使用 ++it(前置),彻底消除多余临时对象的拷贝开销!\n";

    return 0;
}

运行示例

输出:

--- 1. 测试前置自增 ++c1 ---
  [前置 ++] 就地累加至 11,返回自身引用
前置调用返回值: 11, c1 实际值: 11

--- 2. 测试后置自增 c2++ ---
  [后置 ++] 备份旧状态 10 并递增至 11,返回旧副本
后置调用返回值: 10, c2 实际值: 11

结论:在 STL 迭代器遍历循环中,严格推荐使用 ++it(前置),彻底消除多余临时对象的拷贝开销!