【问题标题】:increment operator more purpose? [duplicate]增量运算符更多目的? [复制]
【发布时间】:2013-02-16 22:40:55
【问题描述】:

我想知道递增和递减运算符( ++ -- )是否比它的简单用途更有意义,以使代码更简单

也许:

i++;

比以下更有效:

i = i + 1;

?

【问题讨论】:

  • 很久以前,当恐龙在地球上漫游时,一些编译器为i++ 生成比i = i + 1 更有效的代码。那些日子已经过去了。
  • @OliCharlesworth:这是 C# 问题。
  • @Nawaz: 是的。但无论如何,这个问题之前已经被问过数百次了......
  • @OliCharlesworth 有吗?我刚刚错误地投票关闭了您提出的链接作为重复项,现在我正在寻找一个真正指向重复项的链接。我找不到。
  • @jogojapan:嗯,例如stackoverflow.com/questions/7471891/… 覆盖它。

标签: c++ syntax increment decrement


【解决方案1】:

i++i = i + 1 之间的区别在于,第一个表达式只计算一次 i,第二个表达式计算两次。考虑:

int& f() {
    static int value = 0;
    std::cout << "f() called\n";
    return value;

}

f()++;         // writes the message once
f() = f() + 1; // writes the message twice

如果f() 在不同的调用中返回不同的引用,这两个表达式会产生截然不同的效果。当然,这并不是说这是好的设计......

