ChatGPT解决这个技术问题 Extra ChatGPT

C++ 中的 i++ 和 ++i 之间是否存在性能差异?

我们有问题is there a performance difference between i++ and ++i in C?

C++ 的答案是什么?

我重新标记了,因为这两个标签是查找此类问题的最简单方法。我还查看了其他没有内聚标签的人,并给了他们内聚标签。
使用 C++ 和 ++C 之间是否存在性能差异?
文章:迭代器使用前缀递增运算符 ++it 而不是后缀运算符 it++ 是否合理? - viva64.com/en/b/0093
它可能取决于处理器。 PDP-11 具有后递增和前递减寻址模式,因此 i++--i++ii-- 更有效。

z
zar

[执行摘要:如果您没有使用 i++ 的特定理由,请使用 ++i。]

对于 C++,答案有点复杂。

如果 i 是简单类型(不是 C++ 类的实例),则 then the answer given for C ("No there is no performance difference") 成立,因为编译器正在生成代码。

但是,如果 i 是 C++ 类的实例,则 i++++i 正在调用 operator++ 函数之一。这是这些功能的标准对:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

由于编译器不生成代码,而只是调用 operator++ 函数,因此无法优化掉 tmp 变量及其关联的复制构造函数。如果复制构造函数很昂贵,那么这可能会对性能产生重大影响。


正如另一条评论所提到的,编译器可以避免的是通过 NRVO 在调用者中分配 tmp 来返回 tmp 的第二个副本。
如果 operator++ 是内联的,编译器不能完全避免这种情况吗?
是的,如果 operator++ 是内联的并且从不使用 tmp ,则可以将其删除,除非 tmp 对象的构造函数或析构函数有副作用。
@kriss:C 和 C++ 之间的区别在于,在 C 中,您可以保证运算符将被内联,并且在这一点上,体面的优化器将能够消除差异;相反,在 C++ 中,您不能假设内联 - 并非总是如此。
如果答案提到了一些关于持有指向动态分配(堆)内存的指针(无论是自动、智能还是原始)的类的内容,我会 +1,其中复制构造函数必须执行深度复制。在这种情况下,没有参数,++i 可能比 i++ 效率高一个数量级。他们的关键是养成在算法实际上不需要后增量语义时使用前增量的习惯,然后您将养成编写代码的习惯,这些代码本质上会提高效率,无论如何你的编译器可以优化。
l
lc.

是的。有。

++ 运算符可以定义为函数,也可以不定义为函数。对于原始类型(int、double、...),运算符是内置的,因此编译器可能能够优化您的代码。但是对于定义 ++ 运算符的对象,情况就不同了。

operator++(int) 函数必须创建一个副本。这是因为 postfix ++ 应该返回一个不同于它所持有的值:它必须将它的值保存在一个临时变量中,增加它的值并返回临时变量。在 operator++() 的情况下,前缀 ++,不需要创建副本:对象可以自增,然后简单地返回自己。

以下是这一点的说明:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

每次调用 operator++(int) 都必须创建一个副本,编译器对此无能为力。当给出选择时,使用 operator++();这样你就不会保存副本。在许多增量(大循环?)和/或大对象的情况下,它可能很重要。


“预增量运算符在代码中引入了数据依赖:CPU 必须等待增量操作完成,然后才能在表达式中使用它的值。在深度流水线 CPU 上,这会引入停顿。没有数据依赖对于后增量运算符。” (游戏引擎架构(第 2 版))因此,如果后增量的副本不是计算密集型的,它仍然可以击败前增量。
在后缀代码中,这是如何工作的 C t(*this); ++(*this); return t; 在第二行中,您正在向右递增 this 指针,那么如果您要递增 this,t 如何得到更新。 this 的值不是已经复制到 t 中了吗?
The operator++(int) function must create a copy. 不,不是。不超过 operator++()
2
2 revs

这是增量运算符位于不同翻译单元中的情况的基准。使用 g++ 4.5 的编译器。

暂时忽略风格问题

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

O(n) 增量

测试

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

结果

