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 && '\0' != 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 サイトの他の関連記事を参照してください。

ホットAIツール

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

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

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

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

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

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

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

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

ホットトピック









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

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

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

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

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

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

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

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