在學習第一門編程語言時,就已經介紹了順序分支、條件分支、循環分支。比如循環分支有for、while、do-while語句。在隨后的學校及工作中,如果手工循環一般使用for、while,很少使用do-while,感覺用處不大,但現在看來,do-while大有用途。
1. 幫助定義復雜的宏以避免錯誤
舉例來說,假設你需要定義這樣一個宏:
#define DOSOMETHING() foo1(); foo2();
這個宏的本意是,當調用DOSOMETHING()時,函數foo1()和foo2()都會被調用。但是如果你在調用的時候這么寫:
if(a>0) DOSOMETHING();
因為宏在預處理的時候會直接被展開,你實際上寫的代碼是這個樣子的:
if(a>0) foo1(); foo2();
這就出現了問題,因為無論a是否大於0,foo2()都會被執行,導致程序出錯。
那么僅僅使用{}將foo1()和foo2()包起來行么?比如:
#define DOSOMETHING() { foo1(); foo2(); }
我們在寫代碼的時候都習慣在語句右面加上分號,如果在宏中使用{},代碼里就相當於這樣寫了:“{...};”,展開后就是這個樣子:
if(a>0) { foo1(); foo2(); };
很明顯,這是一個語法錯誤(大括號后多了一個分號)。
在所有可能情況下,期望我們寫的多語句宏總能有正確的表現幾乎是不可能的。你不能讓宏表現的像函數一樣---在沒有do/while(0)的情況下。
如果我們使用do{...}while(0)來定義宏,即:
#define DOSOMETHING() \ do{ \ foo1();\ foo2();\ }while(0)\
這樣,宏被展開后,上面的調用語句才會保留初始的語義。do能確保大括號里的邏輯能被執行,而while(0)能確保該邏輯只被執行一次,就像沒有循環語句一樣。
總結:在Linux和其它代碼庫里的,很多宏實現都使用do/while(0)來包裹他們的邏輯,這樣不管在調用代碼中怎么使用分號和大括號,而該宏總能確保其行為是一致的。
我的心得,這是我最喜歡使用do-while的一處用法,如果你開發了一個庫、甚至就只是一個宏實現提供給別人使用,你不能確信所有人都會按照你的意圖正確使用該實現,有的使用者處處加大括號,也有的人認為你會處理的。因此你需要保證你的實現在各種使用場景下都是表現一致的。另外,就像Scott Meyers在Effective C++中談及的,讓接口容易被正確使用,而不易被誤用。
2. 避免使用goto控制程序流
在一些函數中,我們可能需要在return語句之前做一些清理工作,比如釋放在函數開始處由malloc申請的內存空間,使用goto總是一種簡單的方法:
int foo() { somestruct *ptr = malloc(...); dosomething...; if(error) goto END; dosomething...; if(error) goto END; dosomething...; END: free(ptr); return 0; }
但由於goto不符合軟件工程的結構化,而且有可能使得代碼難懂,所以很多人都不倡導使用,這個時候我們可以使用do{...}while(0)來做同樣的事情:
int foo() { somestruct *ptr = malloc(...); do { dosomething...; if(error) break; dosomething...; if(error) break; dosomething...; } while(0); free(ptr); return 0; }
這里將函數主體部分使用do{...}while(0)包含起來,使用break來代替goto,后續的清理工作在while之后,現在既能達到同樣的效果,而且代碼的可讀性、可維護性都要比上面的goto代碼好的多了。
我的心得,這也是喜歡do-while的第二個用法,非常好。比如有個人認為這樣會導致很多變量要在do-while語句外面提前聲明(這是C++程序反對的一點,用時才聲明),也有人認為還有強大的RAII和智能指針神馬的,不怕內存問題。但是,在某些場景下,要是這些都不能用呢,比如C語言?比如沒有smart ptr?比如維護的是舊代碼?
3. 避免由宏引起的警告
內核中由於不同架構的限制,很多時候會用到空宏,。在編譯的時候,這些空宏會給出warning,為了避免這樣的warning,我們可以使用do{...}while(0)來定義空宏:
#define EMPTYMICRO do{}while(0)
好吧,這種情況我還沒有見到。
4. 定義單一的函數塊來完成復雜的操作
如果你有一個復雜的函數,變量很多,而且你不想要增加新的函數,可以使用do{...}while(0),將你的代碼寫在里面,里面可以定義變量而不用考慮變量名會同函數之前或者之后的重復。
這種情況應該是指一個變量多處使用(但每處的意義還不同),我們可以在每個do-while中縮小作用域,比如:
int key; string value; int func() { int key = GetKey(); string value = GetValue(); dosomething for key,value; do{ int key;string value; dosomething for this key,value; }while(0); }
注,以上內容來自:
http://www.pixelstech.net/article/1390482950-do-%7B-%7D-while-%280%29-in-macros
http://www.pixelstech.net/article/1350871981-Significance-and-use-of-do%7B-%7Dwhile%280%29-