前缀和后缀运算符的必要性

时间:2013-07-02 16:45:59

标签: c

前缀和后缀增量运算符的必要性是什么?还不够吗?

到目前为止,存在类似的while / do-while必要性问题,然而,将它们两者混淆(在理解和使用方面)并没有那么多。但同时具有前缀和后缀(如这些运算符的优先级,它们的关联,使用,工作)。 并且有没有人经历过你说过的情况“嘿,我将使用postfix增量。它在这里很有用。”

8 个答案:

答案 0 :(得分:5)

POSTFIX和PREFIX不一样。 POSTFIX仅在当前语句/指令结束后递增/递减。而PREFIX递增/递减,然后执行当前步骤。例如,要运行循环n次,

while(n--)
{ }

完美无缺。但是,

while(--n)
{
}

只能运行n-1次

或者例如:

x = n--;x = --n;不同(第二种格式为xn将相同)。在课程中,我们可以在多个步骤中使用二元运算符-执行相同的操作。

如果只有post --,我们必须分两步编写x = --n

还有其他更好的理由,但这是我认为保留前缀和后缀运算符的好处。

答案 1 :(得分:3)

[编辑回答OP的第一部分]

显然i++++i同时影响i但返回不同的值。 操作不同。因此,许多代码利用了这些差异。

两个运营商最明显的需要是C的40年代码库。一旦语言中的某个功能被广泛使用,很难删除。

当然,只能使用一种语言或不使用一种语言来定义新语言。但它会在皮奥里亚发挥吗?我们也可以摆脱-运营商,只需使用a + -b,但我认为这是一个艰难的卖点。

需要两者吗?

前缀运算符很容易模仿++i的替代代码与i += 1几乎相同。除了运营商优先权之外,我认为没有区别。

后缀运算符很难模仿 - 就像此失败的尝试if(i++)if(i += 1)一样。

如果未来的C转向折旧其中之一,我怀疑如上所述,折旧前缀运算符的功能会更容易替换。

前瞻性思维:>>和<<运算符在C ++中被用来做与整数位移不同的事情。也许++前后++会在另一种语言中产生扩展意义。

[原文如下]

回答OP问题"有没有人经历过你说过的情况"嘿,我将使用postfix增量。它在这里很有用"?

各种数组处理,如char [],有益处。从0开始的数组索引有助于后缀增量。对于获取/设置数组元素之后,在下一个数组访问之前唯一要处理的是增加索引。不妨立即这样做。

使用前缀增量,可能需要为第0个元素提供一种类型的提取,为其余元素提供另一种类型的提取。

size_t j = 0;

for (size_t i = 0, (ch = inbuffer[i]) != '\0'; i++) {
  if (condition(ch)) {
    outbuffer[j++] = ch;  // prefer this over below
  }
}
outbuffer[j] = '\0';

VS

for (size_t i = 0, (ch = inbuffer[i]) != '\0'; ++i) {
  if (condition(ch)) {
    outbuffer[j] = ch;
    ++j;
  }
}
outbuffer[j] = '\0';

答案 2 :(得分:1)

我认为唯一合理的答案就是取消两者。

例如,如果您要取消使用后缀运算符,那么在使用n++对代码进行紧凑表达的情况下,您现在必须引用(++n - 1),否则您将不得不重新排列其他条款。

如果你在上面提到n的表达式之前或之后打破了增量或减少它自己的行,那么它与你使用的表达方式并不相关,但在这种情况下你可以很容易地使用不,并用n = n + 1;

替换该行

因此,或许真正的问题是带有副作用的表达。如果你喜欢紧凑的代码,那么你会发现前后都是不同情况所必需的。否则,保持其中任何一个似乎都没有多大意义。

每个的使用示例:

char array[10];
char * const end = array + sizeof(array) / sizeof(*array);
char *p = end;
int i = 0;

/* set array to { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } */
while (p > array)
    *--p = i++;

p = array;
i = 0;
/* set array to { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } */
while (p < end)
    *p++ = i++;

答案 3 :(得分:1)

它们是必要的,因为它们已被用于大量代码中,因此如果它们被删除,那么很多代码将无法编译。

至于为什么它们一开始就存在,较旧的编译器可以为++ii++生成比i+=1(i+=1)-1更高效的代码。对于较新的编译器,这通常不是问题。

后缀版本是异常的,因为在C中没有其他地方有一个运算符修改其操作数但是计算其操作数的先前值。

