实用经验 41 goto真的一无是处吗?

实用经验 41 goto真的一无是处吗?

goto是一种无条件跳转语句,其有点儿像汇编语言中的jmp语句。使用goto语句可以实现代码空间中任意位置的跳动。Goto语句的语法如下:

goto 语句标签;

使用goto语句,你必须在希望跳转的语句前面添加语句标签。语句标签就是标示符后面添加一个冒号。包含这些标签的goto语句可出现在同一个函数中的任何位置。我们看goto语句的一个使用例子。

int iVal = 0;

scanf("please input a inter number = %d", &iVal);

if (0 <= iVal)

{

goto END;

}

// 查找从0--iVal中的奇数

...........

END:

return ;

goto语句是一种危险的语句。我想教授C/C++语言的每个老师,都可能向你讲述过这么一种观点:尽量不要使用goto语句,他会是你的程序变得不友好。的确,goto语句确实存在这个缺陷。但这并不能说goto语句就一无是处了。

事实也是这样。goto语句可能是目前程序设计语言中最受争议的机制之一。目前,主流的高级语言基本上都支持goto机制(C、C++、Java、VB都支持goto机制)。关于goto语句的使用存在两种观点:第一种、“goto是有害的”反对观点。第二种、支持goto的观点。

“goto是有害的”的反对观点,最早由Dijkstra在《Go To Statement Considered Harmful》论文中提出。该文指出:一个程序的易读性和易理解性同其中所包含的无条件转移控制的个数成反比关系,也就是说,转向语句的个数越多,程序就越难读、难懂。“GOTO是有害的”观点启发了结构化程序设计的思想。

反对goto的论点

含有goto 的代码很难安排好格式。使用goto 也会破坏编译器的优化特性。使用goto 会使运行速度更慢,而且代码也更大。此论点由《计算机程序设计的艺术》作者高德纳提出。使用goto语句编写的代码较之细心编写的程序,一般会存在难以维护的缺点。

下面这段代码是《C和指针》中使用goto语句执行数组元素的交换排序的代码实现:

// 数组元素交换排序代码实现。

i = 0;

outer_next:

if (i >= NUM_ELEMENTS-1)

{

goto outer_end;

}

j = i+1;

inner_next:

if (i >= NUM_ELEMENTS)

{

goto inner_end;

}

if (value[i] <= value[j])

{

goto no_swap;

}

temp = value[i];

value[i] = value[j];

value[j] = temp;

no_swap:

j += 1;

goto inner_next;

inner_next:

i += 1;

goto outer_next;

outer_end:

;

可以看出虽然这仅仅是一个很小的程序,然而你必须要花费相当长的时间才能搞清楚他的结构。这也许会让你颇为头疼。因为这只是简短的一小段程序,搞清楚它的结构就这么麻烦。如果你面对一个大型工程,我想你肯定会这么想:太复杂了,我投降了。

继续看下面这段基于结构化程序设计思想的代码实现:

for (i = 0; i < NUM_ELEMENTS-1; i += 1)

{

for ( j = i+1; j < NUM_ELEMENTS; j += 1)

{

if ( value[i] > value[j])

{

temp = value[i];

value[j] = value[i];

value[i] = temp;

}

}

}

看了这段代码,你是不是感觉好多了。要搞清楚这段的代码的结构已经变成一件很简单的事情了。对比这两段代码,我们可以得出这样的一个结论:结构化程序设计确实比采用goto机制更容易让人接受。

支持goto的观点,认为goto语句存在各种各样的问题,这是没错的。但是这并不否认goto在某些应用环境下的突出表现。

支持goto的观点

如果使用位置恰当, goto可以减少重复的代码。goto在分配资源、使用资源后再释放资源的子程序里非常有用。在某些情况下,使用goto 会让代码的运行速度更快,体积更小。大部分论断都反对随意使用goto。

为了说明goto语句支持人员的观点。这儿介绍两种适合goto语句使用的情况。有了这两个例子,你也许对goto语句有更为深刻的理解了。

第一种情况:跳出多层嵌套的循环。这种情况下,由于break语句只影响包围它的最内层循环,如欲从深层循环跳出只有一种方法,那就是使用goto语句了。

while(condition1)

{

while(condition2)

{

if (some disaster)

{

goto quit;

}

}

}

quit:

;

如果你想在这种情况下避免使用goto语句,有两种解决方案。但这两种方案都不如使用goto语句来的直接,可读性也较差。第一个方案在欲退出所有循环是设置一个状态标志,但是这个状态标志在每次循环时都必须进行测试。代码如下:

enum {EXIT, OK} status;

...

status = OK;

while (status == OK && contidition1)

{

while(condition2)

{

if (some disaster)

{

status = EXIT;

break;

}

}

}

第二个方案是把所有的循环都放到一个单独的函数中,当灾难临到最内层循环的循环时,你可以使用return语句离开这个函数。

第二种情况:错误处理以及释放资源。这种情况在错误出现时,统一跳到错误处理及释放资源部分。方便简单,更符合人的思维方式。

// 打开所有文件列表

void PurgeFiles(Error_Code& errorState)

{

...

while( fileIndex < numFilesToPurge)

{

fileIndex += 1;

if (!FindFile(fileList(fileInde),fileToPurge))

{

errorState = FileStatus_FileFindeError;

goto END_PROC;

}

if(!OpenFile( fileToPurge )

{

errorState = FileStatus_FileOpenError

goto END_PROC;

}

....

}

END_PROC:

DeletePurgeFileList(fileList, numFilesToPurge);

}

这种处理方式,将所有的异常处理以及资源释放过程,放到END_PROC处理中。确实处理的比较好可以更好的避免了资源泄露和异常处理的错误问题。也方便定位。当你判断这段代码存在资源泄露问题时,你可以立刻锁定问题的所在(即END_PROC子流程)。如果不采用这种形式,每种错误单独处理。这样很容易造成内存泄露的问题。

我们看goto的替代版本,这个版本通过if语句消除goto代码。看完这段代码,我们你会有这两个感觉:

(1)if嵌套的层次实在太深了;

(2)错误处理的代码和引发错误代码问题之间的距离实在太远了。

void PurgeFiles(Error_Code& errorState)

{

while ( (fileIndex < numFilesToPurge) && (errorState == FileStatus_Success) )

{

fileIndex = fileIndex + 1

if (FindFile( fileList( fileIndex ), fileToPurge ))

{

if (OpenFile( fileToPurge )

{

if (OverwriteFile( fileToPurge ))

{

if (!Erase( fileToPurge )

{

errorState = FileStatus_FileEraseError

}

}

else

{

errorState = FileStatus_FileOverwriteError

}

}

else

{

errorState = FileStatus_FileOpenError

}

}

else

{

errorState = FileStatus_FileFindError

}

}

DeletePurgeFileList(fileList, numFilesToPurge);

}

请谨记

goto语句把程序的执行流从一条语句转移到令一条语句,在一般情况下,我们应该避免使用。 在某些环境下,使用goto语句往往可给我们带来程序简洁。提高可读性的优点

相关推荐