C++跳转语句之Goto对变量定义的影响详解
前言
goto语句也称为无条件转移语句,其基本形式如下:
语句标号由一个有效地标识符和符号";"组成,其中,标识符的命名规则与变量名称相同,即由字母、数字和下划线组成,且第一个字符必须是字母或下划线。执行goto语句后,程序就会跳转到语句标号处,并执行其后的语句。
通常goto语句与if条件语句连用,但是,goto语句在给程序带来灵活性的同时,也会使得使程序结构层次不清,而且不易读,所以要合理运用该语句。
发现问题
我们经常碰到有在goto后面定义变量,linux下编译不通过的问题(报错信息:crossesinitializationof)。其实,只要注意一下就好了,今天问了一下公司前辈之后,也翻了些资料,记录一下,加深记忆,也希望能对一些人有些许帮助。
错误示例代码:
#include<iostream> usingnamespacestd; intmain() { gotoExit; inta=0; Exit: return0; }
报错:
[root@localhostc-c++]#g++goto_study.cpp goto_study.cpp:Infunction'intmain()': goto_study.cpp:31:error:jumptolabel'Exit' goto_study.cpp:29:error:fromhere goto_study.cpp:30:error:crossesinitializationof'inta'
正确写法
也不能说是正确的写法,只能说是编译OK的写法。
直接上代码:
写法一:
改变域,变成局部变量:
intmain() { gotoExit; { inta=0; } Exit: return0; }
写法二
神奇的写法:
intmain() { gotoExit; inta; a=1; Exit: cout<<"a="<<a<<endl; return0; }
关键是还可以访问!结果:
[root@localhostc-c++]#g++goto_study.cpp [root@localhostc-c++]#./a.out a=1259648
研究
神奇的写法
看到两个可以编译通过的写法之后,最纳闷的是写法二为毛可以编译通过,而且还能使用???
C++规定
参考[1][2]中提到了C++标准中的规定:>Itispossibletotransferintoablock,butnotinawaythatbypassesdeclarationswithinitialization.Aprogramthatjumpsfromapointwherealocalvariablewithautomaticstoragedurationisnotinscopetoapointwhereitisinscopeisill-formedunlessthevariablehasPODtype(3.9)andisdeclaredwithoutaninitializer.
意思是说:如果一个程序的执行路径从代码中的点A(某个局部变量x还未定义)跳到代码中另一点B(该局部变量x已定义,并且定义的时候有初始化),那么编译器会报错。这样的跳跃可以是由于执行goto语句,或者是switch-case造成的。所以,在写法二中a是int类型,是一个POD类型,并且没有初始化,所以编译通过。但是,很明显:如果去使用这个变量a的时候,结果是未知的,就像前辈说的,没有意义,还不如不支持!那如果只在局部使用,完全可以用花括号括起来!网上也有人说到,C++规范虽然没有明确说明这样是错误的,但是变量的域的规定其实是隐性说这种做法是不可取的,见参考[4]。
隐性说明
Gotocan'tskipoverdefinitionsofvariables,becausethosevariableswouldnotexistafterthejump,sincelifetimeofvariablestartsatthepointofdefinition.Thespecificationdoesnotseemtoexplicitlymentiongotomustnotdothat,butitisimpliedinwhatissaidaboutvariablelifetime.
-fpermissive标记
参考[4]中提到,g++编译器默认是检查的,自己可以设置编译器的这个标记变成警告,未实践!!!
查了下资料-fpermissive标记的作用是:把代码的语法错误作为警告,并继续编译进程,所以就安全起见,这个角度就不要想了,还是老老实实码砖!
POD类型
参考[3],按照上面C++规定的说法,只要是POD类型,并且没有初始化都是可以编译通过的。
看一段代码:
#include<iostream> usingnamespacestd; classA{ public: //注意:和B不同的是有构造和析构函数,所以编译报错 A(){} ~A(){} voidtestA(){ cout<<"A::test."<<endl; } }; classB{ public: voidtestB(){ cout<<"B::test."<<endl; } }; intmain() { gotoExit; //inta=1;//windowsok.linuxfailed! //AclassA;//failed: BclassB;//success: classB.testB(); Exit: classB.testB(); return0; }
结果:
[root@localhostc-c++]#g++goto_study.cpp [root@localhostc-c++]#./a.out a=1259648 B::test.
小结:
1、以上代码在windows和linux下均编译通过和执行;
2、AclassA一句在windows和linux均编译不通过!因为A有构造和析构函数,不满足条件;
3、至于inta=1;这样的写法在windows(msvc)下面能够通过就与C++规范不符了,求解释!!!
以下是POD类型(还是看英文吧):
1、int,char,wchar_t,bool,float,double是POD类型,这些类型的long/shortandsigned/unsigned版本也是;
2、指针(包括函数指针和成员指针)都是POD类型;
3、enums枚举类型;
4、POD的const和普通变量也都是;
5、POD类型的class,struct和union也是。但要求所有的成员是public,并且没有基类,没有构造、析构函数和虚函数。静态成员在这些规则下也是。
总结
1、最好不要用goto;
2、goto后面不要跳过定义和初始化的变量,如果是POD类型可以先申明再定义,是不会编译报错的。但是不建议这么使用,可以看到如果执行语句跳过了赋值语句,那么变量的值是未知的,存在危险性;
3、goto后面如果是局部的变量,可以用花括号括起来构成一个局部域,就安全了。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
参考
[1]Gettingabunchofcrossesinitializationerror
[2]>switchcase、goto对变量定义的影响
[3]>“PODtype”inC++
[4]>Statementgotocannotcrosspointerdefinition?
[5]>error:jumptolabel‘foo'crossesinitializationof‘bar'