当然可以通过仅使用前缀或后缀增量运算符中的一个或其他来获得。通过仅使用whiledo while中的一个或另一个来获取它会更困难,因为它们之间的差异大于我视图中前缀和后缀增量之间的差异。

当然可以不使用前缀或后缀增量,或whiledo while。但是,你在哪里画出了不必要的东西和什么是有用的抽象之间的界线呢?

答案 4 :(得分:1)

这是一个使用两者的快速示例;基于数组的堆栈,堆栈向0增长:

#define STACKSIZE ...

typedef ... T;

T stack[STACKSIZE];
size_t stackptr = STACKSIZE;

// push operation
if ( stackptr )
  stack[ --stackptr ] = value;  

// pop operation
if ( stackptr < STACKSIZE )
  value = stack[ stackptr++ ];

现在我们可以在没有++和 - 运算符的情况下完成同样的事情,但它不会干净地扫描。

答案 5 :(得分:1)

对于C语言中的任何其他模糊机制,有各种历史原因。在恐龙走遍地球的古代,编纂者可以使用i++而不是i+=1来制作更高效的代码。在某些情况下,编译器会为i++生成效率低于++i的代码,因为i++需要将值保存以便以后递增。除非你有一个恐龙编译器,否则这一点在效率方面都不是最重要的。

对于C语言中的任何其他模糊机制,如果存在,人们将开始使用它。我将使用公共表达式*p++作为示例(它表示:p是一个指针,取p的内容,使用它作为表达式的结果,然后递增指针)。它必须使用postfix而不是前缀,否则就意味着完全不同的东西。

有些恐龙曾经开始编写不必要的复杂表达式,例如*p++,因为它们确实如此,它已经变得普遍,今天我们认为这些代码是微不足道的。不是因为,而是因为我们习惯于阅读它。

但在现代编程中,绝对没有理由写*p++。例如,如果我们查看memcpy函数的实现,它具有以下先决条件:

void* memcpy (void* restrict s1, const void* restrict s2, size_t n)
{
  uint8_t* p1 = (uint8_t*)s1;
  const uint8_t* p2 = (const uint8_t*)s2;

然后,实现实际复制的一种流行方式是:

while(n--)
{
  *p1++ = *p2++;
}

现在有些人会欢呼,因为我们使用的代码很少。但是很少的代码行不一定是良好代码的衡量标准。通常情况正好相反:考虑用一行while(n--)*p1++=*p2++;替换它,你就会明白为什么会这样。

我认为这两种情况都不具有可读性,你必须是一位经验丰富的C程序员才能抓住它而不会挠头五分钟。你可以编写相同的代码:

while(n != 0)
{
  *p1 = *p2;

   p1++;
   p2++;  
   n--;
}

更清晰,最重要的是它产生与第一个示例完全相同的机器代码

现在看看发生了什么:因为我们决定不在一个表达式中编写带有大量操作数的模糊代码,所以我们也可以使用++p1++p2。它会提供相同的机器代码。前缀或后缀无关紧要。但是在第一个带有模糊代码的例子中,*++p1 = *++p2会完全改变其含义。

总结一下:

  • 由于历史原因,存在前缀和后缀增量运算符。
  • 在现代编程中,拥有两个不同的此类运算符是完全多余的,除非您在同一表达式中使用多个运算符编写模糊代码。
  • 如果你编写晦涩的代码,会找到激励使用前缀和后缀的方法。但是,所有这些代码都可以被重写。

您可以将其用作代码的质量度量:如果您发现自己编写代码,无论您使用的是前缀还是后缀,那么您编写的代码都是错误的。停止它,重写代码。

答案 6 :(得分:0)

前缀运算符首先递增值,然后在表达式中使用它。 Postfix运算符,首先使用表达式中的值并递增值

前缀/后缀运算符的基本用法是汇编程序将其替换为单个递增/递减指令。如果我们使用算术运算符而不是递增或递减运算符,汇编程序会用两个或三个指令替换它。这就是我们使用递增/递减运算符的原因。

答案 7 :(得分:0)

你不需要两者。

它对于实现堆栈很有用,因此它存在于某些机器语言中。从那里它被间接地继承到C(其中这种冗余仍然有些有用,并且一些C程序员似乎喜欢在单个表达式中组合两个不相关的操作的想法),并且从C到任何其他类似C的语言。