目錄
是否应该使用goto语句" >是否应该使用goto语句
使用宏来简化代码量" >使用宏来简化代码量
统一函数出口" >统一函数出口
测试代码" >测试代码
首頁 後端開發 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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
2 週前 By 尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用磁力鏈接 如何使用磁力鏈接 Feb 18, 2024 am 10:02 AM

如何使用磁力鏈接

如何使用mdf和mds文件 如何使用mdf和mds文件 Feb 19, 2024 pm 05:36 PM

如何使用mdf和mds文件

crystaldiskmark是什麼軟體? -crystaldiskmark如何使用? crystaldiskmark是什麼軟體? -crystaldiskmark如何使用? Mar 18, 2024 pm 02:58 PM

crystaldiskmark是什麼軟體? -crystaldiskmark如何使用?

foob​​ar2000怎麼下載? -foobar2000怎麼使用 foob​​ar2000怎麼下載? -foobar2000怎麼使用 Mar 18, 2024 am 10:58 AM

foob​​ar2000怎麼下載? -foobar2000怎麼使用

網易信箱大師怎麼用 網易信箱大師怎麼用 Mar 27, 2024 pm 05:32 PM

網易信箱大師怎麼用

小愛音箱怎麼用 小愛音箱怎麼連接手機 小愛音箱怎麼用 小愛音箱怎麼連接手機 Feb 22, 2024 pm 05:19 PM

小愛音箱怎麼用 小愛音箱怎麼連接手機

百度網盤app怎麼用 百度網盤app怎麼用 Mar 27, 2024 pm 06:46 PM

百度網盤app怎麼用

pip鏡像來源簡易指南:輕鬆掌握使用方法 pip鏡像來源簡易指南:輕鬆掌握使用方法 Jan 16, 2024 am 10:18 AM

pip鏡像來源簡易指南:輕鬆掌握使用方法

See all articles