搜索
首页后端开发php教程是否应该使用goto语句?

是否应该使用goto语句?

Jun 25, 2017 pm 01:25 PM
goto使用语句

是否应该使用goto语句


       goto语句也被称为无条件转移语句,它通常与条件语句配合使用来改变程序流向,使得程序转去执行语句标号所标识的语句。

       关于是否应该使用goto语句,历史上也争论不休。恐怕国内大部分教授高级编程语言的课堂上,都会主张在结构化程序设计中不使用goto语句, 以免造成程序流程的混乱,使得理解和调试程序都产生困难。历史上支持goto语句有害的人的主要理由是:goto语句会使程序的静态结构和动态结构不一致,从而使程序难以理解难以查错。并且G·加科皮尼和C·波姆从理论上证明了:任何程序都可以用顺序、分支和重复结构表示出来。这个结论表明,从高级程序语言中去掉goto语句并不影响高级程序语言的编程能力,而且编写的程序的结构更加清晰。

       然而伟大的哲学家黑格尔说过:存在即合理。当笔者刚从校园中走出的时候,对于goto语句有害论也深以为然,然后多年之后在自己编写的代码中随处可见goto的身影。如今很多高级编程语言中,似乎是难以看见goto的身影:Java中不提供goto语句,虽然仍然保留goto为关键字,但不支持它的使用;C#中依然支持goto语句,但是一般不建议使用。其实可以很容易发现一点,这些不提倡使用goto语句的语言,大多是有自带的垃圾回收机制,也就是说不需要过多关心资源的释放的问题,因而在程序流程中没有“为资源释放设置统一出口”的需求。然而对于C++语言来说,程序员需要自己管理资源的分配和释放。倘若没有goto语句,那么我们在某个函数资源分配后的每个出错点需要释放资源并返回结果。虽然我们依然可以不使用goto语句完整地写完流程,但是代码将变得又臭又长。譬如我们需要写一个全局函数g_CreateListenSocket用来创建监听套接字,那么如果不使用goto语句,我们的代码将会是这个样子:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define MAX_ACCEPT_BACK_LOG 5

void g_CloseSocket(int &nSockfd)
{
    if ( -1 == nSockfd )
    {
        return;
    }

    struct linger li = { 1, 0 };
    ::setsockopt(nSockfd, SOL_SOCKET, SO_LINGER, (const char *)&li, sizeof(li));
    ::close(nSockfd);
    nSockfd = -1;
}

in_addr_t g_InetAddr(const char *cszIp)
{
    in_addr_t uAddress = INADDR_ANY;

    if ( 0 != cszIp && &#39;\0&#39; != cszIp[0] )
    {
        if ( INADDR_NONE == (uAddress = ::inet_addr(cszIp)) )
        {
            uAddress = INADDR_ANY;
        }
    }

    return uAddress;
}

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nOptVal   = 1;
    int nRetCode  = 0;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if ( -1 == nSocketfd )
    {
        return nSocketfd;
    }

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    if ( 0 != nRetCode )                                                                                  
    {                                                                                                     
        g_CloseSocket(nSocketfd);                                                                         
        return nSocketfd;                                                                                 
    }                                                                                                     

    // bind address
    saBindAddr.sin_family      = AF_INET;
    saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
    saBindAddr.sin_port        = ::htons(uPort);

    nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));                    
    if ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        g_CloseSocket(nSocketfd);                                                                        
        return nSocketfd;                                                                                
    }                                                                                                    

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);                                                 
    if ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        g_CloseSocket(nSocketfd);                                                                        
        return nSocketfd;                                                                                
    }                                                                                                    

    return nSocketfd;
}

       上面蓝色标记的代码中就包含了出错时候对资源(这里是套接字描述符)进行清理的操作,这里只有单一的资源,所以流程看起来也比较干净。倘若流程中还夹杂着内存分配、打开文件的操作,那么对资源释放操作将变得复杂,不仅代码变得臃肿难看,还不利于对流程的理解。而如果使用了goto语句,那么我们统一为资源释放设定单一出口,那么代码将会是下面这个样子:

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nOptVal   = 1;
    int nRetCode  = 0;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);                                              
    if ( -1 == nSocketfd )                                                                               
    {                                                                                                    
        goto Exit0;                                                                                      
    }                                                                                                    

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    if ( 0 != nRetCode )                                                                                  
    {                                                                                                     
        goto Exit0;                                                                                       
    }                                                                                                     

    // bind address
    saBindAddr.sin_family      = AF_INET;
    saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
    saBindAddr.sin_port        = ::htons(uPort);

    nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));                    
    if ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        goto Exit0;                                                                                      
    }                                                                                                    

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);                                                 
    if ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        goto Exit0;                                                                                      
    }                                                                                                    

    // success here
    return nSocketfd;
