C 乾貨系列 談談在變數中使用auto

2021-10-09 18:20:24 字數 3561 閱讀 6143

用作變數型別推演的關鍵字auto可能是c++11最著名的特性了,所以這篇文章我長話短說,簡單寫一下我認為比較重要的使用法則。

我們都是用auto這個關鍵字來代替乙個具體變數的型別定義,讓編譯器自己去尋找和從它的初始化過程中來推演這個變數的型別。最著名的關於auto的用法恐怕莫過於用它來躲開一堆長長的型別名,比如stl容器的iterator,但是它也可以有其他用處:

std::vectornums;

for(auto iter = std::begin(nums); iter != std::end(nums); iter++)

在上邊這個例子中,iter就被推演為型別std::vector::iterator,然後n的型別被推演成int&。需要注意的是n被顯式地宣告為引用型別,否則他就是int型別了。

在上邊的列子中,乙個顯而易見的好處就是:auto打起來要比std::vector::iterator短多了。另外,也有可能我們會碰到一些完全未知的型別,比如lambda表示式中。有讀者可能會有疑惑,明明auto&int&還要長,為什麼那裡我也要堅持用auto呢?

除了碼字碼得少一些意外以外,我使用auto還有另外兩個理由。

上下一致性

在某些地方,如果你希望編譯器去自己根據上下文環境確定乙個變數的型別而是用了auto,那麼你在每個類似的地方都要使用它。使用兩個不同的編碼習慣可能會讓讀你**的人疑惑:為什麼你這個地方用了一種風格,其他地方用了另一種風格呢,會不會這裡有什麼需要注意的地方呢?——其實沒有,只是你的壞習慣讓**的可讀性變差了而已。

可維護性

另乙個原因是一貫地使用auto會改善**的可維護性。上邊例子中,for迴圈裡所有變數的型別都是由nums推導出來了,nums很明顯是一系列數字的集合。但是如果有人突然覺得std::vector在這裡並不合適呢?或者更有可能是你的產品覺得這裡int太小了,要改為unsigned long呢?

如果你像上邊那樣寫,所有你需要做的事情無非就是把std::vector改為std::array——其他變數的型別變化就交給編譯器和auto來處理好了:iter會自動變為std::array::iterator,然後n變為unsigned long&。如果你把n顯式宣告為int&,想想你之後還要改多少東西吧。

給沒耐心的讀者:

初始化auto,請使用不戴花括號的拷貝賦值操作符=

我知道當你一眼看見auto的時候可能內心會蹦出來好幾種可能的用法,但是只有一種是正確的。

auto x,能這麼寫說明你對c++11的initializer_list有了一定了解了;但是在auto中這麼寫,你會得到乙個錯誤的型別推演——它會把x的型別推演成initializer_list,其中sometype就是你寫的something。同樣的,如果你寫成auto x = ;它也會把型別自動推演成initializer_list

所以,只有=會最保險地完成初始化auto這個任務。直接寫成auto x = something

在使用auto的時候,給變數和函式起乙個好名字就顯得極為重要了,因為讀你**的人——很有可能是未來的你——只能通過這個變數名來推斷它的型別和作用了,除非你要寫很多不必要的注釋或者指望讀者往上翻好多上下文。比如:auto x = somefunc();只告訴了我們somefunc函式的返回型別和x的型別是一樣的,是不是很讓人惱火?我們即使靠猜都對x的型別好無頭緒。另一方面呢,auto points = calculatescore();是不是好很多,我們會覺得這是乙個計算分數的函式,返回的結果也八成是和分數有關的數或型別。

這個問題的答案到此就非常明顯了:

如果那個地方的非關鍵中間變數完全依賴於上下文中其他變數的型別,那麼就用auto吧。

如果什麼時候我們想把乙個變數的型別顯式的固定下來呢?下邊有兩種方法做這件事:要麼顯式地宣告這個變數的型別,要麼顯式地使用這個變數的構造器:

std::size_t size;	//2是int型別,但是我們想要size_t

auto size = std::size_t; //同上

關於上邊這兩種用法就以下幾個方面有一些爭執:

上邊第一種方法會讓**更清晰,讀者第一眼看到的就是這個變數被我們固定下來的型別。有了auto後,讀者必須從頭到尾把整句話都讀完,直到他看到了我們顯式呼叫的構造器型別才能知道這個變數的型別。

另一方面呢,有人會說,我們想把某個變數的型別固定下來是一回事,讓讀者知道這裡把這個變數固定成某個型別了卻是另一回事——事實上我們確實可以起乙個好名字讓讀者一下就能把這個變數的型別猜個**不離十。

除此之外,如果乙個顯示的c++cast被呼叫,比如dynamic_cast(baseptr)這個變數的型別已經被宣告在了cast當中並且應該也沒人會看不見它吧,這裡也是使用auto的乙個好理由。

auto強制乙個變數的初始化過程,這是件好事。我們再也不用擔心忘記初始化乙個變數,因為我們不初始化編譯器就沒法編譯它。不過鑑於幾乎所有的編譯器見到沒有初始化的變數都會朝你吼個半天(warning),而且各種static analyzer也都把這種問題當作「不會行走的三等功」看待,我覺得這個點可能也沒那麼重要了。

拷貝構造並不是對所有型別都可行的,特別是某些型別既沒有拷貝也沒有移動建構函式的情形下,直接用auto進行拷貝初始化可能對它們就不太合適了。它就意味著,你無法用auto來初始化類似的物件。

所以就像上邊討論的結果,哪種方法都不是完美的,這就是為什麼我除了「使用auto時應該注意上下一致性」以外不願意給出乙個使用auto的一般性原則。希望這篇文章給你帶來了一些幫助和思考。

後邊我會出另一篇文章,專門講auto在函式中的用法。

面試系列 談談執行緒池的使用

他的主要特點就是 執行緒復用,管理執行緒,控制最大併發數。使用執行緒池的好處 第一 降低資源消耗,通過重複利用自己建立的執行緒降低執行緒建立和銷毀造成的消耗。第二 提高響應速度.當任務到達時,任務可以不需要等到執行緒和粗昂就愛你就能立即執行.第三 提高執行緒的可管理性.執行緒是稀缺資源,如果無限的創...

在C 中使用Queue

介紹 queue類執行將放在在queue中的物件採用先進先出的資料結構。物件從一端插入到佇列中從另一端移除。物件的順序程序使用queue queue介面 queue類實現了三個主要集合介面 icollection ienumerable和icloneable。queue的重要方法 queue類常用的...

在 C 中使用畫筆

出處 在 c 中使用畫筆 public class rectangle shape protected point m start protected point m end public rectangle point start,point end,color fgcolor m start s...