標(biāo)題: C++ const詳解 [打印本頁(yè)]

作者: 51黑tt    時(shí)間: 2016-3-6 13:12
標(biāo)題: C++ const詳解
const給人的第一印象就是定義常量。 (1)const用于定義常量。
      例如:const int N = 100;const int M = 200;
      這樣程序中只要用到 N、M 就分別代表為整型100、200,N、M 為一常量,在程序中不可改變。
      但有人說(shuō)他編程時(shí)從來(lái)不用const定義常量。我相信。但他是不懂得真正的編程藝術(shù),用const定義常量不僅能方便我們編程而且能提高程序的清晰性。你是愿意看到程序中100、200 滿天飛,還是愿意只看到簡(jiǎn)單清晰的N、M。相信有沒(méi)有好處你慢慢體會(huì)。
      還有人說(shuō)他不用const定義常量,他用#define宏定義常量?梢。但不知道你有沒(méi)有發(fā)現(xiàn)有時(shí)#define宏并沒(méi)有如你所愿在定義常量。下面我們比較比較const和#define。
      1。
      (a) const定義常量是有數(shù)據(jù)類型的:
      這樣const定義的常量編譯器可以對(duì)其進(jìn)行數(shù)據(jù)靜態(tài)類型安全檢查,而#define宏定義的常量卻只是進(jìn)行簡(jiǎn)單的字符替換,沒(méi)有類型安全檢查,且有時(shí)還會(huì)產(chǎn)生邊際效應(yīng)(不如你愿處)。所謂邊際效應(yīng)舉例如下:
            #define N 100
            #define M 200 + N
            當(dāng)程序中使用 M*N 時(shí),原本想要 100 * (200+ N )的卻變成了 100 * 200 + N。
      (b)#define宏定義常量卻沒(méi)有。#define <宏名><字符串>,字符串可以是常數(shù)、表達(dá)式、格式串等。在程序被編譯的時(shí)候,如果遇到宏名就喲內(nèi)指定的字符串進(jìn)行替換,然后再進(jìn)行編譯。
      2。
      有些調(diào)試程序可對(duì)const進(jìn)行調(diào)試,但不對(duì)#define進(jìn)行調(diào)試。
      3。
      當(dāng)定義局部變量時(shí),const作用域僅限于定義局部變量的函數(shù)體內(nèi)。但用#define時(shí)其作用域不僅限于定義局部變量的函數(shù)體內(nèi),而是從定義點(diǎn)到整個(gè)程序的結(jié)束點(diǎn)。但也可以用#undef取消其定義從而限定其作用域范圍。只用const定義常量,并不能起到其強(qiáng)大的作用。const還可修飾函數(shù)形式參數(shù)、返回值和類的成員函數(shù)等。從而提高函數(shù)的健壯性。因?yàn)閏onst修飾的東西能受到c/c++的靜態(tài)類型安全檢查機(jī)制的強(qiáng)制保護(hù),防止意外的修改。
(2)const修飾函數(shù)形式參數(shù)
      形式參數(shù)有輸入形式參數(shù)和輸出形式參數(shù)。參數(shù)用于輸出時(shí)不能加const修飾,那樣會(huì)使函數(shù)失去輸出功能。因?yàn)閏onst修飾的東西是不能改變的。
      const只能用于修飾輸入?yún)?shù)。
      談const只能用于修飾輸入?yún)?shù)之前先談?wù)凜++函數(shù)的三種傳遞方式。
      C++函數(shù)的三種傳遞方式為:值傳遞、指針傳遞和引用傳遞。簡(jiǎn)單舉例說(shuō)明之,詳細(xì)說(shuō)明請(qǐng)參考別的資料。
      值傳遞:
        void fun(int x){
              x += 5;        //修改的只是y在棧中copy x,x只是y的一個(gè)副本,在內(nèi)存中重新開(kāi)辟的一塊臨時(shí)空間把y的值 送給了x;這樣也增加了程序運(yùn)行的時(shí)間,降低了程序的效率。
        }
        void main(void){
              int y = 0;
              fun(y);
              cout<<\"y = \"<        }
      指針傳遞:
         void fun(int *x){
              *x += 5;       //修改的是指針x指向的內(nèi)存單元值
         }
         void main(void){
              int y = 0;
              fun(&y);
              cout<<<<\"y = \"<         }
       引用傳遞:
          void fun(int &x){
              x += 5;       //修改的是x引用的對(duì)象值 &x = y;
         }
         void main(void){
              int y = 0;
              fun(y);
              cout<<<<\"y = \"<         }
       看了傳遞方式后我們繼續(xù)來(lái)談“const只能用于修飾輸入?yún)?shù)”的情況。
          當(dāng)輸入?yún)?shù)用“值傳遞”方式時(shí),我們不需要加const修飾,因?yàn)橛弥祩鬟f時(shí),函數(shù)將自動(dòng)用實(shí)際參數(shù)的拷貝初始化形式參數(shù),當(dāng)在函數(shù)體內(nèi)改變形式參數(shù)時(shí),改變的也只是棧上的拷貝而不是實(shí)際參數(shù)。
          但要注意的是,當(dāng)輸入?yún)?shù)為ADT/UDT(用戶自定義類型和抽象數(shù)據(jù)類型)時(shí),應(yīng)該將“值傳遞”改為“const &傳遞”,目的可以提高效率。
          例如:
             void fun(A a);//效率底。函數(shù)體內(nèi)產(chǎn)生A類型的臨時(shí)對(duì)象用于復(fù)制參數(shù) a,但是臨時(shí)對(duì)象的
                           //構(gòu)造、復(fù)制、析構(gòu)過(guò)程都將消耗時(shí)間。
             void fun(A const &a);//提高效率。用“引用傳遞”不需要產(chǎn)生臨時(shí)對(duì)象,省了臨時(shí)對(duì)象的
                                  //構(gòu)造、復(fù)制、析構(gòu)過(guò)程消耗的時(shí)間。但光用引用有可能改變a,所以加const

          當(dāng)輸入?yún)?shù)用“指針傳遞”方式時(shí),加const修飾可防止意外修改指針指向的內(nèi)存單元,起到保護(hù)作用。
          例如:
             void funstrcopy(char *strdest,const char *strsrc)//任何改變strsrc指向的內(nèi)存單元,
                                                              //編譯器都將報(bào)錯(cuò)
             些時(shí)保護(hù)了指針的內(nèi)存單元,也可以保護(hù)指針本身,防止其地址改變。
          例如:
            void funstrcopy(char *strdest,const char *const strsrc)