Exit0:
    // fail and clean up resources here
    if (-1 != nSocketfd)
    {
        g_CloseSocket(nSocketfd);
    }
    return nSocketfd;
}

        其实可以发现,加入goto语句之后,流程反而变得清晰了。一个函数将拥有两个出口:执行成功返回和执行失败返回。每次在流程某处出错后都跳转到固定标号处执行资源释放操作,这样在主体流程中将不再出现与资源释放相关的代码,那么主体流程只需专注于逻辑功能,代码将变得更易于理解和维护。另外一个好处就是不容易忘记释放资源,只需要养成分配完一个资源后立即在资源统一释放处编写资源释放代码的好习惯即可,对于程序员复查自己的代码也带来好处。

使用宏来简化代码量


        仔细观察上面的代码,再结合前面所言的goto语句通常与条件语句配合使用来改变程序流向,可以总结规律:我们总是检查某个条件是否成立,如果条件不成立立即goto到指定的函数执行失败入口处,那么我们可以设计宏如下:

#undef  DISABLE_WARNING
#ifdef _MSC_VER                                                         // MS VC++
#define DISABLE_WARNING(code, expression)           \
    pragma(warning(push))                         \
    pragma(warning(disable:code)) expression      \
    pragma(warning(pop))
#else                                                                   // GCC
#define DISABLE_WARNING(code, expression)           \
    expression
#endif // _MSC_VER

#undef  WHILE_FALSE_NO_WARNING
#define WHILE_FALSE_NO_WARNING DISABLE_WARNING(4127, while(false))

#undef  PROCESS_ERROR_Q
#define PROCESS_ERROR_Q(condition)                  \
    do                                              \
    {                                               \
        if (!(condition))                           \
        {                                           \
            goto Exit0;                             \
        }                                           \
    } WHILE_FALSE_NO_WARNING

#undef  PROCESS_ERROR
#define PROCESS_ERROR(condition)                    \
    do                                              \
    {                                               \
        if (!(condition))                           \
        {                                           \
            assert(false);                          \
            goto Exit0;                             \
        }                                           \
    } WHILE_FALSE_NO_WARNING
int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nOptVal   = 1;
    int nRetCode  = 0;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);                                               
    PROCESS_ERROR(-1 != nSocketfd);                                                                       

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    PROCESS_ERROR(0 == nRetCode);                                                                         

    // bind address
    saBindAddr.sin_family      = AF_INET;
    saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
    saBindAddr.sin_port        = ::htons(uPort);

    nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));                     
    PROCESS_ERROR(0 == nRetCode);                                                                         

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);                                                  
    PROCESS_ERROR(0 == nRetCode);                                                                         

    // success here
    return nSocketfd;
Exit0:
    // fail and clean up resources here
    if (-1 != nSocketfd)
    {
        g_CloseSocket(nSocketfd);
    }
    return nSocketfd;
}

统一函数出口


       如果想统一函数出口,其实方法很简单:只需要加入一个int nResult字段,初始化为false,在函数流程完全走完时标记为true,然后在释放资源处判断该字段是否为false即可。可以参考下面代码:

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nResult   = false;           
    int nRetCode  = false;
    int nOptVal   = 1;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    PROCESS_ERROR(-1 != nSocketfd);

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    PROCESS_ERROR(0 == nRetCode);

    // bind address
    saBindAddr.sin_family      = AF_INET;
    saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
    saBindAddr.sin_port        = ::htons(uPort);

    nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));
    PROCESS_ERROR(0 == nRetCode);

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);
    PROCESS_ERROR(0 == nRetCode);

    // success here
    nResult = true;                  
Exit0:
    // fail and clean up resources here
    if (!nResult)                    
    {                                
        if (-1 != nSocketfd)         
        {                            
            g_CloseSocket(nSocketfd);
        }                            
    }                                

    return nSocketfd;
}

