<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>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # [18] const正確性 ## FAQs in section [18]: * [18.1] 什么是“const正確性”? * [18.2] “const正確性”是如何與普通的類型安全有何聯系? * [18.3] 我應該“盡早”還是“推遲”確定const正確性? * [18.4] “const Fred* p”是什么意思? * [18.5] “const Fred* p”、“Fred* const p”和“const Fred* const p”有什么不同? * [18.6] “const Fred& x”是什么意思? * [18.7] “Fred& const x”有意義嗎? * [18.8] “Fred const& x”是什么意思? * [18.9] “Fred const* x”是什么意思? * [18.10] 什么是“const成員函數”? * [18.11] 返回引用的成員函數和const成員函數之間有什么聯系? * [18.12] “const重載”是做什么用的? * [18.13] 如果我想讓一個const成員函數對數據成員做“不可見”的修改,應該怎么辦? * [18.14] const_cast會導致無法優化么? * [18.15] 當我用const int*指向一個int后,為什么編譯器還允許我修改這個int? * [18.16] “const Fred* p”的意思是*p不會改變么? * [18.17] 當把Foo**轉換成const Foo**時為什么會出錯? ## 18.1 什么是“`const`正確性”? 這是個好東西。意思是用`const`關鍵字來阻止`const`對象被修改。 例如,如果你要編寫一個函數`f()`,它接收`std::string`類型的參數,并且想要對調用者保證不會修改調用者傳過來的`std::string`參數,可以按以下方法聲明`f()` * `void f1(const std::string& s); ????//傳const引用` * `void f2(const std::string* sptr); ?//傳const指針` * `void f3(std::string s); ???????????//傳值` 在_傳`const`引用_和_傳`const`指針_的情況下,任何試圖在`f()`內部修改`std::string`的行為都會在編譯時被編譯器標記為錯誤。這完全是在編譯時做的,所以使用`const`沒有運行時的空間或速度損失。在_傳值_時(`f3()`),被調用函數獲得了調用者`std::string`的一份拷貝。也就是說,`f3()`可以修改這個拷貝,但返回時這個拷貝會被銷毀。尤其是`f3()`無法修改調用者的`std::string`對象。 舉個反例,如果想要編寫一個函數`g()`,也是接收`std::string`,但想要告知調用者g()有可能會修改調用者的`std::string`對象。這時,可以按以下方法聲明`g()`: * `void g1(std::string& s); ??????//傳非const引用` * `void g2(std::string* sptr); ???//傳非const指針` 在這些函數中省去`const`,就是告訴編譯器允許(但不強制)它們修改調用者的`std::string`對象。因此,這些`g()`函數可以把它們的`std::string`傳遞給任何`f()`函數,但只有`f3()`(通過傳值接收參數)能夠將其參數傳遞給`g1()`或`g2()`。如果`f1()`或`f2()`需要調用`g()`函數,必須給`g()`傳遞一份`std::string`的本地拷貝。`f1()`或`f2()`的參數不能直接傳遞給`g()`函數。例如: ``` ?void?g1(std::string&?s); ?void?f1(const?std::string&?s) ?{ ???g1(s);??????????//?編譯錯誤,因為s是const的 ???std::string?localCopy?=?s; ???g1(localCopy);??//?正確,因為localCopy不是const的 ?} ``` 當然,在上面的例子中,任何`g1()`所做的修改都會反映到`f1()`函數內的`localCopy`對象。特別是,通過`const`引用傳遞給`f1()`的參數不會被修改。 ## 18.2 “`const`正確性”是如何與普通的類型安全有何聯系? 將參數聲明為`const`正是另外一種形式的類型安全。這就好像`const std::string`是與`std::string`不同的類一樣。因為`const`變量缺少一些非`const`變量所具有的一些變更性操作(例如,可以想象以下,`const std::string`沒有賦值操作符)。 如果你發現普通的類型安全有助于構建正確的系統(的確有幫助,尤其是對于大型系統來說),你會發現`const`正確性也有幫助。 ## 18.3 我應該“盡早”還是“推遲”確定`const`正確性? 應該在最最最開始。 事后保證`const`正確性會導致一種滾雪球效應:每次你在一個地方添加了`const`會需要在四個更多的地方也添加`const`。 ## 18.4 “`const Fred* p`”是什么意思? 意思是`p`是一個指向`Fred`類的指針,但不能通過`p`來修改`Fred`對象(當然`p`也可以是`NULL`指針)。 例如,假設`Fred`類有一個叫做`inspect()`的`const`成員函數,那么寫`p->inspect()`是可以的。但如果`Fred`類有一個非`const`成員函數`mutate()`,那么寫`p->mutate()`就是個錯誤(編譯器會捕獲這種錯誤;不會在運行時測試;因此`const`不會降低運行速度)。 ## 18.5 “`const Fred* p`”、“`Fred* const p`”和“`const Fred* const p`”有什么不同? 應該從右往左讀指針聲明。 * `const Fred* p`表明`p`指向一個`const`的`Fred`對象——`Fred`對象不能通過`p`修改。 * `Fred* const p`表明`p`是一個指向`Fred`對象的`const`指針——可以通過`p`修改`Fred`對象,但不能修改`p`本身。 * `cosnt Fred* const p`表明“`p`是一個指向`const Fred`對象的`const`指針”——不能修改`p`,也不能通過`p`修改`Fred`對象。 ## 18.6 “`const Fred& x`”是什么意思? 意思是`x`是`Fred`對象的一個別名,但不能通過`x`來修改`Fred`對象。 例如,假設`Fred`類有一個叫做`inspect()`的`const`成員函數,那么寫`x.inspect()`是可以的。但如果`Fred`類有一個非`const`成員函數`mutate()`,那么寫`x.mutate()`就是個錯誤(編譯器會捕獲這種錯誤;不會在運行時檢查;因此`const`不會降低運行速度)。 ## 18.7 “`Fred& const x`”有意義嗎? 沒意義。 為了理解這個聲明,需要從右往左讀這個聲明。因此“`Fred& const x`”的意思是“`x`是一個指向`Fred`的`const`引用”。但這是多余的,因為引用本來就是`const`的。你不能重新綁定一個引用。不管有沒有`const`,都不行。 換句話說,“`Fred& cosnt x`”在功能上與“`Fred& x`”是一樣的。因為在`&`后面加上`const`沒什么用,因此為了避免迷惑就不應該多此一舉。有人可能會認為這里加上`const`后指向的`Fred`對象就是`const`的了,就好像“`const Fred& x`”一樣。 ## 18.8 “`Fred const& x`”是什么意思? “`Fred cosnt& x`”在功能上與`const Fred& x`相同。然而,真正的問題是_應該_用哪一種。 答案:絕_沒有任何人_能夠為你所在的機構做決定,除非他們了解你的機構。沒有放之四海而皆準的規則。沒有對所有機構都“正確”的答案。所以不要讓_任何人_做倉促的選擇。“思考(Think)”并非一個四字母的單詞。 例如,一些機構看重的是一致性,并且已經有大量代碼使用“`const Fred&`”了。對于他們來說,不管是否有優點,“`Fred const&`”都不是個好選擇。還有很多其它的商業環境,一些傾向于“`Fred const&`”,其它則傾向于“`const Fred&`”。 采用適合你機構中_普通維護程序員_的寫法。不是專家,不是傻瓜,而是維護代碼的普通程序員。除非你決定解雇他們并雇傭新人,否則就要確保_他們_能夠理解你的代碼。根據實際情況做商業決定,而不是根據其它什么人的假設。 使用“`Fred const&`”需要克服一些慣性。現在大多數C++書籍都使用`const Fred&`,大多數程序員學C++時接觸的就是這種語法,并且仍然這么用。這并非是說`const Fred&`一定對你的機構好。但在更改(這種風格)期間,和/或在招收新人時,的確可能會引起一些混亂。一些機構認為用`Fred const&`帶來的好處更大,其它機構則不這么認為。 另一個警告:如果決定用`Fred const&`,確保采取措施使人們不會誤寫成沒意義的“`Fred& const x`”。 ## 18.9 “`Fred const* x`”是什么意思? “`Fred cosnt* x`”在功能上與`const Fred* x`相同。然而,真正的問題是_應該_用哪一種。 答案:絕_沒有任何人_能夠為你所在的機構做決定,除非他們了解你的機構。沒有放之四海而皆準的規則。沒有對所有機構都“正確”的答案。所以不要讓_任何人_做倉促的選擇。“思考(Think)”并非一個四字母的單詞。 例如,一些機構看重的是一致性,并且已經有大量代碼使用“`const Fred*`”了。對于他們來說,不管是否有優點,“`Fred const*`”都不是個好選擇。還有很多其它的商業環境,一些傾向于“`Fred const*`”,其它則傾向于“`const Fred*`”。 采用適合你機構中_普通維護程序員_的寫法。不是專家,不是傻瓜,而是維護代碼的普通程序員。除非你決定解雇他們并雇傭新人,否則就要確保_他們_能夠理解你的代碼。根據實際情況做商業決定,而不是根據其它什么人的假設。 使用“`Fred const*`”需要克服一些慣性。現在大多數C++書籍都使用`const Fred*`,大多數程序員學C++時接觸的就是這種語法,并且仍然這么用。這并非是說`const Fred*`一定對你的機構好。但在更改(這種風格)期間,和/或在招收新人時,的確可能會引起一些混亂。一些機構認為用`Fred const*`帶來的好處更大,其它機構則不這么認為。 另一個警告:如果決定用`Fred const*`,確保采取措施使人們不會誤寫成語義不同但語法相似的“`Fred* const x`”。這兩者雖然第一眼看上去非常相似,但含義完全不同。 ## 18.10 什么是“`const`成員函數”? 是指僅查看(而不改變)對象的成員函數。 `const`成員函數會在緊跟函數參數列表的后面跟一個`const`關鍵字。有`const`后綴的成員函數被稱作“`const`成員函數”或者是“查看函數”(inspector)。沒有`const`后綴的成員函數被稱作“非`const`函數”或“變更函數”(mutator)。 ``` ?class?Fred?{ ?public: ???void?inspect()?const;???//?該成員保證不修改*this ???void?mutate();??????????//?該成員可能會修改*this ?}; ?void?userCode(Fred&?changeable,?const?Fred&?unchangeable) ?{ ???changeable.inspect();???//?正確:沒有修改一個可修改對象 ???changeable.mutate();????//?正確:修改一個可修改對象 ???unchangeable.inspect();?//?正確:沒有修改一個不可修改對象。 ???unchangeable.mutate();??//?錯誤:試圖修改一個不可修改對象。 ?} ``` `unchangeable.mutate()`這個錯誤在編譯期被發現。`const`不會有運行時的時空效率損失。 在`inspect()`成員函數后面的`const`后綴表示不會改變對象的(調用方可見的)_抽象_狀態。這并非保證不改變對象的“底層二進制位”。C++編譯器不允許將其解釋為“按位”(不變),除非能解決別名問題,而別名問題一般無法解決(即可能存在會修改對象狀態的非`const`別名)。另外一個對這種別名問題的(重要)認識是:用一根“指向`const`對象的指針”并不能保證對象不改變,它只是保證對象不會_通過該指針_被改變。 ## 18.11 返回引用的成員函數和`const`成員函數之間有什么聯系? 如果想要從一個查看函數中返回`this`對象的引用,那么應該返回指向`cosnst`對象的引用,即`const X&`。 ``` ?class?Person?{ ?public: ???const?std::string&?name_good()?const;??←?正確:調用者不能修改name ???std::string&?name_evil()?const;????????←?錯誤:調用者能夠修改name... ?}; ?void?myCode(const?Person&?p)??←?這里保證不會修改Person?對象... ?{ ???p.name_evil()?=?"Igor";?????←?...但還是修改了!! ?} ``` 好消息是當你犯這種錯誤時,編譯器_通常_能夠發現。尤其是如果不小心返回了`this`對象的非`const`引用,例如上面的`Person::name_evil()`,編譯器在編譯這個成員函數時,_通常_能夠發現并給出一條編譯錯誤。 壞消息是編譯器并不能發現所有這種錯誤:在一些情況下編譯器無法產生一條錯誤消息。 最后:你需要思考,并記住本FAQ所述的原則。如果你通過引用返回的對象在_邏輯上_是`this`對象的一部分,而不管其是否在物理上放在了`this` 對象內,那么`const`方法應該返回`const`引用或直接按值返回。(`this`對象的“邏輯”部分與對象的“抽象狀態”相關。請參閱前一個FAQ。) ## 18.12 “`const`重載”是做什么用的? 當一個查看函數和一個變更函數名字相同,且參數個數與類型也相同時就有用了——即兩者的不同之處僅在于一個有`const`另一個沒有`const`。 `const`重載的一個常見應用是下標運算符。通常應該盡量使用標準模板容器,例如`std::vector`,但有時會需要在自己的類中支持下標運算符。一個經驗法則是:**下標運算符通常成對出現**。 ``` ?class?Fred?{?...?}; ?class?MyFredList?{ ?public: ???const?Fred&?operator[]?(unsigned?index)?const;??←下標運算符通常成對出現 ???Fred&???????operator[]?(unsigned?index);????????←下標運算符通常成對出現... ?}; ``` 當對一個非`const`的`MyFredList`對象使用下標運算符時,編譯器會調用非`const`的下表運算符。因為返回的是一個普通`Fred&`,所以能夠查看或修改對應的`Fred`對象。例如,假設`Fred`類有一個查看函數`Fred::inspect() const`和一個變更函數`Fred::mutate()`: ``` ?void?f(MyFredList&?a)??←?MyFredList是非const的 ?{ ???//?可以調用不修改a[3]處Fred對象的方法: ???Fred?x?=?a[3]; ???a[3].inspect(); ???//?可以調用修改a[3]處Fred對象的方法: ???Fred?y; ???a[3]?=?y; ???a[3].mutate(); ?} ``` 但是,當對一個`const`的`MyFredList`對象使用下標運算符時,編譯器會調用`const`的下標運算符。因為會返回`const Fred&`,所以可以查看對應的`Fred`對象而不能修改它。 ``` ?void?f(const?MyFredList&?a)??←?MyFredList是const的 ?{ ???//?可以調用不修改a[3]處Fred對象的方法: ???Fred?x?=?a[3]; ???a[3].inspect(); ???//?錯誤(很幸運!):試圖改變a[3]出的Fred對象: ???Fred?y; ???a[3]?=?y;???????←?幸運的是編譯器在編譯時發現了這個錯誤。 ???a[3].mutate();??←?幸運的是編譯器在編譯時發現了這個錯誤。 ?} ``` 在以下FAQ中演示了針對下標運算符和函數調用運算符的`const`重載:[13.10], [16.17], [16.18], [16.19]和[35.2] 當然除了下標運算符,其它函數也可以進行`const`重載。 ## 18.13 如果我想讓一個`const`成員函數對數據成員做“不可見”的修改,應該怎么辦? 用`mutable`(或者實在沒辦法了,用最后一招`const_cast`) 少數查看函數需要對數據成員做適當的修改(例如,一個`Set`對象可能想要緩存上一次查看的內容,以便下一次查看時能夠提高性能)。這里“適當”的意思是,所做的修改不會從對象的接口上反映到外部(否則該成員函數就應該是一個變更函數,而不是查看函數了)。 這時,需要修改的數據成員應標記為`mutable`(把`mutable`關鍵字放在數據成員的聲明前;即和`const`的位置一樣)。這就通知編譯器說這個數據成員允許在`const`成員函數中被修改。如果編譯器不支持`mutable`關鍵字,那么可以通過`const_cast`去除掉`this`的`const`(但是記著讀下面的**注意事項**)。例如在`Set::lookup() const`中,可以這么寫: ``` ?Set*?self?=?const_cast<Set*>(this); ???//?在這么做之前,記著讀下面的**注意事項** ``` 然后,`self`和`this`內容一樣(即`self == this`為真),但`self`類型是`Set*`而不是`const Set*`(技術上來講,是`const Set* const`,不過最右邊的`const`與這里的問題無關)。因此可以使用`self`來修改`this`所指向的對象。 **注意:**`const_cast`可能會導致一種非常罕見的錯誤。這個錯誤僅在三件很少見的事情同時發生時出現:數據成員本應該是`mutable`(例如上面所說的),編譯器不支持`mutable`,并且對象原本就定義為`const`(不是通過一根指向`const`對象的指針來訪問的普通`const`對象)。雖然這種組合非常罕見,甚至永遠不會發生,但如果真的發生了,那么這種代碼可能就不能正常運行(標準說這種行為是未定義的)。 如果想要用`const_cast`,那么應該用`mutable`替代。換句話說,如果需要修改一個對象的成員,而又是通過指向`const`對象的指針來訪問這個對象,那么最安全和最簡單的做法就是給該成員的聲明前加上`mutable`。如果你確信實際對象不是`const`的(例如能夠確定對象是像這樣聲明的:`Set s;`),那么也可以用`const_cast`。但如果對象本身就是`const`的(例如可能聲明為:`const Set s;`),那么就應該用`mutable`而不是`const_cast`。 請不要告訴我說Y編譯器的X版本在Z機器上允許修改`const`對象的非`mutable`成員。我不管這個——根據標準這是錯誤的,如果換一個編譯器,甚至是同一編譯器的不同版本(升級版),你的代碼就可能會失敗。不要這么做。用`mutable`吧。 ## 18.14 `const_cast`會導致無法優化么? 在理論上是的;在實際中不會。 即使語言本身禁止了`const_cast`,要想在調用`const`成員函數時避免讀寫寄存器的唯一辦法是解決別名問題(即要證明沒有其它指向該對象的非`const`指針)。這只有在極少數情況下才能辦到(當在調用`const`成員函數時構造對象,所有在構造對象和調用`const`成員函數之間調用的非`const`成員函數是靜態綁定的,并且所有這些調用包括構造函數都是內聯的,同時構造函數調用的任何成員函數也要是內聯的)。 ## 18.15 當我用`const int*`指向一個`int`后,為什么編譯器還允許我修改這個`int`? 因為“`const int* p`”意思是“`p`保證不會修改`*p`”,而_不是說_“`*p`保證不變”。 用`const int*`指向一個`int`,不會使這個`int`變為`const`。`int`不會通過`const int*`被修改,但如果有另外一個`int*`(注意沒有`const`)指向該`int`(這就是“別名”),那么這個`int*`指針可以用來修改`int`。例如: ``` ?void?f(const?int*?p1,?int*?p2) ?{ ???int?i?=?*p1;?????????//?獲得*p1的(原始)值*p1 ???*p2?=?7;?????????????//?如果p1 == p2,這就會修改*p1。 ???int?j?=?*p1;?????????//?獲取*p1(可能是更新后)的值。 ???if?(i?!=?j)?{ ?????std::cout?<<?"*p1?changed,?but?it?didn't?change?via?pointer?p1!\n"; ?????assert(p1?==?p2);??//?這是*p1可能會變化的唯一辦法。 ???} ?} ?int?main() ?{ ???int?x?=?5; ???f(&x,?&x);???????????//?這完全合法(甚至是合情理的!)... ?} ``` 注意`main()`和`f(const int*, int*)`可能是在不同的編譯單元中,并且不是在同一天編譯的。因此,編譯器就無法在編譯時發現別名。因此無法在語言中禁止這種事情。實際上,我們甚至不想添加這樣一個規則。因為一般來說,允許很多指針指向同一個對象,這是一個功能。當指針保證說不去修改所指內容時,這只是該_指針_作出的保證,而_不是_內容所做的保證。 ## 18.16 “`const Fred* p`”的意思是`*p`不會改變么? 不是!(這個與int指針的別名問題相關) “`const Fred* p`”意思是不能通過指針`p`來修改`Fred`,但有可能不經過`const`(例如一個非`const`指針`Fred*`),而是通過其它途徑來訪問`object`。例如,如果有兩根指針“`const Fred* p`”和“`Fred* q`”都指向同一個`Fred`對象(別名),那么指針`q`可以用來修改`Fred`對象,但指針`p`不能。 ``` ?class?Fred?{ ?public: ???void?inspect()?const;???//?const成員函數 ???void?mutate();??????????//?非const成員函數 ?}; ?int?main() ?{ ???Fred?f; ???const?Fred*?p?=?&f; ?????????Fred*?q?=?&f; ???p->inspect();????//?可以:不改變*p_ ???p->mutate();?????//?錯誤:不能通過p來修改*p ???q->inspect();????//?可以:允許使用q來查看對象 ???q->mutate();?????//?可以:允許使用q來修改對象 ???f.inspect();?????//?可以:允許使用f來查看對象 ???f.mutate();??????_//?可以:允許使用f來修改對象... ? ``` ## 18.17 當把`Foo**`轉換成`const Foo**`時為什么會出錯? 因為把`Foo**`轉換成`const Foo**`是非法且危險的。 C++允許`Foo*`到`const Foo*`的轉換(這是安全的)。但如果想要將`Foo**`隱式轉換成`const Foo**`則會報錯。 這么做的原因如下所示。但首先,這里有個最普通的解決辦法:只要把`const Foo**`改成`const Foo* const*`就可以了。 ``` ?class?Foo?{?/*?...?*/?}; ?void?f(const?Foo**?p); ?void?g(const?Foo*?const*?p); ?int?main() ?{ ???Foo**?p?=?/*...*/; ???... ???f(p);??//?錯誤:將Foo**轉換成const Foo**是非法且邪惡的 ???g(p);??//?可以:將Foo**轉換成const Foo* const*是合法且合理的... ?} ``` 之所以`Foo**`到`const Foo**`的轉換是危險的,是因為這會使你沒有經過轉換就在不經意間修改了`const Foo`對象。 ``` ?class?Foo?{ ?public: ???void?modify();??//?修改this對象 ?}; ?int?main() ?{ ???const?Foo?x; ???Foo*?p; ???const?Foo**?q?=?&p;??//?這時q指向p;(幸虧)這是個錯誤。 ???*q?=?&x;?????????????//?這時p指向x ???p->modify();?????????//?啊:修改了const Foo!!... ?} ``` 記住:_請不要_用指針轉換繞過這里。別這么做就是了!
                  <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>

                              哎呀哎呀视频在线观看