(3)const修飾函數(shù)的返回值
      如給“指針傳遞”的函數(shù)返回值加const,則返回值不能被直接修改,且該返回值只能被賦值給加const修飾的同類型指針。
      例如:
         const char *GetChar(void){};
       賦值 char *ch = GetChar();//錯(cuò)誤     const char *ch = GetChar();//正確
(4)const修飾類的成員函數(shù)(函數(shù)定義體)
      任何不會(huì)修改數(shù)據(jù)成員的函數(shù)都應(yīng)用const修飾,這樣當(dāng)不小心修改了數(shù)據(jù)成員或調(diào)用了非const成員函數(shù)時(shí),編譯器都會(huì)報(bào)錯(cuò)。
      const修飾類的成員函數(shù)形式為:int GetCount(void)   const;
(5)用傳引用給const取代傳值
缺省情況下,C++ 以傳值方式將對(duì)象傳入或傳出函數(shù)(這是一個(gè)從 C 繼承來(lái)的特性)。除非你特別指定其它方式,否則函數(shù)的參數(shù)就會(huì)以實(shí)際參數(shù)(actual argument)的拷貝進(jìn)行初始化,而函數(shù)的調(diào)用者會(huì)收到函數(shù)返回值的一個(gè)拷貝。這個(gè)拷貝由對(duì)象的拷貝構(gòu)造函數(shù)生成。這就使得傳值(pass-by-value)成為一個(gè)代價(jià)不菲的操作。例如,考慮下面這個(gè)類層級(jí)結(jié)構(gòu):
class Person {
 public:
  Person(); // parameters omitted for simplicity
  virtual ~Person(); // see Item 7 for why this is virtual
  ...
 private:
  std::string name;
  std::string address;
};
class Student: public Person {
 public:
  Student(); // parameters again omitted
  ~Student();
  ...
 private:
  std::string schoolName;
  std::string schoolAddress;
};
  現(xiàn)在,考慮以下代碼,在此我們調(diào)用一個(gè)函數(shù)—— validateStudent,它得到一個(gè) Student 參數(shù)(以傳值的方式),并返回它是否驗(yàn)證有效的結(jié)果:
bool validateStudent(Student s); // function taking a Student
// by value
Student plato; // Plato studied under Socrates
bool platoIsOK = validateStudent(plato); // call the function
  當(dāng)這個(gè)函數(shù)被調(diào)用時(shí)會(huì)發(fā)生什么呢?
  很明顯,Student 的拷貝構(gòu)造函數(shù)被調(diào)用,用 plato 來(lái)初始化參數(shù) s。同樣明顯的是,當(dāng) validateStudent 返回時(shí),s 就會(huì)被銷毀。所以這個(gè)函數(shù)的參數(shù)傳遞代價(jià)是一次 Student 的拷貝構(gòu)造函數(shù)的調(diào)用和一次 Student 的析構(gòu)函數(shù)的調(diào)用。
  但這還不是全部。一個(gè) Student 對(duì)象內(nèi)部包含兩個(gè) string 對(duì)象,所以每次你構(gòu)造一個(gè) Student 對(duì)象的時(shí)候,你也必須構(gòu)造兩個(gè) string 對(duì)象。一個(gè) Student 對(duì)象還要從一個(gè) Person 對(duì)象繼承,所以每次你構(gòu)造一個(gè) Student 對(duì)象的時(shí)候,你也必須構(gòu)造一個(gè) Person 對(duì)象。一個(gè) Person 對(duì)象內(nèi)部又包含兩個(gè)額外的 string 對(duì)象,所以每個(gè) Person 的構(gòu)造也承擔(dān)著另外兩個(gè) string 的構(gòu)造。最終,以傳值方式傳遞一個(gè) Student 對(duì)象的后果就是引起一次 Student 的拷貝構(gòu)造函數(shù)的調(diào)用,一次 Person 的拷貝構(gòu)造函數(shù)的調(diào)用,以及四次 string 的拷貝構(gòu)造函數(shù)調(diào)用。當(dāng) Student 對(duì)象的拷貝被銷毀時(shí),每一個(gè)構(gòu)造函數(shù)的調(diào)用都對(duì)應(yīng)一個(gè)析構(gòu)函數(shù)的調(diào)用,所以以傳值方式傳遞一個(gè) Student 的全部代價(jià)是六個(gè)構(gòu)造函數(shù)和六個(gè)析構(gòu)函數(shù)!
  好了,這是正確的和值得的行為。畢竟,你希望你的全部對(duì)象都得到可靠的初始化和銷毀。盡管如此,如果有一種辦法可以繞過(guò)所有這些構(gòu)造和析構(gòu)過(guò)程,應(yīng)該變得更好,這就是:傳引用給 const(pass by reference-to-const):
bool validateStudent(const Student& s);
  這樣做非常有效:沒(méi)有任何構(gòu)造函數(shù)和析構(gòu)函數(shù)被調(diào)用,因?yàn)闆](méi)有新的對(duì)象被構(gòu)造。被修改的參數(shù)聲明中的 const 是非常重要的。 validateStudent 的最初版本接受一個(gè) Student 值參數(shù),所以調(diào)用者知道它們屏蔽了函數(shù)對(duì)它們傳入的 Student 的任何可能的改變;validateStudent 也只能改變它的一個(gè)拷貝,F(xiàn)在 Student 以引用方式傳遞,同時(shí)將它聲明為 const 是必要的,否則調(diào)用者必然擔(dān)心 validateStudent 改變了它們傳入的 Student。
  以傳引用方式傳遞參數(shù)還可以避免切斷問(wèn)題(slicing problem)。當(dāng)一個(gè)派生類對(duì)象作為一個(gè)基類對(duì)象被傳遞(傳值方式),基類的拷貝構(gòu)造函數(shù)被調(diào)用,而那些使得對(duì)象的行為像一個(gè)派生類對(duì)象的特殊特性被“切斷”了。你只剩下一個(gè)純粹的基類對(duì)象——這沒(méi)什么可吃驚的,因?yàn)槭且粋(gè)基類的構(gòu)造函數(shù)創(chuàng)建了它。這幾乎絕不是你希望的。例如,假設(shè)你在一組實(shí)現(xiàn)一個(gè)圖形窗口系統(tǒng)的類上工作:
class Window {
 public:
  ...
  std::string name() const; // return name of window
  virtual void display() const; // draw window and contents
};
class WindowWithScrollBars: public Window {
 public:
  ...
  virtual void display() const;
};
  所有 Window 對(duì)象都有一個(gè)名字,你能通過(guò) name 函數(shù)得到它,而且所有的窗口都可以顯示,你可一個(gè)通過(guò)調(diào)用 display 函數(shù)來(lái)做到這一點(diǎn)。display 為 virtual 的事實(shí)清楚地告訴你:一個(gè)純粹的基類的 Window 對(duì)象的顯示方法有可能不同于專門的 WindowWithScrollBars 對(duì)象的顯示方法。
  現(xiàn)在,假設(shè)你想寫(xiě)一個(gè)函數(shù)打印出一個(gè)窗口的名字,并隨后顯示這個(gè)窗口。以下這個(gè)函數(shù)的寫(xiě)法是錯(cuò)誤的:
