【发布时间】: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++、C#)中匿名 { } 块的用途是什么
例子-
void function()
{
{
int i = 0;
i = i + 1;
}
{
int k = 0;
k = k + 1;
}
}
编辑 - 感谢所有出色的答案!
【问题讨论】:
它将变量的范围限制在 { } 内的块中。
【讨论】:
括号指定一个范围 - 括号内声明的任何内容在括号外都是不可见的。
此外,在 C++ 中,分配在堆栈上的对象(例如,不使用“new”)将在超出范围时被破坏。
在某些情况下,它也可以作为一种突出显示作者认为值得查看源代码的人关注的特定功能部分的方法。这是否是一个好的用途是有争议的,但我已经看到它做到了。
【讨论】:
它们通常用于RAII 用途,这意味着当对象超出范围时将释放给定资源。例如:
void function()
{
{
std::ofstream out( "file.txt" );
out << "some data\n";
}
// You can be sure that "out" is closed here
}
【讨论】:
通过创建新范围,它们可用于在 switch 语句中定义局部变量。
例如
switch (i)
{
case 0 :
int j = 0; // error!
break;
对比
switch (i)
{
case 0 :
{
int j = 0; // ok!
}
break;
【讨论】:
{ ... }开辟了一个新的范围
在 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);
// ...
}
}
它将事物分开,以便人们可以轻松找出不同的构建块。您可以使用具有相同名称的变量,就像上面的代码一样,因为它们在其范围之外是不可见的,因此它们不会相互冲突。
【讨论】:
另一个常见用途是使用 OpenGL 的 glPushMatrix() 和 glPopMatrix() 函数来创建与矩阵堆栈相关的逻辑块:
glPushMatrix();
{
glTranslate(...);
glPushMatrix();
{
glRotate(...);
// draw some stuff
}
glPopMatrix();
// maybe draw some more stuff
}
glPopMatrix();
【讨论】:
using 构造执行相同的操作,这样可以避免忘记在最后弹出矩阵,或者在引发异常时使堆栈混乱。
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
}
【讨论】:
当然是范围界定。 (那匹马被打死了吗?)
但如果您查看语言定义,您会看到如下模式:
它简化了复合语句只是几种可能的语句之一的语言语法。
复合语句:{ 语句列表opt }
语句列表:
声明:
【讨论】:
if (...) {...} else {...} 或for(...) {...} 也在使用匿名块。事实上,这个问题的措辞似乎是基于这个前提。
你正在做两件事。
【讨论】:
它们经常用于范围变量,因此变量对于由大括号定义的任意块是局部的。在您的示例中,变量 i 和 k 在它们的大括号之外无法访问,因此不能以任何偷偷摸摸的方式修改它们,并且这些变量名称可以在代码的其他地方重用。像这样使用大括号创建局部范围的另一个好处是,在具有垃圾收集的语言中,垃圾收集器知道清理范围外的变量是安全的。这在 C/C++ 中不可用,但我相信它应该在 C# 中。
一种简单的思考方式是,大括号定义了一段原子代码,有点像命名空间、函数或方法,但不必实际创建命名空间、函数或方法。
【讨论】:
据我了解,它们只是用于范围界定。它们允许您在父/兄弟作用域中重用变量名,这有时会很有用。
编辑:这个问题实际上已经在another Stack Overflow question 上得到了回答。希望对您有所帮助。
【讨论】:
正如之前的海报所提到的,它将变量的使用限制在声明它的范围内。
在 C# 和 Java 等垃圾收集语言中,它还允许垃圾收集器回收范围内使用的任何变量使用的内存(尽管将变量设置为 null 会产生相同的效果)。
{
int[] myArray = new int[1000];
... // Do some work
}
// The garbage collector can now reclaim the memory used by myArray
【讨论】:
这是关于范围的,它是指程序的一个部分中的变量和方法对该程序的另一部分的可见性,考虑这个例子:
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
【讨论】:
如果您仅限于 ANSI C,那么它们可用于声明更接近您使用它们的位置的变量:
int main() {
/* Blah blah blah. */
{
int i;
for (i = 0; i < 10; ++i) {
}
}
}
但对于现代 C 编译器来说不是必需的。
【讨论】:
一个有用的用例 ihmo 是在 C++ 中定义临界区。 例如:
int MyClass::foo()
{
// stuff uncritical for multithreading
...
{
someKindOfScopeLock lock(&mutexForThisCriticalResource);
// stuff critical for multithreading!
}
// stuff uncritical for multithreading
...
}
使用匿名作用域无需显式调用互斥锁或信号量的锁定/解锁。
【讨论】:
我将它用于需要临时变量的代码块。
【讨论】:
要提一提的是,作用域是编译器控制的现象。即使变量超出范围(编译器将调用任何析构函数;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 都在匿名块之前。复合语句,根据条件执行一个块或另一个块。
【讨论】: