[C++ switch 作用域] 跳过初始化之坑

0.Introduction


介绍switch语句的作用域问题,本文参考自浅析C/C++中的switch/case陷阱

1.所有可能遇到的情况:

1.情况一 跳过声明

以下展示一段代码(g++):
附注:该代码缘起自知乎-请教switch内部的变量定义问题?

//test.cpp
 #include <iostream>
using namespace std;
int main()
{
    int choice;
    cin>>choice;
    switch(choice)
    {
        case 1:
            int a;
            break;
        case 2:
            cout<<a<<endl;
            break;
        default:
            cout<<"default"<<endl;
    }
}

在g++编译环境下并未产生任何报错。
运行结果如下:

$ ./test
2    //输入
0

这里应当有所疑虑,假设choice==2,则直接跳过了case 1里对于a的声明,那么case 2应该是输出不了a的。
然而,

在编译的时候,系统会计算当前有多少个局部变量,然后统一开辟空间。所以,其实所有变量的空间都是一进函数就分配好的了,区别只是有没有在那块东西上运行构造函数

2.情况二 跳过初始化

以下展示一段代码(g++):

#include <iostream>
using namespace std;
int main()
{
    int choice;
    cin>>choice;
    switch(choice)
    {
        case 1:
            int a = 1;
            cout<<a<<endl;
            break;
        case 2:
            cout<<a<<endl;
            break;
        default:
            cout<<"default"<<endl;
    }
}

编译错误:

$ g++ -o test test.cpp
test.cpp: 在函数‘int main()’中:
test.cpp:13:8: 错误:跳转至 case 标号 [-fpermissive]
  case 2:
        ^
test.cpp:10:8: 附注:跳过了‘int a’的初始化
    int a = 1;
        ^
test.cpp:16:3: 错误:跳转至 case 标号 [-fpermissive]
  default:
  ^
test.cpp:10:8: 附注:跳过了‘int a’的初始化
    int a = 1;

而这样的编译错误,在C++参考手册-switch也有提及:

因为控制的转移不允许进入变量的作用域,若在 语句 中遇到声明语句,则它必须拥有在其自身的复合语句内的作用域

以下为参考手册的代码:

switch(1) {
    case 1: int x = 0; // 初始化
            std::cout << x << '\n';
            break;
    default: // 编译错误:跳到 default :会进入 'x' 的作用域而不初始化它
            std::cout << "default\n";
            break;
}

其意涵指,
当switch的case 1被跳过时,default 会进入int i该变量的作用域但无法对其进行初始化,有可能改变程序员的意图,比如,程序员打算在其后的case使用到的是已经初始化了的i。
这是语言设计的一种哲学。支持跳过声明却不支持跳过初始化,很大程度上理解并方便了程序员的意图

当然,C++ primer 也有所提及。

如果一定需要在某个case初始化某个变量值,可以加上花括号

#include <iostream>
using namespace std;
int main()
{
    int choice;
    cin>>choice;
    switch(choice)
    {
        case 1:
    {
            int a = 1;
            cout<<a<<endl;
            break;
    }
        case 2:
            cout<<a<<endl;
            break;
        default:
            cout<<"default"<<endl;
    }
}

3.情况三 初始化在switch最末,没有跳过的可能性,编译通过

#include <iostream>
using namespace std;
int main()
{
    int choice;
    cin>>choice;
    switch(choice)
    {
        case 1:
            break;
        case 2:
            break;
        default:
            int a = 1;
            cout<<a<<endl;
    }
}

4.情况四 初始化(其实是赋值)和声明语句分开,编译通过

#include <iostream>
using namespace std;
int main()
{
    int choice;
    cin>>choice;
    switch(choice)
    {
        case 1:
            int a;
            a = 1;
            break;
        case 2:
            cout<<a<<endl;
            break;
    }
}

赋值和声明分开就表示程序员的意图仅仅只是希望
i的“初值”在case 1实现,而非整个switch的作用域
此时,如果choice为2时,输出为:

$ ./test
2
0

2.小结


  • C++不允许在除最末case/default之外进行初始化变量
  • C++允许看似“跳过声明”或看似“初始化”的赋值这两种操作,因为局部变量在编译前一早就已经分配好空间
  • 若一定要在相应case进行初始化,须使用花括号
  • 整个switch语句即一个作用域

发表评论

电子邮件地址不会被公开。 必填项已用*标注