在虚拟机上使用 g++ 4.5 的结果(计时以秒为单位):

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

O(1) 增量

测试

现在让我们获取以下文件:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

它在增量中没有任何作用。这模拟了增量具有恒定复杂性的情况。

结果

结果现在变化很大:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

结论

性能方面

如果您不需要以前的值,请养成使用预增量的习惯。即使与内置类型保持一致,您也会习惯它,并且如果您将内置类型替换为自定义类型,则不会冒遭受不必要的性能损失的风险。

语义方面

i++ 表示递增 i,不过我对之前的值感兴趣。

++i 表示增量 i,我对当前值或增量 i 感兴趣,对之前的值不感兴趣。同样,即使您现在不习惯,您也会习惯它。

克努特。

过早的优化是万恶之源。正如过早的悲观情绪一样。


有趣的测试。现在,差不多两年半后,gcc 4.9 和 Clang 3.4 显示出类似的趋势。 Clang 两者都快一点,但前后缀之间的差异比 gcc 差。
我真正想看到的是一个真实世界的例子,其中 ++i / i++ 有所作为。例如,它对任何 std 迭代器都有影响吗?
@JakobSchouJensen:这些都是为了成为现实世界的例子。考虑一个大型应用程序,具有复杂的树结构(例如 kd 树、四叉树)或表达式模板中使用的大型容器(以最大化 SIMD 硬件上的数据吞吐量)。如果它在那里有所作为,我不确定如果在语义上不需要的话,为什么会回退到特定情况下的后增量。
@phresnel:我不认为 operator++ 在你的日常生活中是一个表达式模板——你有一个实际的例子吗? operator++ 的典型用途是整数和迭代器。那就是我认为知道是否有任何区别会很有趣(当然整数没有区别-但是迭代器)。
@JakobSchouJensen:没有实际的业务示例,但有一些数字运算应用程序可以计算东西。 Wrt 迭代器,考虑一个以惯用 C++ 风格编写的光线追踪器,并且您有一个用于深度优先遍历的迭代器,例如 for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; },不要介意实际的树结构(BSP、kd、四叉树、八叉树网格等)。 )。这样的迭代器需要维护一些状态,例如 parent nodechild nodeindex 和类似的东西。总而言之,我的立场是,即使只有几个例子,......
J
James Sutherland

说编译器无法优化后缀情况下的临时变量副本并不完全正确。对 VC 的快速测试表明,它至少在某些情况下可以做到这一点。

在以下示例中,生成的代码对于前缀和后缀是相同的,例如:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

无论您执行 ++testFoo 还是 testFoo++,您仍然会得到相同的结果代码。事实上,在没有从用户那里读取计数的情况下,优化器将整个事情归结为一个常数。所以这:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

结果如下:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

因此,虽然后缀版本可能会更慢,但如果您不使用它,优化器很可能足以摆脱临时副本。


您忘记了重要的一点,即这里所有内容都是内联的。如果运算符的定义不可用,则无法避免在行外代码中进行的复制;内联优化是非常明显的,所以任何编译器都会这样做。
H
Harshil Modi

Google C++ Style Guide 说:

Preincrement 和 Predecrement 将递增和递减运算符的前缀形式 (++i) 用于迭代器和其他模板对象。定义:当一个变量递增(++i 或 i++)或递减(--i 或 i--)且未使用表达式的值时,必须决定是预递增(递减)还是后递增(递减)。优点:当返回值被忽略时,“pre”形式(++i)的效率永远不会低于“post”形式(i++),而且通常效率更高。这是因为后递增(或递减)需要制作 i 的副本,这是表达式的值。如果 i 是迭代器或其他非标量类型,复制 i 可能会很昂贵。既然当值被忽略时两种类型的增量行为相同,为什么不总是预增量呢?缺点:在 C 中形成的传统是在不使用表达式值时使用后增量,尤其是在 for 循环中。有些人发现后增量更容易阅读,因为“主语”(i)在“动词”(++)之前,就像在英语中一样。决定:对于简单的标量(非对象)值,没有理由偏爱一种形式,我们也允许。对于迭代器和其他模板类型,使用预增量。


“决定:对于简单的标量(非对象)值,没有理由偏爱一种形式,我们也允许。对于迭代器和其他模板类型,使用预增量。”
呃……那是什么东西?
答案中提到的链接目前已损坏
佚名

我想指出 Andrew Koenig 最近在 Code Talk 上发表的一篇出色的文章。

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

在我们公司,我们还在适用的情况下使用 ++iter 的约定来保持一致性和性能。但安德鲁提出了关于意图与绩效的被忽视的细节。有时我们想使用 iter++ 而不是 ++iter。

因此,首先确定您的意图,如果 pre 或 post 无关紧要,然后使用 pre,因为它可以通过避免创建额外的对象并抛出它来获得一些性能优势。


M
Motti

@科坦

...提出了有关意图与性能的被忽视的细节。有时我们想使用 iter++ 而不是 ++iter。

显然 post 和 pre-increment 有不同的语义,我相信每个人都同意,当使用结果时,你应该使用适当的运算符。我认为问题是当结果被丢弃时应该做什么(如在 for 循环中)。 this 问题(恕我直言)的答案是,由于性能考虑充其量可以忽略不计,因此您应该做更自然的事情。对我自己来说,++i 更自然,但我的经验告诉我,我是少数,使用 i++ 将减少 大多数 人阅读您的代码的金属开销。

毕竟这就是语言不被称为“++C”的原因。[*]

[*] 插入关于 ++C 是一个更合乎逻辑的名称的强制性讨论。


@Motti:(开玩笑)如果您还记得 Bjarne Stroustrup C++ 最初将其编码为生成 C 程序的预编译器,那么 C++ 名称是合乎逻辑的。因此 C++ 返回了一个旧的 C 值。或者可能是为了增强 C++ 从一开始就存在概念上的缺陷。
H
Hans Malherbe

++i - 更快地不使用返回值 i++ - 更快地使用返回值

当不使用返回值时,编译器保证不会在 ++i 的情况下使用临时值。不保证更快,但保证不会变慢。

当使用返回值时,i++ 允许处理器将增量和左侧推入管道,因为它们不相互依赖。 ++i 可能会停止管道,因为处理器无法启动左侧,直到预增量操作一直蜿蜒通过。同样,不能保证流水线停顿,因为处理器可能会找到其他有用的东西来坚持。


0
0124816

Mark:只是想指出,operator++ 是内联的好候选,如果编译器选择这样做,在大多数情况下冗余副本将被消除。 (例如,迭代器通常是 POD 类型。)

也就是说,在大多数情况下使用 ++iter 仍然是更好的风格。 :-)


2
2 revs

当您将运算符视为返回值的函数以及它们的实现方式时,++ii++ 之间的性能差异会更加明显。为了更容易理解正在发生的事情,以下代码示例将使用 int,就好像它是 struct

++i 递增变量,然后 返回结果。这可以就地完成,并且使用最少的 CPU 时间,在许多情况下只需要一行代码:

int& int::operator++() { 
     return *this += 1;
}

但对于 i++ 则不能这样说。

后递增 i++ 通常被视为在递增之前返回原始值。但是,函数只有在完成后才能返回结果。因此,有必要创建包含原始值的变量的副本,递增变量,然后返回包含原始值的副本:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

当前增量和后增量之间没有功能差异时,编译器可以执行优化以使两者之间没有性能差异。但是,如果涉及到 structclass 等复合数据类型,则会在后增量时调用复制构造函数,如果需要深度复制,则无法执行此优化。因此,预增量通常比后增量更快并且需要更少的内存。


M
Mike Dunlavey

@Mark:我删除了我之前的答案,因为它有点翻转,仅此一项就值得一票。实际上,我认为这是一个很好的问题,因为它询问了很多人的想法。

通常的答案是 ++i 比 i++ 快,毫无疑问,但更大的问题是“你什么时候应该关心?”

如果用于递增迭代器的 CPU 时间比例小于 10%,那么您可能不会在意。

