<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                條款五:優先使用`auto`而非顯式類型聲明 ========================= 使用下面語句是簡單快樂的 ```cpp int x; ``` 等等。見鬼,我忘記初始化`x`了,因此它的值是無法確定的。也許,它會被初始化為0。但是這根據上下文語境決定。這真令人嘆息。 不要介意。我們來看看一個要通過迭代器解引用初始化的局部變量聲明的簡單與快樂。 ```cpp template<typename It> void dwim(It b, It e) { while(b != e){ typename std::iterator_traits<It>::value_type currValue = *b; ... } } ``` 額。`typename std::iterator_traits<It>::value_type`來表示被迭代器指向的值的類型?真的是這樣嗎?我必須努力不去想這是多么有趣的一件事。見鬼。等等,難道我已經說出來了。 好吧,有三個令人愉悅的地方:聲明一個封裝好的局部變量的類型帶來的快樂。是的,這是沒有問題的。一個封裝體的類型只有編譯器知道,因此不能被顯示的寫出來。哎,見鬼。 見鬼,見鬼,見鬼!使用`C++`編程并不是它本該有的愉悅體驗。 是的,過去的確不是。但是由于`C++11`,得益于`auto`,這些問題都消失了。`auto`變量從他們的初始化推導出其類型,所以它們必須被初始化。這就意味著你可以在現代的`C++`高速公路上對沒有初始化的變量的問題說再見了。 ``` cpp int x1; // potentially uninitialized auto x2; // error! initializer required auto x3 = 0; // fine, x's value is well-defined ``` 如上所述,高速公路上不再有由于解引用迭代器的聲明局部變量而引起的坑坑洼洼。 ``` cpp template<typename It> void dwim(It b, It e) { while(b != e){ auto currValue = *b; ... } } ``` 由于`auto`使用類型推導(參見條款2),它可以表示那些僅僅被編譯器知曉的類型: ``` cpp auto dereUPLess = // comparison func. [](const std::unique_ptr<Widget>& p1, // for Widgets const std::unique_ptr<Widget>& p2) // pointed to by { return *p1 < *p2}; // std::unique_ptrs ``` 非常酷。在`C++14`中,模板(原文為temperature)被進一步丟棄,因為使用`lambda`表達式的參數可以包含`auto`: ``` cpp auto derefLess = // C++14 comparison [](const auto& p1, // function for const auto& p2) // values pointed { return *p1 < *p2; }; ``` 盡管非常酷,也許你在想,我們不需要使用`auto`去聲明一個持有封裝體的變量,因為我們可以使用一個`std::function`對象。這是千真萬確的,我們可以這樣干,但是也許那不是你正在思考的東西。也許你在思考“`std::function`是什么東東?”。因此讓我們解釋清楚。 `std::function`是`C++11`標準庫的一個模板,它可以使函數指針普通化。鑒于函數指針只能指向一個函數,然而,`std::function`對象可以應用任何可以被調用的對象,就像函數。就像你聲明一個函數指針的時候,必須指明這個函數指針指向的函數的類型,你產生一個`std::function`對象時,你也指明它要引用的函數的類型。你可以通過`std::function`的模板參數來完成這個工作。例如,有聲明一個名為`func`的`std::function`對象,它可以引用有如下特點的可調用對象: ``` cpp bool(const std::unique_ptr<Widget> &, // C++11 signature for const std::unique_ptr<Widget> &) // std::unique_ptr<Widget> // comparison funtion ``` 你可以這么寫: ``` cpp std::function<bool(const std::unique_ptr<Widget> &, const std::unique_ptr<Widget> &)> func; ``` 因為`lambda`表達式得到一個可調用對象,封裝體可以存儲在`std::function`對象里面。這意味著,我們可以聲明不適用`auto`的`C++11`版本的`dereUPLess`如下: ``` cpp std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> derefUPLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) {return *p1 < *p2; }; ``` 意識到需要重復參數的類型這種冗余的語法是重要的,使用`std::function`和使用`auto`并不一樣。一個使用`auto`聲明持有一個封裝的變量和封裝體有同樣的類型,也僅使用和封裝體同樣大小的內存。持有一個封裝體的被`std::function`聲明的變量的類型是`std::function`模板的一個實例,并且對任何類型只有一個固定的大小。這個內存大小可能不能滿足封裝體的需求。出現這種情況時,`std::function`將會開辟堆空間來存儲這個封裝體。導致的結果就是`std::function`對象一般會比`auto`聲明的對象使用更多的內存。由于實現細節中,約束內嵌的使用和提供間接函數的調用,通過`std::function`對象來調用一個封裝體比通過`auto`對象要慢。換言之,`std::function`方法通常體積比`auto`大,并且慢,還有可能導致內存不足的異常。就像你在上面一個例子中看到的,使用`auto`的工作量明顯小于使用`std::function`。持有一個封裝體時,`auto`和`std::function`之間的競爭,對`auto`簡直就是游戲。(一個相似的論點也成立對于持有`std::blind`調用結果的`auto`和`std::function`,但是在條款34中,我將竭盡所能的說服你盡可能使用`lambda`表達式,而不是`std::blind`)。 `auto`的優點除了可以避免未初始化的變量,變量聲明引起的歧義,直接持有封裝體的能力。還有一個就是可以避免“類型截斷”問題。下面有個例子,你可能見過或者寫過: ```cpp std::vector<int> v; ... unsigned sz = v.size(); ``` `v.size()`定義的返回類型是`std::vector<int>::size_type`,但是很少有開發者對此十分清楚。`std::vector<int>::size_type`被指定為一個非符號的整數類型,因此很多程序員認為`unsigned`類型是足夠的,然后寫出了上面的代碼。這將導致一些有趣的后果。比如說在32位`Windows`系統上,`unsigned`和`std::vector<int>::size_type`有同樣的大小,但是在64位的`Windows`上,`unsigned`是32bit的,而`std::vector<int>::size_type`是64bit的。這意味著上面的代碼在32位`Windows`系統上工作良好,但是在64位`Windows`系統上時有可能不正確,當應用程序從32位移植到64位上時,誰又想在這種問題上浪費時間呢? 使用`auto`可以保證你不必被上面的東西所困擾: ```cpp auto sz = v.size() // sz's type is std::vector<int>::size_type ``` 仍然不太確定使用`auto`的高明之處?看看下面的代碼: ```cpp std::unordered_map<std::string, int> m; ... for (const std::pair<std::string, int>& p : m) { ... // do something with p } ``` 這看上去完美合理。但是有一個問題,你看出來了嗎? 意識到`std::unorder_map`的`key`部分是`const`類型的,在哈希表中的`std::pair`的類型不是`std::pair<std::string, int>`,而是`std::pair<const std::sting, int>`。但是這不是循環體外變量`p`的聲明類型。后果就是,編譯器竭盡全力去找到一種方式,把`std::pair<const std::string, int>`對象(正是哈希表中的內容)轉化為`std::pair<std::string, int>`對象(`p`的聲明類型)。這個過程將通過復制`m`的一個元素到一個臨時對象,然后將這個臨時對象和`p`綁定完成。在每個循環結束的時候這個臨時對象將被銷毀。如果是你寫了這個循環,你將會感覺代碼的行為令人吃驚,因為你本來想簡單地將引用`p`和`m`的每個元素綁定的。 這種無意的類型不匹配可以通過`auto`解決 ```cpp for (const auto& p : m) { ... // as before } ``` 這不僅僅更高效,也更容易敲擊代碼。更近一步,這個代碼還有一些吸引人的特性,比如如果你要取`p`的地址,你的確得到一個指向`m`的元素的指針。如果不使用`auto`,你將得到一個指向臨時對象的指針——這個臨時對象在每次循環結束時將被銷毀。 上面兩個例子中——在應該使用`std::vector<int>::size_type`的時候使用`unsigned`和在該使用`std::pair<const std::sting, int>`的地方使用`std::pair<std::string, int>`——說明顯式指定的類型是如何導致你萬萬沒想到的隱式的轉換的。如果你使用`auto`作為目標變量的類型,你不必為你聲明類型和用來初始化它的表達式類型之間的不匹配而擔心。 有好幾個使用`auto`而不是顯式類型聲明的原因。然而,`auto`不是完美的。`auto`變量的類型都是從初始化它的表達式推導出來的,一些初始化表達式并不是我們期望的類型。發生這種情況時,你可以參考條款2和條款6來決定怎么辦,我不在此處展開了。相反,我將我的精力集中在你將傳統的類型聲明替代為`auto`時帶來的代碼可讀性問題。 首先,深呼吸放松一下。`auto`是一個可選項,不是必須項。如果根據你的專業判斷,使用顯式的類型聲明比使用`auto`會使你的代碼更加清晰或者更好維護,或者在其他方面更有優勢,你可以繼續使用顯式的類型聲明。牢記一點,`C++`并沒有在這個方面有什么大的突破,這種技術在其他語言中被熟知,叫做類型推斷(`type inference`)。其他的靜態類型過程式語言(像`C#`,`D`,`Scala`,`Visual Basic`)也有或多或少等價的特點,對靜態類型的函數編程語言(像`ML`,`Haskell`,`OCaml`,`F#`等)另當別論。一定程度上說,這是受到動態類型語言的成功所啟發,比如`Perl`,`Python`,`Ruby`,在這些語言中很少顯式指定變量的類型。軟件開發社區對于類型推斷有很豐富的經驗,這些經驗表明這些技術和創建及維護巨大的工業級代碼庫沒有矛盾。 一些開發者被這樣的事實困擾,使用`auto`會消除看一眼源代碼就能確定對象的類型的能力。然而,IDE提示對象類型的功能經常能緩解這個問題(甚至考慮到在條款4中提到的IDE的類型顯示問題),在很多情況下,一個對象類型的摘要視圖和顯示完全的類型一樣有用。比如,摘要視圖足以讓開發者知道這個對象是容器還是計數器或者一個智能指針,而不需要知道這個容器,計數器或者智能指針的確切特性。假設比較好的選擇變量名字,這樣的摘要類型信息幾乎總是唾手可得的。 事實是顯式地寫出類型可能會引入一些難以察覺的錯誤,導致正確性或者效率問題,或者兩者兼而有之。除此之外,`auto`類型會自動的改變如果初始化它的表達式改變后,這意味著通過使用`auto`可以使代碼重構變得更簡單。舉個例子,如果一個函數被聲明為返回`int`,但是你稍后決定返回`long`可能更好一些,如果你把這個函數的返回結果存儲在一個`auto`變量中,在下次編譯的時候,調用代碼將會自動的更新。結果如果存儲在一個顯式聲明為`int`的變量中,你需要找到所有調用這個函數的地方然后改寫他們。 |要記住的東西| | :--------- | | `auto`變量一定要被初始化,并且對由于類型不匹配引起的兼容和效率問題有免疫力,可以簡單化代碼重構,一般會比顯式的聲明類型敲擊更少的鍵盤| | `auto`類型的變量也受限于[條款2](../DeducingTypes/2-Understand-auto-type-deduction.html)和條款6中描述的陷阱|
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看