测试代码


        最后附上上述代码的测试代码:

int main(int argc, char ** argv)
{
    socklen_t nAddrLen        = sizeof(struct sockaddr_in);
    int       nListenSocketfd = -1;
    struct sockaddr_in saRemoteAddr;

    nListenSocketfd = g_CreateListenSocket("", 9999);
    if ( -1 == nListenSocketfd )
    {
        return 0;
    }

    while (true)
    {
        ::memset(&saRemoteAddr, 0, sizeof(saRemoteAddr));
        int nSocketfd = ::accept(nListenSocketfd, (struct sockaddr *)&saRemoteAddr, &nAddrLen);

        ::printf("Accept a new connection from [ip - %s, port - %d]\n",
            ::inet_ntoa(saRemoteAddr.sin_addr),
            ::ntohs(saRemoteAddr.sin_port)
        );

        g_CloseSocket(nSocketfd);
    }

    return 1;
}

 

以上是是否应该使用goto语句?的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
高流量网站的PHP性能调整高流量网站的PHP性能调整May 14, 2025 am 12:13 AM

TheSecretTokeEpingAphp-PowerEdwebSiterUnningSmoothlyShyunderHeavyLoadInVolvOLVOLVOLDEVERSALKEYSTRATICES:1)emplactopCodeCachingWithOpcachingWithOpCacheToreCescriptexecution Time,2)使用atabasequercachingCachingCachingWithRedataBasEndataBaseLeSendataBaseLoad,3)

PHP中的依赖注入:初学者的代码示例PHP中的依赖注入:初学者的代码示例May 14, 2025 am 12:08 AM

你应该关心DependencyInjection(DI),因为它能让你的代码更清晰、更易维护。1)DI通过解耦类,使其更模块化,2)提高了测试的便捷性和代码的灵活性,3)使用DI容器可以管理复杂的依赖关系,但要注意性能影响和循环依赖问题,4)最佳实践是依赖于抽象接口,实现松散耦合。

PHP性能:是否可以优化应用程序?PHP性能:是否可以优化应用程序?May 14, 2025 am 12:04 AM

是的,优化papplicationispossibleandessential.1)empartcachingingcachingusedapcutorediucedsatabaseload.2)优化的atabaseswithexing,高效Quereteries,and ConconnectionPooling.3)EnhanceCodeWithBuilt-unctions,避免使用,避免使用ingglobalalairaiables,并避免使用

PHP性能优化:最终指南PHP性能优化:最终指南May 14, 2025 am 12:02 AM

theKeyStrategiestosiminificallyBoostphpapplicationPermenCeare:1)useOpCodeCachingLikeLikeLikeLikeLikeCacheToreDuceExecutiontime,2)优化AtabaseInteractionswithPreparedStateTemtStatementStatementSandProperIndexing,3)配置

PHP依赖注入容器:快速启动PHP依赖注入容器:快速启动May 13, 2025 am 12:11 AM

aphpdepentioncontiveContainerIsatoolThatManagesClassDeptions,增强codemodocultion,可验证性和Maintainability.itactsasaceCentralHubForeatingingIndections,因此reducingTightCightTightCoupOulplingIndeSingantInting。

PHP中的依赖注入与服务定位器PHP中的依赖注入与服务定位器May 13, 2025 am 12:10 AM

选择DependencyInjection(DI)用于大型应用,ServiceLocator适合小型项目或原型。1)DI通过构造函数注入依赖,提高代码的测试性和模块化。2)ServiceLocator通过中心注册获取服务,方便但可能导致代码耦合度增加。

PHP性能优化策略。PHP性能优化策略。May 13, 2025 am 12:06 AM

phpapplicationscanbeoptimizedForsPeedAndeffificeby:1)启用cacheInphp.ini,2)使用preparedStatatementSwithPdoforDatabasequesies,3)3)替换loopswitharray_filtaray_filteraray_maparray_mapfordataprocrocessing,4)conformentnginxasaseproxy,5)

PHP电子邮件验证:确保正确发送电子邮件PHP电子邮件验证:确保正确发送电子邮件May 13, 2025 am 12:06 AM

phpemailvalidation invoLvesthreesteps:1)格式化进行regulareXpressecthemailFormat; 2)dnsvalidationtoshethedomainhasavalidmxrecord; 3)

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。