在C中惯用的任意块是什么?


15

块是要执行的语句列表。在C中出现块的示例是在while语句和if语句之后

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C还允许将一个块嵌套在一个块中。我可以用它重用变量名,假设我真的很喜欢'x'

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

将数字5打印十次。我想我会看到需要保持变量名数量少的情况。也许在宏观扩张中。但是,我看不出有任何硬性理由需要此功能。任何人都可以通过在使用该功能的地方提供一些惯用法来帮助我理解此功能的用法。


1
老实说,我只是想了解C语法,并且很好奇。
Jonathan Gallagher 2013年

C的精神是信任程序员。程序员可以利用自己的力量去做一些伟大的事情……或者做一件可怕的事情。就个人而言,我不喜欢重载的变量名,但另一个程序员可能会喜欢。归根结底,如果我们用最少的错误来完成我们应该做的事情……我们为什么要争论?
摆弄位

1
这就是我从C语言中获得的感觉。我全都支持支持不同编码风格的语法(只要语言的语义还可以)。仅仅是……我看到了,我的立即反应是,我可以应用源到源转换,以新鲜的名称重命名块中的所有变量,然后将其完全展平。每当我认为我可以摆脱某些东西时,我都会认为我错过了一些东西。
乔纳森·加拉格尔

有趣的时机,作为一个非C程序员,我今天在C代码中偶然发现了这种语法,并对它的用途感到好奇。我很高兴你问。
布兰登

Answers:


8

这个主意不是要使变量名的数量保持在低水平或以其他方式鼓励名称的重用,而是要限制变量的范围。如果你有:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

那么的范围y仅限于该块,这意味着您可以在块之前或之后忘记它。您会看到这最常与循环和条件结合使用。您还经常在类似C的语言(例如C ++)中看到它,其中变量超出范围会导致破坏。


我认为,条件,循环和函数体自然会发生阻塞。我很好奇的是,在C语言中,我可以在任何地方放置一个块。关于C ++的第二点评论很有趣-离开范围会导致破坏。这是否仅是指垃圾回收,还是下面的另一种用法:我是否可以采用具有不同“节”的函数体,并使用块通过触发变量空间的破坏来控制内存占用量?
乔纳森·加拉格尔

1
据我所知,C将为函数中的所有变量预分配所有内存。在函数中部分退出作用域将不会对C产生任何性能上的好处。类似地,只有在某些情况下使变量进入作用域“仅在某些情况下”不会更改内存占用量
Gankro 2013年

@Gankro如果存在多个互斥嵌套作用域,则可能会产生影响。编译器可以将唯一的预分配内存块重新用于这些作用域中的变量。当然,没有想到它的原因是,如果单个任意作用域已经是一个符号,则您可能需要将其提取到函数中,那么两个或多个任意作用域绝对是您需要重构的一个很好的指示。尽管如此,它在某些情况下仍会作为理智的解决方案而弹出,例如不时切换机箱。
tne

16

因为在过去的C语言中,新变量只能在新块中声明。

这样,程序员可以在函数中间引入新变量,而不会泄漏该函数并最大程度地减少堆栈使用。

对于当今的优化器,它是无用的,并且是您需要考虑以其自身功能提取块的标志。

在switch语句中,将案例封装在自己的块中以避免重复声明很有用。

在C ++中,这对于RAII锁定防护非常有用,例如,确保在执行超出范围时析构函数运行释放锁定,并且仍在关键部分之外执行其他操作。


2
+1为RAII锁卫。话虽这么说,这个相同的概念对例程部分中的大型堆栈缓冲区重新分配是否也有用?我从未做过,但是听起来似乎某些嵌入式代码中肯定会发生某些事情……
J Trana 2013年

2

我不会将其视为“任意”块。这不是一个足够供开发人员使用的功能,但是C使用块的方式允许在具有相同语义的多个地方使用相同的块构造。块(在C中)是一个新的作用域,保留该变量的变量将被消除。无论如何使用该块,这都是统一的。

在其他语言中,情况并非如此。这样的好处是可以减少您所显示的滥用,但是缺点是,块的行为取决于它们所处的上下文。

