信奥崔老师:变量自增与自减运算

66 阅读4分钟

4.4 [等级1] 变量自增与自减运算 (Increment & Decrement)

图片

1. 概念介绍

自增 (++) 和自减 (--) 运算符是 C++ 中的一元运算符,用于将变量的值加 1 或减 1。它们是编程中用于计数的常用快捷方式。 这两种运算符都有两种形式:

  • 前缀 (Prefix)++i 或 --i。先将变量的值加/减 1,然后返回的值。
  • 后缀 (Postfix)i++ 或 i--。先返回变量的原始值,然后再将变量的值加/减 1。

2. 算法步骤

  • 前缀自增 ++i:
    1. 将 i 的值加 1。
    2. 整个表达式的结果是 i 的新值。
  • 后缀自增 i++ :
    1. 创建一个 i 的临时副本,保存其原始值。
    2. 将 i 的值加 1。
    3. 整个表达式的结果是那个临时副本的值(即 i 的原始值)。

自减运算符 --i 和 i-- 的步骤与此类似,只是操作为减 1。

3. 算法可视化SVG图示

下图清晰地展示了前缀和后缀自增的区别。

图片

4. 核心特性

  • 副作用 (Side Effect) : 这两个运算符的主要作用是改变变量自身的值,这被称为副作用。
  • 返回值差异: 前缀返回新值,后缀返回旧值。这是它们最关键的区别。
  • 优先级: 后缀 ++ 和 -- 的优先级非常高(与函数调用 () 同级),而前缀的优先级稍低(与 ! 同级)。
  • 未定义行为: 在一个表达式中对同一个变量多次使用自增/自减,可能导致未定义行为。例如 i = i++ 或 cout << i++ << i; 的结果在不同编译器下可能不同,应避免这种写法。

5. C++代码基础实现

#include <iostream>

int main() {
    int i = 5;
    int j = 5;

    // 前缀自增
    int prefix_result = ++i; 
    std::cout << "Prefix:" << std::endl;
    std::cout << "  Result: " << prefix_result << std::endl// 输出 6
    std::cout << "  i after: " << i << std::endl;           // 输出 6

    // 后缀自增
    int postfix_result = j++;
    std::cout << "\nPostfix:" << std::endl;
    std::cout << "  Result: " << postfix_result << std::endl// 输出 5
    std::cout << "  j after: " << j << std::endl;            // 输出 6

    return 0;
}

6. 优化策略

  • 优先使用前缀形式: 对于内置类型(如 int),编译器通常能优化得一样快。但对于复杂类型(如迭代器),前缀形式 (++it) 通常比后缀形式 (it++) 更高效,因为后缀形式需要创建一个临时对象来保存原始值,而前缀形式不需要。养成优先使用前缀形式的习惯是个好做法。
  • 单独使用时无差别: 如果只是为了增加变量的值,而不是在表达式中使用其返回值(如 i++; 单独一行),那么前缀和后缀没有性能差异。

7. 优缺点

  • 优点: 代码更简洁,特别是在循环中(for (int i=0; i<n; ++i))。
  • 缺点: 容易因混淆前缀和后缀的返回值而产生 bug。在复杂表达式中过度使用会严重降低代码可读性,并可能导致未定义行为。

8. 应用场景

  • 循环计数器for 循环的经典用法。
  • 迭代器遍历: 在遍历STL容器时,使用 ++it 来移动迭代器。
  • 计数: 简单地对某个事件发生的次数进行加一或减一。

9. 扩展

  • 指针运算: 自增/自减运算符也可以用于指针。ptr++ 会使指针指向内存中的下一个元素,移动的字节数取决于指针指向的类型大小。
  • 重载: C++ 允许为自定义类重载自增和自减运算符,以便让自定义对象(如智能指针、迭代器)支持这种操作。

10. 课后配套练习及答案

练习1: 循环打印使用 for 循环和自增运算符,打印从1到10的整数。

// 答案
#include <iostream>
int main() {
    for (int i = 1; i <= 10; ++i) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

练习2: 前缀与后缀的值预测并验证下面代码的输出。

// 答案
#include <iostream>
int main() {
    int x = 10;
    int y = 10;
    std::cout << ++x << std::endl// 预测:11
    std::cout << y++ << std::endl// 预测:10
    std::cout << x << std::endl;   // 预测:11
    std::cout << y << std::endl;   // 预测:11
    return 0;
}

练习3: 倒计时使用 while 循环和自减运算符,从10倒数到1。

// 答案
#include <iostream>
int main() {
    int count = 10;
    while (count > 0) {
        std::cout << count << std::endl;
        --count;
    }
    return 0;
}

练习4: 表达式求值预测并验证下面代码的输出。

// 答案
#include <iostream>
int main() {
    int a = 3;
    int b = 5;
    int c = (a++) * b; // a先参与运算(3),再自增
    std::cout << "a=" << a << ", c=" << c << std::endl// 预测:a=4, c=15
    return 0;
}

练习5: 复杂表达式求值预测并验证下面代码的输出。

// 答案
#include <iostream>
int main() {
    int i = 2;
    int j = 8;
    int k = (++i) + (j--); // i先自增(3),j先参与运算(8)再自减
    std::cout << "i=" << i << ", j=" << j << ", k=" << k << std::endl// 预测:i=3, j=7, k=11
    return 0;
}

11. 相关网络资源推荐

  • Increment/decrement operators - cppreference.com
  • Prefix vs Postfix in C++ - GeeksforGeeks