【问题标题】:What is the purpose of anonymous { } blocks in C style languages?C 风格语言中匿名 { } 块的目的是什么?
【发布时间】:2010-10-04 17:47:18
【问题描述】:

C 风格语言(C、C++、C#)中匿名 { } 块的用途是什么

例子-



void function()
{

  {
    int i = 0;
    i = i + 1;
  }

  {
    int k = 0;
    k = k + 1;
  }

}

编辑 - 感谢所有出色的答案!

【问题讨论】:

  • 为了完整起见,您也可以在 Java 中执行此操作。

标签: c# c++ c


【解决方案1】:

它将变量的范围限制在 { } 内的块中。

【讨论】:

    【解决方案2】:

    括号指定一个范围 - 括号内声明的任何内容在括号外都是不可见的。

    此外,在 C++ 中,分配在堆栈上的对象(例如,不使用“new”)将在超出范围时被破坏。

    在某些情况下,它也可以作为一种突出显示作者认为值得查看源代码的人关注的特定功能部分的方法。这是否是一个好的用途是有争议的,但我已经看到它做到了。

    【讨论】:

      【解决方案3】:

      它们通常用于RAII 用途,这意味着当对象超出范围时将释放给定资源。例如:

      void function()
      {
          {
              std::ofstream out( "file.txt" );
              out << "some data\n";
          }
          // You can be sure that "out" is closed here
      }
      

      【讨论】:

        【解决方案4】:

        通过创建新范围,它们可用于在 switch 语句中定义局部变量。

        例如

        switch (i)
        {
            case 0 :
                int j = 0;   // error!
                break;
        

        对比

        switch (i)
        {
            case 0 :
            {
                int j = 0;   // ok!
            }
            break;
        

        【讨论】:

        • @Ferrucio:不仅在 switch 语句中。在 C 中,变量应该在任何指令之前定义在范围内。因此,如果您有一个大型函数并且需要一个临时变量进行计算,您可以在函数的任何位置打开一个新范围并定义您的临时变量。
        • @Valentin - 对于 K&R C 和 C89 来说确实如此,但我认为 C99 允许您根据需要声明和初始化变量(如 C++),因此您不需要仅为那。但是,您仍然需要在 switch 语句中创建范围,否则编译器可能会抱怨变量的初始化被一种或多种情况跳过。
        【解决方案5】:

        { ... } 开辟了一个新的范围

        在 C++ 中,您可以像这样使用它们:

        void function() {
            // ...
            {
                // lock some mutex.
                mutex_locker lock(m_mutex);
                // ...
            }
            // ...
        }
        

        一旦控制离开块,互斥锁就被破坏了。在它的析构函数中,它会自动解锁它所连接的互斥锁。这是非常常见的,称为 RAII(资源获取是初始化)和 SBRM(范围绑定资源管理)。另一个常见的应用是分配内存,然后在析构函数中再次释放该内存。

        另一个目的是做几件类似的事情:

        void function() {
            // set up timer A
            {
                int config = get_config(TIMER_A);
                // ... 
            } 
        
            // set up timer B
            {
                int config = get_config(TIMER_B);
                // ...
            } 
        }
        

        它将事物分开,以便人们可以轻松找出不同的构建块。您可以使用具有相同名称的变量,就像上面的代码一样,因为它们在其范围之外是不可见的,因此它们不会相互冲突。

        【讨论】:

          【解决方案6】:

          另一个常见用途是使用 OpenGL 的 glPushMatrix()glPopMatrix() 函数来创建与矩阵堆栈相关的逻辑块:

          glPushMatrix();
          {
              glTranslate(...);
              glPushMatrix();
              {
                  glRotate(...);
                  // draw some stuff
              }
              glPopMatrix();
              // maybe draw some more stuff
          }
          glPopMatrix();
          

          【讨论】:

          • 这对于 C/C++ 来说似乎是个好主意。在 C# 中,您可以通过 using 构造执行相同的操作,这样可以避免忘记在最后弹出矩阵,或者在引发异常时使堆栈混乱。
          【解决方案7】:
          class ExpensiveObject {
          public:
              ExpensiveObject() {
                  // acquire a resource
              }
              ~ExpensiveObject() {
                  // release the resource
              }
          }
          
          int main() {
              // some initial processing
              {
                  ExpensiveObject obj;
                  // do some expensive stuff with the obj
              } // don't worry, the variable's scope ended, so the destructor was called, and the resources were released
              // some final processing
          }
          

          【讨论】:

            【解决方案8】:

            当然是范围界定。 (那匹马被打死了吗?)

            但如果您查看语言定义,您会看到如下模式:

            • if ( 表达式 ) 语句
            • if ( 表达式 )   语句   else   声明
            • switch ( 表达式 ) 语句
            • while ( 表达式 ) 语句
            •   statement   while ( expression ) ;

            它简化了复合语句只是几种可能的语句之一的语言语法。


            复合语句{ 语句列表opt }

            语句列表

            • 声明
            • 语句列表 语句

            声明

            • 标签声明
            • 表达式语句
            • 复合语句
            • 选择声明
            • 迭代语句
            • 跳转语句
            • 声明声明
            • 尝试阻止

            【讨论】:

            • 是的,人们经常忘记if (...) {...} else {...}for(...) {...} 也在使用匿名块。事实上,这个问题的措辞似乎是基于这个前提。
            【解决方案9】:

            你正在做两件事。

            1. 您正在对该块中的变量施加范围限制。
            2. 您正在允许同级代码块使用相同的变量名。

            【讨论】:

              【解决方案10】:

              它们经常用于范围变量,因此变量对于由大括号定义的任意块是局部的。在您的示例中,变量 i 和 k 在它们的大括号之外无法访问,因此不能以任何偷偷摸摸的方式修改它们,并且这些变量名称可以在代码的其他地方重用。像这样使用大括号创建局部范围的另一个好处是,在具有垃圾收集的语言中,垃圾收集器知道清理范围外的变量是安全的。这在 C/C++ 中不可用,但我相信它应该在 C# 中。

              一种简单的思考方式是,大括号定义了一段原子代码,有点像命名空间、函数或方法,但不必实际创建命名空间、函数或方法。

              【讨论】:

                【解决方案11】:

                据我了解,它们只是用于范围界定。它们允许您在父/兄弟作用域中重用变量名,这有时会很有用。

                编辑:这个问题实际上已经在another Stack Overflow question 上得到了回答。希望对您有所帮助。

                【讨论】:

                  【解决方案12】:

                  正如之前的海报所提到的,它将变量的使用限制在声明它的范围内。

                  在 C# 和 Java 等垃圾收集语言中,它还允许垃圾收集器回收范围内使用的任何变量使用的内存(尽管将变量设置为 null 会产生相同的效果)。

                  {
                      int[] myArray = new int[1000];
                      ... // Do some work
                  }
                  // The garbage collector can now reclaim the memory used by myArray
                  

                  【讨论】:

                  • 实际上,根据 GC 实现,JIT 应该已经知道该变量没有更多的读取,因此它已经有资格被收集。
                  【解决方案13】:

                  这是关于范围的,它是指程序的一个部分中的变量和方法对该程序的另一部分的可见性,考虑这个例子:

                  int a=25;
                  int b=30;
                  { //at this point, a=25, b=30
                       a*=2; //a=50, b=30
                       b /= 2; //a=50,b=15
                       int a = b*b; //a=225,b=15  <--- this new a it's
                                    //                 declared on the inner scope
                  }
                  //a = 50, b = 15
                  

                  【讨论】:

                    【解决方案14】:

                    如果您仅限于 ANSI C,那么它们可用于声明更接近您使用它们的位置的变量:

                    int main() {
                        /* Blah blah blah. */
                        {
                            int i;
                            for (i = 0; i < 10; ++i) {
                            }
                        }
                    }
                    

                    但对于现代 C 编译器来说不是必需的。

                    【讨论】:

                    • 是的,有时它只是视觉上的东西。同样值得注意的是,对于那些“只接受单个语句”的语法结构,大括号内的任何内容都被视为“语句”。
                    • @Valentin:是的,在 C++ 中,您可以在任何地方声明变量,而不仅仅是在块的顶部。
                    【解决方案15】:

                    一个有用的用例 ihmo 是在 C++ 中定义临界区。 例如:

                    int MyClass::foo()
                    {    
                       // stuff uncritical for multithreading
                       ...
                       {
                          someKindOfScopeLock lock(&mutexForThisCriticalResource);
                          // stuff critical for multithreading!
                       }
                       // stuff uncritical for multithreading
                       ...    
                    }
                    

                    使用匿名作用域无需显式调用互斥锁或信号量的锁定/解锁。

                    【讨论】:

                      【解决方案16】:

                      我将它用于需要临时变量的代码块。

                      【讨论】:

                        【解决方案17】:

                        要提一提的是,作用域是编译器控制的现象。即使变量超出范围(编译器将调用任何析构函数;POD 类型会立即优化到代码中),它们仍留在堆栈中,并且在父范围中定义的任何新变量都不会在 gcc 或 clang 上覆盖它们(即使使用 -Ofast 编译)。除了通过地址访问它们是未定义的行为,因为变量在概念上已经超出了编译器级别的范围 - 编译器将阻止您通过它们的标识符访问它们。

                        #include <stdio.h>
                        int main(void) {
                          int* c;
                          {
                            int b = 5; 
                            c=&b;
                          }
                          printf("%d", *c); //undefined behaviour but prints 5 for reasons stated above
                          printf("%d", b); //compiler error, out of scope
                          return 0;
                        }
                        

                        另外,for、if 和 else 都在匿名块之前。复合语句,根据条件执行一个块或另一个块。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2012-08-16
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-06-12
                          • 1970-01-01
                          • 1970-01-01
                          • 2014-09-09
                          • 1970-01-01
                          相关资源
                          最近更新 更多