1. 程式人生 > >C++類成員變數是為什麼選擇private?

C++類成員變數是為什麼選擇private?

      首先說明下,在c++標準中,類成員變數可以有3個關鍵詞,public,protected,private,分別對應公有成員,保護成員,私有成員        換句話說,c++標準是完全允許使用公有成員的,所以我討論的是我個人不用protected或public成員的原因,並不代表你(或其他人)不可以用       事實上,我絕對不會反對你將類成員設為公有,因為那和我沒關係。。 回到正題
~~~~~~~~~~第一步~~~~~~~~~~ 對於一個類的定義
class Human_protected{
protected:
  double Age;
  std::string nm;
public:
  void GetName(std::string NewName){nm = NewName;}
};
他還有另一個“寫法”
class Human_private
{
  private:
    double Age;
    std::string nm;
  public:
    std::string GetName()const{return nm;}
    double GetAge()const{return Age;}
    void GetName(std::string NewName){nm = NewName}; 
};
          這兩個類將會作為基類,而他們類成員的值需要在繼承類中使用,於是一個類將他的成員設為protected,以方便繼承他的類的使用,而另一個類更側重封裝,將類成員設為私有,如需使用他們的值則需要呼叫類成員函式
        先看下兩者的區別,以便讓你有充足的理由選擇其中的某一個
      目前為止雙方的優勢為
class Human_protected
    1 少打了不少字母,同時少定義兩個成員函式 class Human_private
     暫無。。。      目前看來,class Human_protected暫時優勢,所以你選擇了他,也就是選擇將成員變數設為protected Let's Continue.. ~~~~~~~~~~~第二步~~~~~~~~~~
         在c++中,繼承是個很常用的概念,對於第一步中的兩個Human類,通過繼承來建立一個Worker類是個不錯的主意
class Worker_protected:public Human_protected
{
  private:
    double Pay;  //工資
    std::string TechType;  //技術工種名稱
  public:
    void ShowInfo()const;
};
他的另一種“寫法”其實完全一樣
class Worker_private:public Human_private
{
  private:
    double Pay;
    std::string TechTypeSchool;
  public:
    void ShowInfo()const;
};

他們的不同在於成員函式ShowInfo()函式,他們的程式碼有所不同
void Worker_protected::ShowInfo()const
{
  std::cout<<"Name:"<<nm<<std::endl;
  std::cout<<"Age:"<<Age<<std::endl;
  std::cout<<"Technology Name :"<<TechType<<std::endl;
}
//----------------------
void Student_private::ShowInfo()const
{
  std::cout<<"Name:"<<GetName()<<std::endl;  //區別
  std::cout<<"Age:"<<GetAge()<<std::endl;  //區別
  std::cout<<"Technology Name :"<<TechType<<std::endl;
}
       這裡的區別在於,對於protected成員nm和Age,繼承他的類可以直接在成員函式中呼叫,而對於私有成員,繼承他的類要使用他們的值必須使用基類的成員函式
     我們再次統計兩個繼承類的優劣勢
Human_protected
     1成員函式中不需要呼叫基類的成員函式就可以直接使用基類的成員變數,節約的呼叫基類成員函式的開支,同時再次少打了一些字母。
       PS:不要否認少打字母不是優勢,這有時候會讓人感到厭煩甚至出錯(這也是C++11引入關鍵字auto的原因之一,跑題了。。。)
Human_private
暫無 這裡protected再次取勝
不過實際的軟體設計往往會比較複雜,所以我們繼續 ~~~~~~~~~~第三步~~~~~~~~~~
         對於一個表示工人(worker)的類來說,他需要的功能遠遠不止顯示資訊(ShowInfo()函式)怎麼多,我們需要加入更多的函式來完善這個類
