你好,C++(35)類是如何藏私房錢的?6.2.4 拷貝構造函數

6.2.6  類成員的訪問控制

類成員包括類的成員變量和成員函數,它們分別用來描述類的屬性和行爲。而類成員的訪問控制決定了哪些成員是公開的,能夠被外界訪問,也能夠被自身訪問;哪些成員是私有的,只能在類的內部訪問,外界沒法訪問。就像一我的的錢包,只有他本身能動,別人是不能動的。又如同本身藏的私房錢也只有本身知道,對其餘人而言,私房錢是徹底隱藏的。安全

你們可能會問,爲何要對類成員的訪問加以控制,大公無私地誰均可以訪問不是挺好的嗎?這是由於在現實世界中,人們對事物的訪問是受到控制的,咱們能夠訪問到某些事物,但不可能訪問到任何事物。就如同咱們只能知道本身錢包裏有多少錢,而沒法知作別人錢包裏有多少錢同樣。這一點反映到C++中,就成了類成員的訪問控制。能夠設想這樣的情形:錢包裏的錢只能由本身訪問,別人是無權訪問的。當它成爲「人」這個類的一個屬性,用某個成員變量來表示後,其訪問天然也應該受到控制,只能由「人」這個類自身的行爲(成員函數)來訪問,對於外界其餘函數而言,這個成員變量就被隱藏起來是不可見的,天然也就沒法訪問。若是不對訪問加以控制,誰均可以訪問,那就頗有可能其餘函數(小偷)會錯誤地修改這個本不應它修改的數據,數據安全沒法獲得保證。換句話說,要想讓咱們錢包裏的錢安安穩穩的,要想讓類的成員避免不安全的訪問,咱們必須對類成員的訪問加以控制。函數

在C++中,對類成員的訪問控制是經過設置成員的訪問級別來實現的。按照訪問範圍的大小,訪問級別被分爲公有類型(public)、保護類型(protected)和私有類型(private) 三種,如圖6-9所示。spa

1. 公有類型

公有類型的成員用關鍵字public修飾,在成員變量或者成員函數前加上public關鍵字就表示這是一個公有類型的成員。公有類型的成員的訪問不受限制,在類的內外均可以訪問,但它更多的仍是做爲類提供給外界訪問的接口,是類跟外界交流的通道,外界經過訪問公有類型的成員完成跟類的交互。例如Teacher類的表示上課行爲的GiveLesson()成員函數,這是它向外界提供的服務,應該被外界訪問,因此這個成員函數應該設置爲公有類型。設計

 

圖6-9  訪問級別code

2. 保護類型

保護類型的成員用關鍵字protected修飾,其聲明格式跟public類型相同。保護類型的成員在類的外部沒法訪問,可是能夠在類的內部及繼承這個類而得的派生類中訪問,它主要用於將屬性或者方法遺傳給它的下一代子類。例如,對於Teacher類的表示姓名屬性的m_strName成員變量,誰也不肯意本身的名字被別人修改,因此應該限制外界對它的訪問;而顯然本身能夠修改本身的名字,因此Teacher類內部能夠訪問這個成員變量;同時,若是Teacher類有派生類,好比表示大學老師的Lecturer,咱們也但願這個成員變量能夠遺傳給派生類,使其派生類也擁有這個成員變量,能夠對其進行訪問。通過這樣的分析,把m_strName成員變量設置爲保護類型最爲合適。對象

3. 私有類型

私有類型的成員用關鍵字private修飾,其聲明格式跟public類型相同。私有類型的成員只能在類的內部被訪問,全部來自外部的訪問都是非法的,這樣就把類的成員徹底隱藏在類當中,很好地保護了類中數據和行爲的安全。因此,趕快把咱們的錢包聲明爲私有成員吧,這樣小偷就不會訪問到它了。blog

這裏須要說明的是,若是class關鍵字定義的類當中類成員沒有顯式地說明它的訪問級別,那麼默認狀況下它就是私有類型,外界是沒法訪問的,因而可知類實際上是很是「自私」的(相反地,由struct定義的類中,默認的訪問級別爲公有類型)。繼承

