switch-case陷阱

本文详细解析了C++中switch-case语句的使用规则,特别是关于case标签、局部变量作用域、初始化跳过以及goto语句的陷阱。通过具体的代码实例,阐述了如何避免常见的编译错误,并解释了switch-case语句与goto语句之间的关系,以及它们如何影响程序的执行流程。
switch( flag ) {
	case 0:
			int var = 1;
			break;
	case 2:
			int var2 = 2;
			break;
	default:
		break;
	}

像上面这样的代码就会报错

error C2360: initialization of 'var' is skipped by 'case' label
error C2361: initialization of 'var' is skipped by 'default' label

加上括号就不会出错了

switch( flag ) {
	case 0:
		{
			int var = 1;
			break;
		}	
	case 2:
		{
			int var2 = 2;
			break;
		}
	default:
		break;
	}


————————————————————————————————————————————————————————————————————————————

先来看看下面这段代码,有问题吗?

  1. void RunStateMachine()  
  2. {  
  3.     switch(m_status)  
  4.     {  
  5.     case TASK_START:  
  6.         int data = FormDataToSend();  
  7.         m_mailbox->Send(data);  
  8.         m_status = TASK_SENT;  
  9.         break;  
  10.     case TASK_SENT:  
  11.         //..  
  12.         break;  
  13.     //..  
  14.     }  
  15. }  

这就是今天写的一部分代码的原型,初看一下并没觉得有什么问题,但是编译器报错:initialization of 'data' is skipped by 'case' label。

switch-case的“穿越”

网上搜了一下,并且看了下标准,终于明白了来龙去脉。在C++中,switch-case中的case实质上只是一个标签(label),就像goto的标签一样。case中的代码并没有构成一个局部作用域,虽然它的缩进给人一种错觉,好像它是一个作用域。也就是说,所有在case里面定义的变量作用域都是switch{...},在后面其他case中依然可以访问到这个变量。而switch本质上相当于goto,因此下面紧跟switch的打印语句永远不会执行到。

  1. switch(selector)  
  2. {  
  3. cout << selector;  
  4. case selector_a:  
  5.     int i = 1;  
  6. case selector_b:  
  7.     // i在此仍然可见  
  8. }  
那这和错误有什么关系呢?C++标准规定:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer. (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)

意思是说,如果一个程序的执行路径从代码中的点A(某个局部变量x还未定义)跳到代码中另一点B(该局部变量x已定义,并且定义的时候有初始化),那么编译器会报错。这样的跳跃可以是由于执行goto语句,或者是switch-case造成的。

这里有两种情况:首先,对于POD对象而言,只有当有初始化式的声明被跳过时才会报错。

  1. switch(selector)  
  2. {  
  3. case selector_a:  
  4.     int i = 1;  // 如果是"int i;",则编译器不报错,只有下面的警告  
  5. case selector_b:  
  6.     cout << i; // warning: 使用未初始化i  
  7. }  
值得注意的是,变量i是否有定义,空间大小这些在编译期就已确定,而初始化是运行时的行为。跳过的是初始化,而非它的定义。

其次,对于有显式定义构造函数的类对象来说,其声明被跳过时会报错。

  1. class Employee  
  2. {  
  3. public:  
  4.     Employee() {..}  
  5.     void f() {..}  
  6. };  
  7.   
  8. ..  
  9. switch(selector)  
  10. {  
  11. case selector_a:  
  12.     Employee e;  // 编译报错!  
  13. case selector_b:  
  14.     e.f();  
  15. }  

当一个POD对象有初始化式或者一个类具有显式构造函数时,表明程序员希望初始化这个对象。如果程序的执行有任何可能会导致该初始化被跳过,程序很可能进入程序员意料之外的某个不正确状态。所以,标准明确禁止了这种行为。

当然了,对于没有初始化过的POD对象,后面的读取可能有问题,也可能没问题,取决于使用前是否进行过赋值。然后这一切都是运行时才知道,编译器无能为力。所以,大多数情况下并不报错。

  1. void f(bool set)  
  2. {  
  3.     goto label;  
  4.     int i;  
  5. label:  
  6.     if (set)  
  7.     {  
  8.         i = 3;  
  9.     }  
  10.     cout << i; // i到底有没有初始化?只有运行时才知道  
  11. }  
  12.   
  13. ..  
  14. f(false);  
好了,回到问题上来。出现上述编译错误的根本原因是对象的作用域“拖的”比较长,跨越了多个case。解决的办法是用大括号{}将每个case中的代码封成一个局部作用域。由于i具有局部作用域,因此也就不存在声明被跳过的问题了。

  1. switch(selector)  
  2. {  
  3. case selector_a:  
  4.     {  
  5.         int i = 1;  
  6.     }  
  7. case selector_b:  
  8.     // i在此不可见  
  9. }  

goto的“穿越”

在翻看标准的时候,发现了另外一个有趣的例子。这个例子是为了说明向前穿越过变量的定义会有问题,这个在上面已经讨论过了。但是注释说,在执行goto ly的时候会调用对象a的析构函数。

  1. void f()  
  2. {  
  3.     // ...  
  4.     goto lx; // 编译出错,原因见上分析  
  5.     // ...  
  6. ly:  
  7.     A a = 1;  
  8.     // ...  
  9. lx:  
  10.     goto ly; // OK, 跳跃到ly意味着a的析构函数被调用  
  11.     A b = a; // OK, 这里属于a的作用域,但是永远不会执行到  
  12. }  