void GetInfo(const std::string& NM , int ages , const std::string& techtype) //錄入資訊
void CountPay(int Hours);  //計算工資,小時數*工資數 +工種工資,不同的共種待遇不同
void ShowPay()const; //顯示格式 (張三 :2000元)
void ShowHoliday(int Hours)const; //顯示格式 (李四:已用年假5小時)
//。
//。
//。
//。
            以上成員函式實現Worker類的功能,包括錄入資訊,計算,顯示工人的工資,顯示他已經請的年假,等等,這些函式都除CountPay()外,都使用到了基類的成員變數nm(姓名).
          當然這些對於一個Worker類任然不夠,我們假設還有其他10個成員函式(其實還是不夠,但已經足夠說明我們今天需要討論的問題)來實現Worker類
          這些函式都有一個共同特點,他們需要使用到基類的成員變數nm,呼叫方式第二步已經說明了           現在protected和private的優劣勢越發明顯,隨著繼承類(需要使用基類成員變數的)成員函式增多,private需要呼叫跟多次的函式,寫更多的字母。。          似乎勝負已分,那讓我們再次繼續 ~~~~~~~~~~~第四步~~~~~~~~~~~~           軟體設計中途遇到設計上的變化是很稀鬆平常的事,一個軟體在設計週期內經常需要改動其設計,而這次促使你修改軟體的原因是你的老闆跑過來找你,要求把姓名改用編號
         老闆這麼要求的原因有兩個
1.很多員工抱怨顯示名字有侵犯隱私的嫌疑
2.工人的姓名有重複的,財務根據姓名支付薪水的時候把錢打進了錯誤的人的賬號裡(啊,這真是最令人愉快的事,當然,前提是那是我的銀行賬號)
           據此,你的老闆要求你用一個六位編號(數字)來代替姓名,因為每個工廠裡的每個工人入職是都會分配一個唯一的六位編號,不用擔心重複以及隱私的問題
          另外你的老闆還有個很重要的要求,這個編號必須為int型別,因為這個編號將來可能會用於計算或者和其他系統的對接(的確,未雨綢繆通常是個不錯的選擇)           對於Worker_private類來說,你可以把基類Human_private中的成員變數std::string nm直接註釋掉,然後增加一個int Number,在GetName(std::string NewName)中把引數型別改為int,即GetName(int NewName)
        然後把函式std::string GetName()const{return nm;}改為
std::string GetName()const
{
  //將int轉化未std::string的程式碼,這樣的程式碼相信難不倒你吧
  return Number
}
就可以了,你根本不用考慮繼承他的類,繼承他的類都呼叫GetName()函式獲得一個string,繼承他的所有類(包含所有的子子孫孫)都不要為此做任何修改
PS:其實這樣做有一個隱患,等會我在告訴你。             然後我們來看Human_protected類,他同樣需要修改,你第一個想到的辦法可能是和上面Human_private類一樣,註釋掉std::string nm成員,然後增加一個int Number類成員,但你是一名嚴謹的程式設計師(是的,我相信你),未了程式的安全可靠性,你在做這樣的修改後把這個程式其他的程式碼全部都檢查了一遍,這樣做的原因在於,子類是直接使用nm變數的,所以如果子類中有類似
std::cout<<nm;
std::string Info = nm + TechType; 這樣的程式碼,直接刪除類成員變數nm,就會導致程式崩潰,所以你檢查整個程式也就理所當然了
        當然,你也可以有另一種該法,即把std::string nm 直接改成int nm;這樣變數名不變對於類似std::cout<<nm的程式碼,他可以繼續正常執行(有原來的輸出string變成輸出int),但對於
std::string Info = nm + TechType; 這樣的程式碼,,直接修改變數型別依然會出錯。
            所有的情況,最後的解決辦法是一樣的,那就是你必須檢查所有繼承類的程式碼,以確保修改基類後,這些繼承類任然能正確的執行他們的功能
           結論:對於類成員未protected的基類的類成員的修改結果就是你必須檢查整個程式以確保修改後的正確性,當然對於我現在寫的例子來說這沒什麼,畢竟這隻有幾行程式碼,但如果你的專案比較大呢?
現在你有沒有隱隱約約的意識到一些問題?(背後涼颼颼了。。) ~~~~~~~~~~第五步~~~~~~~~~~~~             我們繼續設計軟體,實際工作中的軟體設計規模往往比較大,而聰明的你滿腹才華(是的,我相信你),你不再滿足在一家工廠拿著3K的月薪,為一個老闆編寫工人管理系統,你覺得自立門戶自主創業,經過幾年的努力,你已經是一位軟體公司的BOSS,擁有500個員工,現在你接到一筆價值一億美元的軟體生意(這其實也算不上大生意),軟體是幫助客戶定做一套企業管理軟體,你和客戶商量花費三年實際來完成,兩年半時間完成,半年時間測試
            這個軟體擁有三千萬行的程式碼,大約包含了1500個類,其中有一個基類human類,他有一個成員變數std::string Name,整個程式有大約1000個類直接或間接繼承自這個類,大約有12000個類成員函式都