咱們把前面例子中的Teacher類根據訪問控制加以改寫,以更加真實地反映現實的狀況:接口

// 進行訪問控制後的Teacher類
class Teacher
{
// 公有類型成員
// 外界經過訪問這些成員跟該類進行交互,得到類提供的服務
public: // 冒號後的變量或者函數都受到它的修飾
    // 構造函數應該是公有的,這樣外界才能夠利用構造函數建立該類的對象
    Teacher(string strName) : m_strName(strName)
    {
        //
    }

    // 老師要爲學生們上課,它應該被外界調用,因此這個成員函數是公有類型的
    void GiveeLesson()
    {
        // 在類的內部,能夠訪問自身的保護類型和私有類型成員
        PrepareLesson();  // 先備課,訪問保護類型成員
        cout<<"老師上課。"<<endl;
        m_nWallet += 100;  // 一節課100塊錢,訪問私有類型成員
    }

    // 咱們不讓別人修更名字,可總得讓別人知道咱們的名字吧,
// 對於只可供外界只讀訪問的成員變量,
// 能夠提供一個公有的成員函數供外界對其進行讀取訪問
    string GetName()
    {
        return m_strName;
    }

// 保護類型成員
// 不能被外界訪問,可是能夠被自身訪問,也能夠遺傳給下一級子類,
// 供下一級子類訪問
protected:
// 只有本身備課,子類也須要備課,因此設置爲保護類型
void PrepareLesson()
{
     cout<<"老師備課。"<<endl;
}

// 只有本身能夠修改本身的名字,子類也須要這樣的屬性
    string m_strName;
private:    // 私有類型
   int m_nWallet; // 錢包只有本身能夠訪問,因此設置爲私有類型
};

有了訪問控制以後,如今再對Teacher類對象的成員進行訪問時就須要注意了,咱們只能在外界訪問其公有成員,若是試圖訪問其保護類型或者私有類型的成員,就會被絕不留情地拒之門外,吃人家的閉門羹:生命週期

int main()
{
    // 建立對象時會調用類的構造函數
    // 在Teacher類中,構造函數是公有類型,因此能夠直接調用
    Teacher MrChen("ChenLiangqiao");

    // 外部變量,用於保存從對象得到的數據
    string strName;
   // 經過類的公有類型成員函數,讀取得到類中的保護類型的成員變量   
    strName = MrChen.GetName();

    // 錯誤:沒法直接訪問類的保護類型和私有類型成員
    // 想改個人名字?先要問我答應不答應
    MrChen.m_strName = "WangGang";
   // 想從我錢包中拿走200塊,那更不行了
    MrChen.m_nWallet -= 200;

    return 0;
}

在主函數中,咱們首先建立一個Teacher類對象,這個建立過程會調用它的構造函數,這就要求構造函數是公有類型。在構造函數中,咱們會訪問成員變量m_strName,雖然它是保護類型的,可是能夠在類自身的構造函數中對其進行修改。另外一方面,爲了讓外界也可以安全地讀取該成員變量的值,咱們爲Teacher類添加了一個公有類型的成員函數GetName(),這樣就能夠經過它讓外界訪問類中保護類型的成員得到必要的數據。除此以外,Teacher類還提供了一個公有類型的GiveLesson()函數,外界能夠直接調用這個函數得到Teacher類提供的上課服務。而在這個公有類型的成員函數內部,咱們還訪問了類中的保護類型和私有類型成員。

以上的訪問都是合理合法的,可是,若是想在類的外部直接訪問保護類型或私有類型的成員,編譯器會幫咱們檢測出這個非法訪問,產生一個編譯錯誤提示沒法訪問保護或私有類型的成員。這樣,有編譯器幫咱們看着,小偷就再也別想動咱們的錢包了。

經過對類成員的訪問進行控制,起到了很好地保護數據和行爲的做用,防止了數據被外界隨意修改,同時也限制了外界使用不合理的行爲。若是對象的某些成員變量由於業務邏輯的須要容許外界訪問(好比這裏的m_strName),也建議採用提供公有接口的方法,讓外界經過公有接口來訪問這些成員變量,而不是直接把這些成員變量設置爲公有類型。通常狀況下,類的成員變量都應該設置爲保護或私有類型。

