前缀和后缀增量运算符的必要性是什么?一个还不够吗?
就这一点而言,存在类似的 while/do-while 必要性问题,但是,同时拥有它们并没有太多混淆(在理解和使用方面)。但是同时具有前缀和后缀(例如这些运算符的优先级,它们的关联,用法,工作)。有没有人遇到过你说“嘿,我将使用后缀增量。它在这里很有用”的情况。
前缀和后缀增量运算符的必要性是什么?一个还不够吗?
就这一点而言,存在类似的 while/do-while 必要性问题,但是,同时拥有它们并没有太多混淆(在理解和使用方面)。但是同时具有前缀和后缀(例如这些运算符的优先级,它们的关联,用法,工作)。有没有人遇到过你说“嘿,我将使用后缀增量。它在这里很有用”的情况。
POSTFIX and PREFIX are not the same. POSTFIX increments/decrements only after the current statement/instruction is over. Whereas PREFIX increments/decrements and then executes the current step. Example, To run a loop n times,
while(n--)
{ }
works perfectly. But,
while(--n)
{
}
will run only n-1 times
Or for example:
x = n--;
different then x = --n;
(in second form value of x
and n
will be same). Off-course we can do same thing with binary operator -
in multiple steps.
Point is suppose if there is only post --
then we have to write x = --n
in two steps.
There can be other better reasons, But this is one I suppose a benefit to keep both prefix and postfix operator.
[编辑回答OP的第一部分]
显然i++
,++i
两者都影响i
相同但返回不同的值。 操作不同。因此,很多代码都利用了这些差异。
拥有这两个运算符最明显的需要是 40 年的 C 代码库。一旦一种语言中的一个特性被广泛使用,就很难删除。
当然,一种新语言可以只定义一个或一个都没有。但它会在皮奥里亚演出吗?我们也可以摆脱-
运营商,只需使用a + -b
,但我认为这很难卖。
两者都需要?
前缀运算符很容易用替代代码来模仿,因为++i
它与i += 1
. 除了括号解决的运算符优先级之外,我看不出有什么区别。
后缀运算符模仿起来很麻烦 - 就像这次失败的尝试if(i++)
与if(i += 1)
.
如果未来的 C 开始贬值其中一个,我怀疑它会贬值前缀运算符,因为它的功能如上所述,更容易替换。
前瞻性的想法:>> 和 << 运算符在 C++ 中被用于做与整数位移完全不同的事情。也许 ++pre 和 post++ 会在另一种语言中产生扩展的含义。
【原文如下】
回答尾随的 OP 问题“有没有人经历过你说“嘿,我将使用后缀增量。它在这里有用吗?
各种数组处理,如 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';
对比
for (size_t i = 0, (ch = inbuffer[i]) != '\0'; ++i) {
if (condition(ch)) {
outbuffer[j] = ch;
++j;
}
}
outbuffer[j] = '\0';
它们是必要的,因为它们已经在很多代码中使用,所以如果它们被删除,那么很多代码将无法编译。
++i
至于它们最初存在的原因,旧的编译器可以为和生成i++
比它们更有效的代码i+=1
和(i+=1)-1
。对于较新的编译器,这通常不是问题。
后缀版本有点反常,因为在 C 语言中没有其他地方存在修改其操作数但计算其操作数的先前值的运算符。
当然可以通过仅使用前缀或后缀增量运算符中的一种或其他来获得。while
仅使用 or 中的一个或另一个会有点困难do while
,因为在我看来,它们之间的差异大于前缀和后缀增量之间的差异。
当然,一个人可以不使用前缀或后缀增量,或者while
or do while
。但是你在什么是不必要的杂乱无章和什么是有用的抽象之间划清界限呢?
这是一个使用两者的快速示例;基于数组的堆栈,堆栈向 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++ ];
现在我们可以在没有 ++ 和 -- 运算符的情况下完成完全相同的事情,但它不会扫描得那么干净。
至于 C 语言中任何其他晦涩难懂的机制,都有各种历史原因。在恐龙在地球上行走的远古时代,编译器将使用i++
比i+=1
. 在某些情况下,编译器生成的 for 代码效率会i++
低于 for ++i
,因为i++
需要保存值以供以后递增。除非你有一个恐龙编译器,否则这些在效率方面都无关紧要。
至于C语言中任何其他晦涩难懂的机制,如果存在,人们就会开始使用它。我将使用通用表达式*p++
作为示例(这意味着:p
是一个指针,取 的内容p
,将其用作表达式的结果,然后递增指针)。它必须使用后缀而不是前缀,否则将意味着完全不同的东西。
一些恐龙曾经开始编写不必要的复杂表达式,例如*p++
and,因为他们这样做了,所以它变得很普遍,今天我们认为这样的代码是微不足道的。不是因为它是,而是因为我们习惯于阅读它。
但在现代编程中,绝对没有理由编写*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
and ++p2
。它会给出相同的机器代码。前缀或后缀无关紧要。但是在第一个示例中使用晦涩的代码,*++p1 = *++p2
就完全改变了含义。
把它们加起来:
您可以将其用作代码质量的衡量标准:如果您发现自己编写的代码无论使用前缀还是后缀都很重要,那么您正在编写糟糕的代码。停止它,重写代码。
我认为唯一可以保留的公平答案是同时取消它们。
例如,如果您要取消后缀运算符,那么曾经使用 紧凑表示代码的地方n++
,您现在必须引用(++n - 1)
,或者您必须重新排列其他术语。
如果您在上面提到的表达式之前或之后将增量或减量分解到自己的行n
上,那么您使用的并不真正相关,但在这种情况下,您可以轻松地不使用任何一个,并将该行替换为n = n + 1;
因此,这里真正的问题可能是带有副作用的表达式。如果您喜欢紧凑的代码,那么您会发现 pre 和 post 对于不同的情况都是必要的。否则,保留它们中的任何一个似乎都没有多大意义。
每个示例的用法:
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++;
前缀运算符首先增加值,然后在表达式中使用它。后缀运算符,首先使用表达式中的值并递增值
前缀/后缀运算符的基本用法是汇编程序用单增量/减量指令替换它。如果我们使用算术运算符而不是递增或递减运算符,汇编程序会用两条或三条指令代替它。这就是我们使用递增/递减运算符的原因。
你不需要两者。
它对于实现堆栈很有用,因此它存在于某些机器语言中。从那里它被间接继承到 C(其中这种冗余仍然有些用,一些 C 程序员似乎喜欢在一个表达式中组合两个不相关的操作的想法),并从 C 继承到任何其他类似 C 的语言。