需要使用到這個類的成員變數std::string Name,為了少打一些字母,你的程式設計師把std::string Name設為protected
            兩年多過去了,你公司的軟體即將完成並準備展開為期半年的測試,這時候你的客戶打電話過來,要求把姓名改為用編號(一個七位數,型別必須是long long)直接原因是你客戶的直接競爭對手在慫恿工人去控告公司侵犯隱私(啊,你的客戶似乎攤上大事了),
          另外,軟體的起始設計開始於兩年前,硬體的變化也促使客戶要求改用編號(long long),想想日新月異的硬體更新吧,兩年多的時間足夠硬體發生翻天覆地的變化了。
          再者,公司人員的增加使得員工重名的機率增大,工資發錯人之類的現象不時出現
          由於std::string Name涉及了1000多個類和其中大約12000個成員函式,所以你要把這些全部逐一檢查,修改(必須怎麼做的原因就和第四步所說的一樣)。當然,你還必須在幾個月內完成軟體剩餘部分的同時完成這些工作(實時上需要修改的程式碼佔了已完成部分的大多數)。。。。。你嘗試在上一步中的方法,直接用替換,但是的程式設計師告訴你,無法保證修改後程式還能正確的執行,用為很多地方有std::string Name和另一個std::string相加的程式碼,但你的程式設計師很難準確的記住所有,畢竟人類的記憶很難記住如此龐大的程式碼。。
            現在你面臨著到時間不能完成而支付鉅額違約金的問題,或者你先發制人去控告客戶提出了合同外的要求,但眼下最重要的問題在於你的一億美元很有可能拿不到了,而這兩年公司的開支都依賴銀行貸款。。。。
回過來想想,相比於這些問題,最初將變數設為private,然後修改對應的成員函式(具體方法見第四步)是不是就不會遇到這些麻煩了,最後我們比較下多打幾個字母是不是會更容易些。。。。 ~~~~~~~~~第六步~~~~~~~~~~~
書接上回
           最終,你贏得了和你客戶的官司,由於你客戶提出了合同外的要求,所以你不需要支付違約金,但客戶顯然也不願意支付你一億美元的合同款,於是你和你的客戶再次陷入漫長的官司中,當然我們先撇開法律問題不談,眼下你最重要的是找到資金支付員工工資,維持公司的正常運營,這公司畢竟是你一手建立的心血。。。
          而你手裡有一個現成的C++類庫可以對外出售,畢竟你原來的客戶已經不願為這個程式支付一毛錢。。。
對於第三方的C++類庫,市場上有很多,影象庫Qt,微軟的MFC圖形庫,甚至Boost庫,你迫切的希望把你的類庫出售,讓儘可能多的人來購買你的拷貝
          為此,你為你的C++類庫寫了一份說明文件,這份文件和我們常見的文件有所不同,他比較厚。。。。
之所以比較厚,原因在於上面有一份關鍵詞列表,上面也許有五萬甚至十萬個關鍵詞,你告訴你(潛在的)客戶,你們使用我的類庫程式設計的時候,如果需要繼承類庫中的類(其實就是C++程式設計師很熟悉的程式碼重用)的時候,請背誦著數萬個關鍵詞,(數萬 == 類庫protected成員個數。。。)
           因為這些類成員是保護的,所以你在繼承的時候必須格外小心。。。當然你也許會提出,這種問題namespace是個很不錯的解決方案
         好吧,暫且是這樣,那客戶命名的問題我們先暫且放一下,稍後再談          在第四步裡,我增說過把std::string nm註釋掉,增加一個int Number這樣做有個隱患,比較正確的做法就是把std::string nu直接改成int nu,然後有
std::string GetName()const
{
  //將int轉化未std::string的程式碼
  return nm;
}
            事實上,在大型工程中往(protected)類中新增類成員是個很危險的行為,原因就在於你無法確定你新增的變數名是否已經在其他類中被使用。對於類成員未private的類來說,這個問題不大,僅僅是我上面說的一個隱患,因為你只要保證新加入的類成員名和該類的其他成員不一樣,不需要考慮其他的類,但如果你要新增一個protected屬性的類成員(而且其他相關的各種繼承類也有大量的protected類成員),你就會發現問題了,你如何知道繼承這個類,或者這個類繼承的其他類中沒有這樣一個變數呢?              好吧,讓我們把時光往回推一些(愛因斯坦大大被打我,我純粹是假設),你的公司設計的程式包含了1500個類,你當初開始設計第一個類的時候,你就要把類成員名稱放入一個namespace,然後告訴你們公司所有的500個程式設計師,告訴他們,
           “這個變數名已經被使用了,因為他是protected的,所以不要在繼承類中使用到這個變數名,你們記住,這樣的變數名大概有5-10萬個,以後將陸續加入這個namespace,你們一定要全記住了。。。千萬啊。。。。”
            如果你不是Boss而是一名員工,當你需要開發一個需要在腦海中記住數萬乃至跟多的變數名的程式的時候,有木有一種災難的感覺?