【讨论】:

    【解决方案2】:

    您提供的两个示例几乎肯定会编译为完全相同的机器代码。编译器非常聪明。了解编译器很少执行您实际编写的代码。它会扭曲并塑造它以提高性能。任何现代编译器都会知道i++;i = i + 1; 是相同的(对于算术类型)并生成相同的输出。

    但是,除了代码可读性之外,有充分的理由同时拥有两者。从概念上讲,多次增加一个值和增加一个值是不同的操作——它们在这里只是相同的,因为你要加 1。一个加法,比如x + 3,是一个单一的操作,而做++++++x 代表三个单独的操作具有相同效果的操作。当然,聪明的编译器也会知道,对于算术类型的对象,x,它可以通过 x + N 以恒定时间递增 N

    但是,如果x 是具有重载operator+operator++ 的类类型,则编译器无法做出此假设。这两个运算符可能会做完全不同的事情。此外,将operator+ 实现为非常量时间操作会产生错误的印象。

    当我们处理迭代器时,两者的重要性就变得很明显了。只有随机访问迭代器支持加法和减法。例如,标准原始指针是随机访问迭代器,因为您可以执行ptr + 5 并获得指向第 5 个对象的指针。但是,所有其他类型的迭代器(双向、前向、输入)不支持这一点 - 您只能增加它们(并减少双向迭代器)。要使用双向迭代器到达第 5 个元素,您需要执行五次++。那是因为加法表示一个恒定时间的操作,但许多迭代器根本无法在恒定时间内遍历。强制多个增量表明它不是一个恒定时间的操作。

    【讨论】:

    • 您可能会争论关于递增和添加不同值的观点。当然,使用++ 推进迭代器,而不是成员函数,实际上是运算符重载滥用的情况。但这是对现代 C++ 差异的解释。这并不能解释最初的动机:C++ 有++,因为C 有++,因为B 有++。并且迭代器参数当然不适用于 B。
    • @JamesKanze 我想我并没有考虑++ 的来源。我没有给出我们首先拥有它的原因,只是解释为什么现在拥有它很方便。
    • 我不确定我是否同意“增量”和“加 1”在概念上是不同的。
    • @OliCharlesworth 我的意思是“多次递增”和“添加”在概念上是不同的。
    【解决方案3】:

    性能取决于i 的类型。

    如果它是内置类型,那么优化器会“注意到”您的两条语句是相同的,并为两者发出相同的代码。

    由于您使用了后增量(并忽略了分号),即使 i 是内置类型,这两个表达式也具有不同的值。 i++ 的值是旧值,i = i + 1 的值是新值。所以,如果你写:

    j = i++;
    k = (i = i + 1);
    

    那么现在两者是不同的,并且不会为两者发出相同的代码。

    由于后增量的后置条件与前置增量相同,因此您可以说后增量运算符的 主要 目的是它评估为不同的值。无论性能如何,它都能让语言更具表现力。

    如果i 具有带有重载运算符的类类型,那么它可能不会那么简单。从理论上讲,这两者甚至可能没有相同的结果,但假设“明智的”运算符i + 1 返回该类的新实例,然后对其进行移动分配(在 C++11 中,如果该类型具有移动赋值运算符)或复制分配(在 C++03 中)到 i。无法保证优化器可以像 operator++ 那样高效,尤其是当运算符重载定义在另一个翻译单元中关闭时。

    有些类型有operator++,但没有operator+,例如std::list&lt;T&gt;::iterator。不过,这不是 ++ 存在的原因,因为它存在于 C 中,并且 C 没有带有 ++ 但不带有 + 的类型。这是 C++ 充分利用现有技术的一种方式。

    【讨论】:

    • 如果你写k = (i = i + 1);,你有未定义的行为。 (j = i ++; 只是混淆了。)
    • 是你的最后一段,有人可能滥用运算符,并使用++ 来进行不相关的迭代器推进操作,这并不是拥有++ 的真正理由。跨度>
    • @JamesKanze:(a) 为什么是 UB? (b) 我不同意使用++ 来表示一般的“增量”而不是+=1 是操作员滥用,但我认为这完全是主观的。我认为人们可以(主观地)争辩说,除非实现数字类型,否则重载算术运算符始终是运算符滥用,但我个人很高兴任何模拟有限或无限类似 Peano 的系统使用++,任何模拟环的东西使用+* 等。我同意(并且我明确声明)这不是引入++ 的原因。
    • @JamesKanze:为什么 5.17/1 不能把我从 UB 中拯救出来? “赋值顺序在左右操作数的值计算之后,赋值表达式的值计算之前”。因此,从i 读取以确定k 的值是在分配给i 之后排序的。
    • 我对未定义行为的错误。我已经习惯了看到像i = ++ i 这样的东西嵌入到更多的表达式中。像k = (i = i + 1) 这样的东西是完全合法的(很遗憾,甚至被使用过)。
    【解决方案4】:

    在许多方面,运算符的主要目的是倒退的 兼容性。在设计 C++ 时,一般规则 是做 C 做的事情,至少对于非类类型;如果 C 没有 有++--,我怀疑C++ 会有它们。

    这当然引出了问题。这是不可思议的 他们会在现代编译器中生成不同的代码,并且 委员会会引入是相当不可思议的 他们出于优化的原因(尽管你从来没有 知道——引入移动语义主要是为了 优化原因)。但早在 1970 年代中期,在 C 的成长期?当时普遍认为, 介绍它们是因为它们对应于机器 PDP-11 上的说明。另一方面,他们是 已经存在于 B.C 从 B 那里获得它们。B 是 解释语言,所以没有问题 对应机器指令。我自己的怀疑, 适用于许多运算符(&amp;,而不是and 等) 是他们被引入是因为当时的发展是 主要用于电传打字机(tty),以及您输出到的每个字符 电传打字机发出了很多令人不快的噪音。所以越少 你需要的字符,更好的。

    关于++ i;i += 1;i = i + 1;之间的选择: 不必重复i 有一个明显的优势 (当然,它可以是一个或多或少复杂的表达式),所以 你至少想要i += 1;。 Python 停在那里,如果没有 除了将赋值视为语句之外的其他原因,而不是 而不是作为任意表达式的副作用。随着结束 30 年的 C 和 C++ 编程经验,我仍然觉得 在 Python 中编程时缺少 ++ i,即使我 几乎限制自己在 C++ 中将赋值视为 声明(并且不要将++ i 嵌入更复杂的 表达式)。

    【讨论】:

    • “B 是一种解释性语言,因此它们不存在对应于机器指令的问题”——不一定遵循。我当时不在,但 Ritchie 的“C 语言的发展”声称只有 B 的“早期版本”没有被编译。即使第一个实现没有编译,你能排除 B 在设计时考虑到编译吗?
    【解决方案5】:

    两者并不完全相同,虽然功能相同,但优先级有所不同i++i=i+1具有更高的优先级(更多优先级)

    【讨论】:

      【解决方案6】:

      尝试用一些更复杂的表达式替换 ++i 或 i++。您将在使用前增量/后增量功能时遇到困难。您将需要将表达式拆分为多个。这就像三元运算符。等价的,无论你输入代码的方式如何,编译器都可能会进行一些优化,但是三元运算符和前置/后置语法只是为你节省了空间和代码的可读性。

      【讨论】:

      • "您需要将表达式拆分为多个。"对我来说,这听起来更像是反对++ 的论据。在复杂表达式中间嵌入状态变化主要是一种混淆技术,严重的程序员会避免使用。
      • 当然。这就是为什么你必须在开发过程中思考,而不是像“++ 运算符不好”这样的一般性答案:当代码难以阅读时,它有它的用处并且不应该使用它。想象一下,有人会开始使用for (i = 0; i &lt; 10; i = i + 1) - 这太荒谬了。
      【解决方案7】:
      1. ++-- 运算符允许将其组合到其他语句中,而不是 i=i+1。例如while (i++ &lt; 10) 允许进行 while 循环检查并在其后进行增量。 i=i+1 无法做到这一点。

      2. ++-- 运算符可以在其他类中重载以具有其他含义或通过其他操作进行递增和递减。请参阅this page 了解更多信息。

      【讨论】:

      • 可能的,如果有些丑陋和可能低效,在没有该运算符的情况下对后增量i++ 的语义进行建模:while ((i=i+1,i-1) &lt; 10)
      • Re 1. 语言允许这样做,但我认为大多数程序员已经超越了while ( *dst++ = *src++ ); 之类的东西。当 K&R 编写第一个“C 语言编程”时,它已经被认为是不好的做法。
      • 回复。确实,您可以重载 ++ 来执行与添加一个完全无关的操作,但这通常被认为是运算符重载滥用。
      • 我认为使用++while (i++ &lt; 10) 是不错的做法,但使用while ( *dst++ = *src++ ) 肯定是。使用运算符重载,您可以执行以下操作。我定义了一个保持整数值的类,最小值和最大值可以定义为构造函数参数,比如 0 和 100。现在我重载 --++ 运算符,以便它只有在新的情况下才会递减和递增value 在 0 和 100 的范围内。所以如果 x 的值为 100 而你做 x++,它将保持在 100。我不认为这种滥用,而是针对特定需求的非常具体的设计.
      【解决方案8】:

      不,只是为了让打字简单,让语法看起来更简单。

      当它们都被编译时,它们被缩减为相同的编译代码,并以相同的速度运行。

      【讨论】:

        【解决方案9】:

        两者都会通过一个体面的编译器在优化代码中生成相同的机器指令。

        如果编译器发现i++ 更高效,那么它会将i=i+1 转换为i++,反之亦然。不管你写什么结果都是一样的。

        我更喜欢 ++i 。我从不i=i+1

        【讨论】:

        • 我认为 OP 以一种误导性的方式提出了这个问题。 i = i + 1; 需要指定i 两次; i 不是问题,但 i 可以是任意表达式。 (如果你写f() = f() + 1;,而不是++ f();,编译器必须调用f两次,除非它可以“看到”f。)真正的问题是++ i;i += 1;
        • 我在完成评论之前不小心按了 Enter 的事实:-)。没有我的后续编辑,这没有任何意义。
        • @JamesKanze:在我的回答中,我假设i 是内置类型之一。
        猜你喜欢
        • 2023-03-26
        • 2012-11-23
        • 2011-04-12
        • 1970-01-01
        • 2019-09-25
        • 2016-02-02
        • 1970-01-01
        • 2017-06-23
        • 1970-01-01
        相关资源
        最近更新 更多