void printNameAndDisplay(Window w) // incorrect! parameter
{
 // may be sliced!
 std::cout << w.name();
 w.display();
}
  考慮當(dāng)你用一個(gè) WindowWithScrollBars 對(duì)象調(diào)用這個(gè)函數(shù)時(shí)會(huì)發(fā)生什么:
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
  參數(shù) w 將被作為一個(gè) Window 對(duì)象構(gòu)造——它是被傳值的,記得嗎?而且使 wwsb 表現(xiàn)得像一個(gè) WindowWithScrollBars 對(duì)象的特殊信息都被切斷了。在 printNameAndDisplay 中,全然不顧傳遞給函數(shù)的那個(gè)對(duì)象的類型,w 將始終表現(xiàn)得像一個(gè) Window 類的對(duì)象(因?yàn)樗褪且粋(gè) Window 類的對(duì)象)。特別是,在 printNameAndDisplay 中調(diào)用 display 將總是調(diào)用 Window::display,絕不會(huì)是 WindowWithScrollBars::display。
  繞過(guò)切斷問(wèn)題的方法就是以傳引用給 const 的方式傳遞 w:
void printNameAndDisplay(const Window& w) // fine, parameter won’t
{
 // be sliced
 std::cout << w.name();
 w.display();
}
  現(xiàn)在 w 將表現(xiàn)得像實(shí)際傳入的那種窗口。
  如果你掀開(kāi)編譯器的蓋頭偷看一下,你會(huì)發(fā)現(xiàn)用指針實(shí)現(xiàn)引用是非常典型的做法,所以以引用傳遞某物實(shí)際上通常意味著傳遞一個(gè)指針。由此可以得出結(jié)論,如果你有一個(gè)內(nèi)建類型的對(duì)象(例如,一個(gè) int),以傳值方式傳遞它常常比傳引用方式更高效。那么,對(duì)于內(nèi)建類型,當(dāng)你需要在傳值和傳引用給 const 之間做一個(gè)選擇時(shí),沒(méi)有道理不選擇傳值。同樣的建議也適用于 STL 中的迭代器(iterators)和函數(shù)對(duì)象(function objects),因?yàn),作為慣例,它們就是為傳值設(shè)計(jì)的。迭代器(iterators)和函數(shù)對(duì)象(function objects)的實(shí)現(xiàn)有責(zé)任保證拷貝的高效并且不受切斷問(wèn)題的影響。(這是一個(gè)“規(guī)則如何變化,依賴于你使用 C++ 的哪一個(gè)部分”的實(shí)例。)
  內(nèi)建類型很小,所以有人就斷定所有的小類型都是傳值的上等候選者,即使它們是用戶定義的。這樣的推論是不可靠的。僅僅因?yàn)橐粋(gè)對(duì)象小,并不意味著調(diào)用它的拷貝構(gòu)造函數(shù)就是廉價(jià)的。很多對(duì)象——大多數(shù) STL 容器也在其中——容納的和指針一樣,但是拷貝這樣的對(duì)象必須同時(shí)拷貝它們指向的每一樣?xùn)|西。那可能是非常昂貴的。
  即使當(dāng)一個(gè)小對(duì)象有一個(gè)廉價(jià)的拷貝構(gòu)造函數(shù),也會(huì)存在性能問(wèn)題。一些編譯器對(duì)內(nèi)建類型和用戶定義類型并不一視同仁,即使他們有同樣的底層表示。例如,一些編譯器拒絕將僅由一個(gè) double 組成的對(duì)象放入一個(gè)寄存器中,即使在常規(guī)上它們非常愿意將一個(gè)純粹的 double 放入那里。如果發(fā)生了這種事情,你以傳引用方式傳遞這樣的對(duì)象更好一些,因?yàn)榫幾g器理所當(dāng)然會(huì)將一個(gè)指針(引用的實(shí)現(xiàn))放入寄存器。
  小的用戶定義類型不一定是傳值的上等候選者的另一個(gè)原因是:作為用戶定義類型,它的大小常常變化。一個(gè)現(xiàn)在較小的類型在將來(lái)版本中可能變得更大,因?yàn)樗膬?nèi)部實(shí)現(xiàn)可能會(huì)變化。甚至當(dāng)你換了一個(gè)不同的 C++ 實(shí)現(xiàn)時(shí),事情都可能會(huì)變化。例如,就在我這樣寫(xiě)的時(shí)候,一些標(biāo)準(zhǔn)庫(kù)的 string 類型的實(shí)現(xiàn)的大小就是另外一些實(shí)現(xiàn)的七倍。
  通常情況下,你能合理地假設(shè)傳值廉價(jià)的類型僅有內(nèi)建類型及 STL 中的迭代器和函數(shù)對(duì)象類型。對(duì)其他任何類型,請(qǐng)遵循本 Item 的建議,并用傳引用給 const 取代傳值。
  Things to Remember
  ·用傳引用給 const 取代傳值。典型情況下它更高效而且可以避免切斷問(wèn)題。
  ·這條規(guī)則并不適用于內(nèi)建類型及 STL 中的迭代器和函數(shù)對(duì)象類型。對(duì)于它們,傳值通常更合適。
   
只在總結(jié),也許不夠?qū)I(yè),不夠全面,請(qǐng)大家指教。






歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1