如果用于递增迭代器的 CPU 时间比例大于 10%,您可以查看哪些语句正在执行该迭代。看看你是否可以只增加整数而不是使用迭代器。你有可能,虽然在某种意义上它可能不太理想,但很有可能你会节省在这些迭代器上花费的所有时间。

我见过一个例子,其中迭代器递增消耗了超过 90% 的时间。在这种情况下,进行整数递增将执行时间减少了该数量。 (即优于 10 倍加速)


T
Tim Cooper

@wilhelmtell

编译器可以省略临时。来自另一个线程的逐字记录:

即使这样做会改变程序行为,C++ 编译器也可以消除基于堆栈的临时变量。 VC 8 的 MSDN 链接:

http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx


那不相关。 NRVO 避免了将 "CC::operator++(int)" 中的 t 复制回调用者的需要,但 i++ 仍将复制调用者堆栈上的旧值。如果没有 NRVO,i++ 会创建 2 个副本,一个到 t,一个返回给调用者。
J
Josh

即使在没有性能优势的内置类型上也应该使用 ++i 的原因是为自己养成一个好习惯。


对不起,但这让我很困扰。谁说这是一个“好习惯”,而它几乎不重要?如果人们想让它成为他们学科的一部分,那很好,但让我们将重要的原因与个人品味的问题区分开来。
@MikeDunlavey 好的,那么当它不重要时,您通常使用哪一侧? xD 它不是一个或另一个不是它! post++(如果您使用它的一般含义。更新它,返回旧的)完全不如 ++pre(更新它,返回)没有任何理由想要降低性能。如果您想在之后更新它,程序员甚至根本不会做 post++。当我们已经拥有它时,不要浪费时间复制。使用后更新。然后编译器具有您希望它具有的常识。
@Puddle:当我听到这个消息时:“你没有任何理由想要表现不佳”,我知道我听到的是“一分钱一分货——一分钱一分货”。您需要了解所涉及的数量级。只有当这占所涉及时间的 1% 以上时,您才应该考虑一下。通常,如果您正在考虑这一点,那么您没有考虑到的问题是数百万倍的问题,这就是使软件比它可能运行的慢得多的原因。
@MikeDunlavey 反刍废话以满足您的自我。你试图听起来像一个聪明的和尚,但你什么也没说。所涉及的幅度......如果你应该关心的时间只有 1% 以上...... xD 绝对运球。如果它效率低下,则值得了解和修复。正是因为这个原因,我们才在这里思考这个问题!我们并不关心我们可以从这些知识中获得多少。当我说你不想降低性能时,继续,然后解释一个该死的场景。怀斯先生!
2
2 revs, 2 users 95%

两者都一样快;)如果您希望处理器的计算相同,则只是执行顺序不同。

例如,以下代码:

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

生成以下程序集:

0x0000000100000f24 : 推 %rbp 0x0000000100000f25 : mov %rsp,%rbp 0x0000000100000f28 : movl $0x0,-0x4(%rbp) 0x0000000+1100cl0-f2f> in: (%rbp) 0x0000000100000f32 : movl $0x0,-0x8(%rbp) 0x0000000100000f39 : incl -0x8(%rbp) 0x0000000100000f3c : mov $0x0,%eax 0000010101 main+29>:leaveq 0x0000000100000f42 :retq

您会看到,对于 a++ 和 b++,它是一个 incl 助记符,所以它是相同的操作;)


它是 C,而 OP 询问 C++。在 C 中也是一样的。在 C++ 中,速度更快的是 ++i;由于其对象。然而,一些编译器可能会优化后增量运算符。
2
2 revs

预期的问题是关于何时未使用结果(从 C 的问题中可以清楚地看到)。由于问题是“社区维基”,有人可以解决这个问题吗?

关于过早优化,经常引用 Knuth。这是正确的。但是 Donald Knuth 永远不会用你现在看到的可怕的代码来辩护。在 Java 整数(不是 int)中见过 a = b + c 吗?这相当于 3 次装箱/拆箱转换。避免这样的事情很重要。并且无用地写 i++ 而不是 ++i 是同样的错误。编辑:正如 phresnel 在评论中所说的那样,这可以概括为“过早的优化是邪恶的,过早的悲观化也是如此”。

