目次
是否应该使用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 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

マグネットリンクの使い方 マグネットリンクの使い方 Feb 18, 2024 am 10:02 AM

マグネット リンクは、リソースをダウンロードするためのリンク方法であり、従来のダウンロード方法よりも便利で効率的です。マグネット リンクを使用すると、中間サーバーに依存せずに、ピアツーピア方式でリソースをダウンロードできます。この記事ではマグネットリンクの使い方と注意点を紹介します。 1. マグネット リンクとは? マグネット リンクは、P2P (Peer-to-Peer) プロトコルに基づくダウンロード方式です。ユーザーはマグネット リンクを通じてリソースの発行者に直接接続し、リソースの共有とダウンロードを完了できます。従来のダウンロード方法と比較して、磁気

mdfおよびmdsファイルの使用方法 mdfおよびmdsファイルの使用方法 Feb 19, 2024 pm 05:36 PM

mdf ファイルと mds ファイルの使用方法 コンピューター技術の継続的な進歩により、さまざまな方法でデータを保存および共有できるようになりました。デジタル メディアの分野では、特殊なファイル形式に遭遇することがよくあります。この記事では、一般的なファイル形式である mdf および mds ファイルについて説明し、その使用方法を紹介します。まず、mdf ファイルと mds ファイルの意味を理解する必要があります。 mdf は CD/DVD イメージ ファイルの拡張子で、mds ファイルは mdf ファイルのメタデータ ファイルです。

CrystalDiskmarkとはどのようなソフトウェアですか? -crystaldiskmarkの使い方は? CrystalDiskmarkとはどのようなソフトウェアですか? -crystaldiskmarkの使い方は? Mar 18, 2024 pm 02:58 PM

CrystalDiskMark は、シーケンシャルおよびランダムの読み取り/書き込み速度を迅速に測定する、ハード ドライブ用の小型 HDD ベンチマーク ツールです。次に、編集者が CrystalDiskMark と Crystaldiskmark の使用方法を紹介します。 1. CrystalDiskMark の概要 CrystalDiskMark は、機械式ハード ドライブとソリッド ステート ドライブ (SSD) の読み取りおよび書き込み速度とパフォーマンスを評価するために広く使用されているディスク パフォーマンス テスト ツールです。 ). ランダム I/O パフォーマンス。これは無料の Windows アプリケーションで、使いやすいインターフェイスとハード ドライブのパフォーマンスのさまざまな側面を評価するためのさまざまなテスト モードを提供し、ハードウェアのレビューで広く使用されています。

foob​​ar2000のダウンロード方法は? -foobar2000の使い方 foob​​ar2000のダウンロード方法は? -foobar2000の使い方 Mar 18, 2024 am 10:58 AM

foob​​ar2000 は、音楽リソースをいつでも聴くことができるソフトウェアです。あらゆる種類の音楽をロスレス音質で提供します。音楽プレーヤーの強化版により、より包括的で快適な音楽体験を得ることができます。その設計コンセプトは、高度なオーディオをコンピュータ上で再生可能 デバイスを携帯電話に移植し、より便利で効率的な音楽再生体験を提供 シンプルでわかりやすく、使いやすいインターフェースデザイン 過度な装飾や煩雑な操作を排除したミニマルなデザインスタイルを採用また、さまざまなスキンとテーマをサポートし、自分の好みに合わせて設定をカスタマイズし、複数のオーディオ形式の再生をサポートする専用の音楽プレーヤーを作成します。過度の音量による聴覚障害を避けるために、自分の聴覚の状態に合わせて調整してください。次は私がお手伝いさせてください

NetEase メールボックス マスターの使用方法 NetEase メールボックス マスターの使用方法 Mar 27, 2024 pm 05:32 PM

NetEase Mailbox は、中国のネットユーザーに広く使用されている電子メール アドレスとして、その安定した効率的なサービスで常にユーザーの信頼を獲得してきました。 NetEase Mailbox Master は、携帯電話ユーザー向けに特別に作成された電子メール ソフトウェアで、電子メールの送受信プロセスが大幅に簡素化され、電子メールの処理がより便利になります。 NetEase Mailbox Master の使い方と具体的な機能について、以下ではこのサイトの編集者が詳しく紹介しますので、お役に立てれば幸いです。まず、モバイル アプリ ストアで NetEase Mailbox Master アプリを検索してダウンロードします。 App Store または Baidu Mobile Assistant で「Ne​​tEase Mailbox Master」を検索し、画面の指示に従ってインストールします。ダウンロードとインストールが完了したら、NetEase の電子メール アカウントを開いてログインします。ログイン インターフェイスは次のとおりです。

Baidu Netdisk アプリの使用方法 Baidu Netdisk アプリの使用方法 Mar 27, 2024 pm 06:46 PM

クラウド ストレージは今日、私たちの日常生活や仕事に欠かせない部分になっています。中国有数のクラウド ストレージ サービスの 1 つである Baidu Netdisk は、強力なストレージ機能、効率的な伝送速度、便利な操作体験により多くのユーザーの支持を得ています。また、重要なファイルのバックアップ、情報の共有、オンラインでのビデオの視聴、または音楽の聴きたい場合でも、Baidu Cloud Disk はニーズを満たすことができます。しかし、Baidu Netdisk アプリの具体的な使用方法を理解していないユーザーも多いため、このチュートリアルでは Baidu Netdisk アプリの使用方法を詳しく紹介します。まだ混乱しているユーザーは、この記事に従って詳細を学ぶことができます。 Baidu Cloud Network Disk の使用方法: 1. インストール まず、Baidu Cloud ソフトウェアをダウンロードしてインストールするときに、カスタム インストール オプションを選択してください。

PIP ミラー ソースの簡単なガイド: 使い方を簡単にマスターする PIP ミラー ソースの簡単なガイド: 使い方を簡単にマスターする Jan 16, 2024 am 10:18 AM

簡単に始めましょう: pip ミラー ソースの使用方法 世界中での Python の人気により、pip は Python パッケージ管理の標準ツールになりました。ただし、pip を使用してパッケージをインストールするときに多くの開発者が直面する一般的な問題は速度の遅さです。これは、デフォルトでは、pip は Python 公式ソースまたはその他の外部ソースからパッケージをダウンロードしますが、これらのソースが海外のサーバーに配置されている可能性があるため、ダウンロード速度が遅くなることがあります。ダウンロード速度を向上させるために、pip ミラー ソースを使用できます。ピップミラーソースとは何ですか?簡単に言えば、ただ、

BTCC チュートリアル: BTCC 取引所で MetaMask ウォレットをバインドして使用する方法は? BTCC チュートリアル: BTCC 取引所で MetaMask ウォレットをバインドして使用する方法は? Apr 26, 2024 am 09:40 AM

MetaMask (中国語ではリトル フォックス ウォレットとも呼ばれます) は、無料で評判の高い暗号化ウォレット ソフトウェアです。現在、BTCC は MetaMask ウォレットへのバインドをサポートしており、バインド後は MetaMask ウォレットを使用してすぐにログイン、値の保存、コインの購入などが可能になり、初回バインドで 20 USDT のトライアル ボーナスも獲得できます。 BTCCMetaMask ウォレットのチュートリアルでは、MetaMask の登録方法と使用方法、および BTCC で Little Fox ウォレットをバインドして使用する方法を詳しく紹介します。メタマスクウォレットとは何ですか? 3,000 万人を超えるユーザーを抱える MetaMask Little Fox ウォレットは、現在最も人気のある暗号通貨ウォレットの 1 つです。無料で使用でき、拡張機能としてネットワーク上にインストールできます。

See all articles