~~~~~~~~~第七步~~~~~~~~~~
           假設你的公司是一家極具韌性的公司,你的員工在你的帶領下客服了第六步我所述的困難,而且你也找到了一個優質客戶,他不建議程式設計的時候使用額外的名稱空間(namespace),現在你終於有了第一個客戶,一切想著好的方向在發展
你的類庫中的一個類,有類似程式碼
int dev; 這個表示某個硬體裝置,0-9表示該裝置的10種不同型號如果基類是protected,則有
class Pro
{
  protected:
    int dev;
}
當然,如果設為private的,則有必須使用一個專用的介面(成員函式)
class Pri
{
  public:
    int ShowDevictType(return dev;)const
}
         你的客戶在購買了你的類庫後使用(繼承)了這個類,他需要在這個了類的基礎上增加一些額外的功能
然而硬體的飛速發展使得人們很快不在使用這種裝置了,所以刪掉他是個不錯的主意          作為一家C++類庫的開發公司,保證類庫版本的跟新很重要,尤其是硬體飛速發展的今天。如果類成員是protected,你現在在刪除這個變數的時候面臨一個問題,你如何知道你的客戶是如何使用他的呢?客戶繼承了你的這個類,然後添加了一些他們公司所需要的程式碼,他的程式碼裡有很多直接使用int dev的程式碼,比如std::cout<<dev,或者其他相關dev的計算,如果你突然刪除了這個變數,那當你的客戶更新了類庫,然後你的客戶發現他以前的程式碼出現了BUG井噴。。。。
         也許你會提議,直接在類的建構函式裡使用dev = 1;來固定他的值,然後再文件中說明下情況(做法類似下面的private的方法),但如我前面所說,你永遠不可能確定你的客戶會做什麼?
          假如你的客戶繼承了你的類,然後再類裡有一行,dev = 12;的程式碼。。。。不要懷疑,這是很容易犯的錯誤。
        沒錯,繼承類可以直接使用基類的成員,dev的值被重置為12,但他的取值只能未0-9,超出了這個範圍,你的類庫中其他依賴這個值的程式碼就有可能出錯,你的客戶怎麼做了以後,當他再次使用類庫中其他的類,只要牽扯帶這個dev變數的類都會出現莫名的錯誤,於是你的客戶打電話過來,告訴你,你的類庫存在著嚴重的bug,並且要求退款。。。你會不會感到很無辜          而如果你的變數是private的,那問題就簡單多了,你不知道你的客戶將如何使用int dev的值,但你可以確定的是,你的客戶是呼叫ShowDeviceType()函式來獲得值的,所以當你的類庫需要更新刪除這個變數時
1 直接註釋掉 int dev;
2 將ShowDeviceType()函式裡的程式碼 return dev;改成return 1;  //數值只要是以前dev合法的值就可以
          沒錯啦,你和他一樣,也會在類庫其他地方的程式碼中使用到這個int dev的值,但都是呼叫ShowDeviceType()函式來獲取的,所以不用擔心刪掉變數會對其他類有什麼影響,這麼改後你的類庫中的程式碼以後可以慢慢的修改。        
           然後你在你更新類庫的文件加上這麼一句,“原先的介面(ShowDeviceType())已被宣告廢棄,但仍保留其介面(值固定為1)用作和以前的程式碼相容,但會在將來的某個時間移除。”          這句話是不是很熟悉?相信你以前看文件的時候,如果有版本升級,那肯定會見到類似的話,沒錯啦,其實這句話翻譯過來就是,”變數我刪了,你如果還用這個變數的話他是個固定的值,我程式碼裡原來用到的地方我以後會逐一修改,但目前沒時間,具體修改完成是在將來的某個時間。。。。“          呼~~~~經過這麼多步,我們回到最初的問題,你的類成員是選擇private還是protected,或者乾脆是public呢?