我很少见过在C或C ++中使用独立块的情况-经常是在存在表示连接的大型结构或对象或您要强行破坏的对象时。通常,这表明您的函数执行了太多操作和/或时间过长。


1
不幸的是,我经常看到独立的块-在几乎所有情况下,由于函数作用很大和/或时间太长。
mattnz

我也经常在C ++中查看和编写独立的块,但仅仅是为了破坏销子和其他共享资源周围的包装器。
J特拉纳

2

您必须意识到,现在看来显而易见的编程原理并不总是如此。C最佳实践在很大程度上取决于示例的年代。首次引入C时,将代码分解为小函数被认为效率太低。丹尼斯·里奇(Dennis Ritchie)基本上是在撒谎,并说函数调用在C中确实非常有效(当时还不是),这使人们开始更多地使用它们,尽管C程序员从某种程度上从来没有真正完全摆脱过早的优化文化。

一个良好的编程习惯即使在今天,你变量的范围限制尽可能小。如今,我们通常通过创建一个新函数来做到这一点,但是如果函数被认为是昂贵的,则引入一个新的块是一种在不增加函数调用开销的情况下限制范围的逻辑方法。

但是,我20多年前就开始用C进行编程,那时候您不得不在作用域的顶部声明所有变量,而我也没有想起像以前那样被认为是好的样式的变量阴影。像switch语句一样,在两个块中一个接一个地重新声明,是的,但是没有阴影。 也许,如果变量已被使用,并且您正在调用的API的特定名称非常惯用,例如destsrc中的strcpy


1

任意块可用于引入仅在特殊情况下使用的中间变量。

这是科学计算中的一种常见模式,其中数字过程通常:

  1. 依赖很多参数或中间数量;
  2. 必须处理很多特殊情况。

由于第二点,引入有限范围的临时变量很有用,这可以通过使用任意块或通过引入辅助函数来实现。

虽然引入辅助功能可能看起来像是一厢情愿盲目遵循最佳实践,在这种特殊情况下这样做实际上没有什么好处

因为有很多参数和中间量,所以我们想引入一种结构将它们传递给辅助功能。

但是,由于我们希望遵循自己的做法,因此我们将不会仅介绍一个辅助功能,而是介绍几个辅助功能。因此,我们引入了为每个函数传递参数的即席结构,这引入了很多代码开销来回移动参数,或者引入了一个将它们视为所有工作表结构的规则,其中包含了所有变量,但是看起来没有一致性的抓斗,在任何时候,只有一半的参数具有有趣的意义。

因此,这些辅助结构通常很麻烦,使用它们意味着在代码膨胀之间进行选择,或者引入范围过于广泛并削弱程序含义的抽象,而不是加强它

引入辅助功能可以通过引入更好的测试粒度来简化程序的单元测试,但是可以结合使用单元测试(而不是低级过程)和回归测试(以numdiff的形式对过程进行数字化跟踪)来达到同样的效果。


0

通常,以这种方式重用变量名会对以后的代码阅读者造成太多混乱。最好简单地将内部变量命名为其他名称。实际上,C#语言明确禁止以这种方式使用变量。

我可以看到在宏扩展中使用嵌套块对于防止变量命名冲突将很有用。


0

它用于确定通常在该级别没有作用域的事物。这是极为罕见的,一般而言,重构是一个更好的选择,但过去我在switch语句中遇到过一两次:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

当您考虑维护时,只要将所有实现都排成一行,就应该在重构这些结构时保持平衡。将它们放在一起而不是函数名列表会更容易。

就我而言,如果我没记错的话,大多数情况是同一代码的细微变化-除了其中一个与另一个完全相同之外,只是需要额外的预处理。开关最终是代码采用的最简单的形式,并且额外的范围允许使用多余的局部变量,而不必担心其他情况(例如,变量名意外地与开关前定义的变量重叠,但是稍后使用)。

请注意,switch语句只是我遇到的它的使用。我确信它也可以在其他地方应用,但是我不记得看到它们以这种方式有效地使用过。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.