这段程序形成了一个死循环,最先a的析构被调用,然后被构造,被析构,不断轮回。那么,为什么在执行goto ly的时候会调用a的析构函数呢?这个在标准里面也有专门说明:

Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of variables with automatic storage duration that are in scope at the point transferred from but not at the point transferred to.

意思说:当程序执行路径离开一个循环、局部作用域{..}、或者回到一个已初始化对象之前时,相应的析构函数会被调用。

这是可以理解的,上例中对象a的作用域是从A a=1开始,到f的右括号}结束。如果程序执行跳到A a=1之前,显然已经不在a的作用域了。离开作用域自然应该调用a的析构函数,就像中途抛出一个异常或者return返回一样。

小结

1. switch-case中的switch相当于goto, case相当于一个goto标签。

2. 在case中定义变量时必须在周围加{..}以形成局部作用域,否则编译报错。习惯上总是将case中的代码用{..}括起来,比较省事。是有些公司的coding convention之一。

  1. switch(selector)  
  2. {  
  3. case selector_a:  
  4.     {  
  5.         int i;  
  6.     }  
  7. case selector_b:  
  8.     // i在此不可见  
  9. }  

3. 当goto往回穿越过一个对象的初始化时,该对象的析构函数被调用。


1、ETL(Extract-Transform-Load的缩写,即数据抽取、转换、装载的过程),对于企业或行业应用来说,我们经常会遇到各种数据的处理,转换,迁移,所以掌握一个ETL工具的使用,必不可少。Kettle作为ETL工具是非常强大和方便的。Kettle是一款国外开源的ETL工具,纯java编写,可以在Window、Linux、Unix上运行,绿色无需安装,数据抽取高效稳定。Kettle中文名称叫水壶,该项目的主程序员MATT希望把各种数据放到一个壶里,然后以一种指定的格式流出。Kettle这个ETL工具集,它允许你管理来自不同数据库的数据,通过提供一个图形化的用户环境来描述你想做什么,而不是你想怎么做。Kettle中有两种脚本文件,transformation和job,transformation完成针对数据的基础转换,job则完成整个工作流的控制。2、Clickhouse 是俄罗斯的“百度”Yandex公司在2016年开源的,一款针对大数据实时分析的高性能分布式数据库,与之对应的有hadoop生态hive,Vertica和百度出品的palo。这是战斗民族继nginx后,又开源的一款“核武器”。Hadoop 生态体系解决了大数据界的大部分问题,当然其也存在缺点。Hadoop 体系的最大短板在于数据处理时效性。基于 Hadoop 生态的数据处理场景大部分对时效要求不高,按照传统的做法一般是 T + 1 的数据时效。即 Trade + 1,数据产出在交易日 + 1 天。ClickHouse 的产生就是为了解决大数据量处理的时效性。独立于Hadoop生态圈。3、Superset 是一款由 Airbnb 开源的“现代化的企业级 BI(商业智能) Web 应用程序”,其通过创建和分享 dashboard,为数据分析提供了轻量级的数据查询和可视化方案。 Superset 的前端主要用到了 React 和 NVD3/D3,而后端则基于 Python 的 Flask 框架和 Pandas、SQLAlchemy 等依赖库,主要提供了这几方面的功能:01、集成数据查询功能,支持多种数据库,包括 MySQL、PostgresSQL、Oracle、SQL Server、SQLite、SparkSQL 等,并深度支持 Druid。02、通过 NVD3/D3 预定义了多种可视化图表,满足大部分的数据展示功能。如果还有其他需求,也可以自开发更多的图表类型,或者嵌入其他的 JavaScript 图表库(如 HighCharts、ECharts)。03、提供细粒度安全模型,可以在功能层面和数据层面进行访问控制。支持多种鉴权方式(如数据库、OpenID、LDAP、OAuth、REMOTE_USER 等)。 基于Kettle+Clickhouse+Superset构建亿级大数据实时分析平台课程将联合这三大开源工具,实现一个强大的实时分析平台。该系统以热门的互联网电商实际业务应用场景为案例讲解,对电商数据的常见实战指标处理使用kettle等工具进行了详尽讲解,具体指标包括:流量分析、新增用户分析、活跃用户分析订单分析、团购分析。能承载海量数据的实时分析,数据分析涵盖全端(PC、移动、小程序)应用。项目代码也是具有很高的商业价值的,大家可以根据自己的业务进行修改,便可以使用。本课程包含的技术:开发工具为:IDEAKettleClickhouseSupersetBinlogCanalKafkaHbaseHadoopZookeeperFlinkSpringBootSpringCouldPythonAnconaMySQL等 课程亮点:1.与企业对接、真实工业界产品2.强大的ETL工具Kettle全流程讲解实现3.ClickHouse高性能列式存储数据库4.Superset现代化的企业级BI可视化5.数据库实时同步解决方案6.集成Flink实时数据转换解决方案7.主流微服务SpringBoot后端系统8.互联网大数据企业热门技术栈9.支持海量数据的实时分析10.支持全端实时数据分析11.全程代码实操,提供全部代码和资料12.提供答疑和提供企业技术方案咨询 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值