即使人们更习惯于 i++ 这一事实也是不幸的 C 遗产,这是由 K&R 的概念错误造成的(如果您遵循意图论证,这是一个合乎逻辑的结论;并且因为 K&R 是 K&R 而为 K&R 辩护是没有意义的,他们是很棒,但他们作为语言设计者并不出色;C 设计中存在无数错误,从 get() 到 strcpy(),再到 strncpy() API(从第一天起就应该有 strlcpy() API) )。

顺便说一句,我是那些对 C++ 还不够熟悉,以至于觉得 ++i 读起来很烦人的人之一。不过,我使用它,因为我承认它是正确的。


我看到你正在攻读博士学位。对编译器优化和类似的事情感兴趣。这很好,但不要忘记学术界是一个回音室,常识经常被排除在门外,至少在 CS 中您可能对此感兴趣:stackoverflow.com/questions/1303899/…
我从来没有发现 ++ii++ 更烦人(事实上,我发现它更酷),但你的帖子的其余部分得到了我的完全认可。也许加一点“过早的优化是邪恶的,过早的悲观也是如此”
strncpy 在他们当时使用的文件系统中发挥作用;文件名是一个 8 个字符的缓冲区,它不必以空值结尾。你不能责怪他们没有看到 40 年后语言进化的未来。
@MattMcNabb:8 个字符的文件名不是 MS-DOS 独有的吗? C 是 Unix 发明的。无论如何,即使 strncpy 有道理,strlcpy 的缺乏也不是完全合理的:即使是原始 C 语言也有不应该溢出的数组,这需要 strlcpy;最多,他们只是错过了意图利用这些漏洞的攻击者。但是不能说预测这个问题是微不足道的,所以如果我重写我的帖子,我不会使用相同的语气。
@Blaisorblade:我记得,早期的 UNIX 文件名限制为 14 个字符。缺少 strlcpy() 的理由是它还没有被发明出来。
T
Tristan

由于您也要求使用 C++,因此这里是 java (made with jmh) 的基准:

private static final int LIMIT = 100000;

@Benchmark
public void postIncrement() {
    long a = 0;
    long b = 0;
    for (int i = 0; i < LIMIT; i++) {
        b = 3;
        a += i * (b++);
    }
    doNothing(a, b);
}

@Benchmark
public void preIncrement() {
    long a = 0;
    long b = 0;
    for (int i = 0; i < LIMIT; i++) {
        b = 3;
        a += i * (++b);
    }
    doNothing(a, b);
}  

结果表明,即使在某些计算中实际使用了递增变量 (b) 的值,强制在后递增的情况下需要存储一个附加值,每次操作的时间也完全相同:

Benchmark                         Mode  Cnt  Score   Error  Units
IncrementBenchmark.postIncrement  avgt   10  0,039   0,001  ms/op
IncrementBenchmark.preIncrement   avgt   10  0,039   0,001  ms/op

S
Siddhant Jain

++ii = i +1 快,因为在 i = i + 1 中进行了两次操作,第一次递增,第二次将其分配给变量。但是在 i++ 中只发生了增量操作。


S
Severin Pappadeux

是时候为人们提供智慧的宝石了;) - 有一个简单的技巧可以使 C++ 后缀增量的行为与前缀增量几乎相同(这是为我自己发明的,但在其他人的代码中也看到了它,所以我不是独自的)。

基本上,技巧是使用辅助类在返回后推迟增量,RAII 来救援

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

Invented 适用于一些繁重的自定义迭代器代码,它减少了运行时间。前缀与后缀的成本现在是一个参考,如果这是自定义运算符进行大量移动,前缀和后缀对我来说产生了相同的运行时间。


4
4 revs, 3 users 73%

++ii++ 快,因为它不返回值的旧副本。

它也更直观:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

This C example 打印“02”而不是您可能期望的“12”:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

Same for C++

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}

我认为答案(er)没有任何线索说明操作员想要什么或更快的词是什么意思..