6.2.7  在友元中訪問類的隱藏信息

採用類成員的訪問控制機制以後,很好地實現了數據和行爲的隱藏,這種隱藏有效地避免了來自外界的非法訪問,保護了數據和行爲的安全。可是,這種嚴格的成員訪問控制是不問原因的,任何來自外界的對類中隱藏信息(保護或私有類型成員)的訪問都會被拒絕,這樣天然也會將一些合理的訪問擋在門外,給類成員的訪問帶來一些麻煩。例如,有時須要定義某個函數,這個函數不是類的一部分,但又須要頻繁地訪問類的隱藏信息;又或者須要定義某個新的類,由於某種緣由,這個類須要訪問另一個類的隱藏信息,就像現實世界中老婆須要訪問老公的錢包同樣。在這些狀況下,咱們須要從外界直接訪問類的保護或私有類型成員,但卻被嚴格的成員訪問控制機制擋在了門外。

凡事都有例外。爲了給訪問控制機制開個後門,讓外界值得信任的函數或者類可以訪問某個類的隱藏信息,C++提供了友元機制。它利用「friend」關鍵字,能夠將外界的某個函數或者類聲明爲類的友元函數或者友元類,二者統稱爲友元。當成爲類的友元后,就能夠對類的隱藏信息進行訪問了。

1. 友元函數

友元函數其實是一個定義在類外部的普通函數,它不屬於任何類。當使用「friend」關鍵字在類的定義中加以聲明後,這個函數就成爲類的友元函數,以後它就能夠不受類成員訪問控制的限制,直接訪問類的隱藏信息。在類中聲明友元函數的語法格式以下:

class 類名
{
    friend 返回值類型 函數名(形式參數列表);
// 類的其餘聲明和定義…
};

友元函數的聲明跟類的普通成員函數的聲明是相同的,只不過在函數聲明前加上了friend關鍵字修飾而且定義在類的外部,並不屬於這個類。友元函數的聲明不受訪問控制的影響,既能夠放在類的私有部分,也能夠放在類的公有部分,它們是沒有區別的。另外,一個函數能夠同時是多個類的友元函數,只是須要在各個類中分別聲明。

2. 友元類

跟友元函數類似,友元類是定義在某個類以外的另一個獨立的普通類。由於須要訪問這個類的隱藏信息,因此利用「friend」關鍵字將其聲明爲這個類的友元類,賦予它訪問這個類的隱藏信息的能力。成爲這個類的友元類以後,友元類的全部成員函數也就至關於成爲了這個類的友元函數,天然也就能夠訪問這個類中的隱藏信息。      

在C++中,聲明友元類的語法格式以下:

class 類名
{
    friend class 友元類名;
// 類的其餘聲明和定義
};

友元類的聲明跟友元函數的聲明相似,這裏就再也不贅述。惟一須要注意的是這裏兩個類之間的相互關係,若是咱們但願A類可以訪問B類的隱藏信息,就在B類中將A類聲明爲它的友元類。這就表示A類是B類通過認證後的值得信賴的「朋友」,這樣A類才能夠訪問B類的隱藏信息。爲了更好地理解友元的做用,仍是來看一個實際的例子。假設在以前定義的Teacher類中有一個成員變量m_nSalary記錄了老師的工資信息。工資信息固然是我的隱私須要保護了,因此將其訪問控制級別設置爲保護類型,只有本身和派生的子類能夠訪問:

class Teacher
{
//// 保護類型的工資信息
protected:
    int m_nSalary;
};

將m_nSalary 設置爲保護類型,能夠很好地保護數據安全。可是,在某些特殊狀況下,咱們又不得不在外界對其進行訪問。好比,稅務局(用TaxationDep類表示)要來查老師的工資收入,他固然應該有權力也有必要訪問Teacher類中m_nSalary這個保護類型的成員;又或者學校想用AdjustSalary()函數給老師調整工資,老師天然樂意它來訪問m_nSalary這個保護類型的成員。在這種狀況下,咱們就須要把TaxationDep類和AdjustSalary()函數聲明爲Teacher類的友元,讓它們能夠訪問Teacher類的隱藏信息:

// 擁有友元的Teacher類
class Teacher
{
    // 聲明TaxationDep類爲友元類
    friend class TaxationDep;
    // 聲明AdjustSalary()函數爲友元函數
    friend int AdjustSalary(Teacher* teacher);
// 其餘類的定義…
protected:
    int m_nSalary; // 保護類型的成員
};

// 在類的外部定義的友元函數
int AdjustSalary(Teacher* teacher)
{
    // 在Teacher類的友元函數中訪問它的保護類型的成員m_nSalary
    if( teacher != nullptr && teacher->m_nSalary < 1000)
   {
        teacher->m_nSalary += 500;  // 漲工資
       return teacher->m_nSalary;
}

    return 0;
}

// 友元類
class TaxationDep
{
// 類的其餘定義…
public:
    void CheckSalary( Teacher* teacher )
    {
        // 在Teacher類的友元類中訪問它的保護類型成員m_nSalary
        if(teacher != nullptr && teacher->m_nSalary > 1000)
        {
            cout<<"這位老師應該交稅"<<endl;
      }
    }
};

能夠看到,當Teacher類利用「friend」關鍵字將AdjustSalary()函數和TaxationDep類聲明爲它的友元以後,在友元中就能夠直接訪問它的保護類型的成員m_nSalary,這就至關於爲友元打開了一個後門,使其能夠翻過訪問控制這道保護牆而直接訪問到類的隱藏信息。

友元雖然能給咱們帶來必定的便利,可是「開後門」畢竟不是一件多麼正大光明的事情,在使用友元的時候,還應該注意如下幾點:

l  友元關係不能被繼承。這一點很好理解,咱們跟某個類是朋友(便是某個類的友元類),並不表示咱們跟這個類的兒子(派生類)一樣是朋友。

l  友元關係是單向的,不具備交換性。好比,TaxationDep類是Teacher類的友元類,稅務官員能夠檢查老師的工資,但這並不表示Teacher類也是TaxationDep類的友元類,老師也能夠檢查稅務官員的工資。

l  將某個函數或者類聲明爲友元,即意味着對方是通過審覈認證的,是值得信賴的,因此才受權給它訪問本身的隱藏信息。這也提示咱們在將函數或類聲明爲友元以前,有必要對其進行必定的審覈。只有值得信賴的函數和類才能將其聲明爲友元。

友元的使用並無破壞封裝

在友元函數或者友元類中,咱們能夠直接訪問類的保護或私有類型的成員,這個「後門」打開後,不少人擔憂這樣會讓類的隱藏信息暴露出來,破壞類的封裝。但事實是,合理地使用友元,不只不會破壞封裝,反而會加強封裝。

在面向對象的設計中,咱們強調的是「高內聚、低耦合」的設計原則。當一個類的不一樣成員變量擁有兩個不一樣的生命週期時,爲了保持類的「高內聚」,咱們常常須要將這些不一樣生命週期的成員變量分割成兩部分,也就是將一個類分割成兩個類。在這種狀況下,被分割的兩部分一般須要直接存取彼此的數據。實現這種狀況的最安全途徑就是使這兩個類成爲彼此的友元。可是,一些「高手」想固然地認爲友元破壞了類的封裝,轉而經過提供公有的 get()和set()成員函數使兩部分能夠互相訪問數據。實際上,他們不知道這樣的作法正是破壞了封裝。在大多數狀況下,這些 get()和set()成員函數和公有數據同樣差勁:它們僅僅隱藏了私有數據的名稱,而沒有隱藏對私有數據的訪問。

友元的使用,只是向必要的客戶公開類的隱藏數據,好比Teacher類只是向TaxationDep類公開它的隱藏數據,只是讓稅務官能夠知道它的工資是多少。這要遠比使用公有的get()/set()成員函數,而讓全世界均可以知道它的工資要安全隱蔽的多。

相關文章
相關標籤/搜索