第5章 隱藏實現
一、C++的訪問控制符
public 在其後聲明的所有成員可以被所有人訪問。
private 表示除了該類型的創建者和類的內部成員函數之外,任何人都不能訪問。
protected 繼承的結構可以訪問protected成員,但不能訪問private結構
二、友元:朋友才能看到
C++是一種很“人性化”的語言,跟我不熟的人,都不能知道我家的家庭成員。朋友就可以。
可以聲明爲友元的函數類型:
1、全局函數(可以用本結構的指針作爲參數)
2、另一個結構中的函數(可以用本結構的指針作爲參數)
3、整個另一個結構
friend的雙重聲明作用:1) 聲明一個函數
2) 聲明這個函數爲當前結構的友元
開個小差:書中有這樣一個代碼段
struct X;
struct Y{
void f(X*);
};
結構Y要聲明其成員函數f(X*),必須知道X的定義,但X結構體又是在結構Y之後明確定義的。這種情況下可以在前面先聲明一個struct X。函數要傳遞的是一個地址,不關X是什麼類型,其類型的地址都是一個固定的值,因此編譯器知道如何傳遞一個地址。編譯是可以通過的。
但是如果聲明這樣一個函數void f(X);就不行了,因爲編譯器還不知道X的具體結構,編譯會報錯。
三、嵌套友元
嵌套的結構並不能自動獲得訪問private成員的權限。若遵循下列規則就可以
1、聲明一個嵌套結構
2、聲明該結構爲全局範圍的friend
3、定義這個結構
主要就是將聲明結構與聲明友元分開~!
struct Holder{
private:
int a[sz];
public:
void initialize();
struct Pointer;
friend Pointer;
struct Pointer{
private:
Holder* h;
int* p;
public:
void initialize(Holder* h);
void next();
void previous();
void top();
void end();
int read();
void set(int i);
};
};
這樣,結構Pointer就可以直接訪問數組a了。
再做個試驗,在public區域定義一個變量int n; 如果並沒有聲明結構Pointer是結構Holder的友元,結構Pointer的函數仍然能夠訪問數據成員n。這說明,friend關鍵字是用來讓外部函數訪問類的私有成員的一種方法!!!
從第三點看出,C++並非一個純面向對象的語言,friend關鍵字用來解決一些實際問題。
五、類
關鍵字class被用來描述一個新的數據類型。它和struct的每一個方面都是一樣的,不同在於
class聲明的類中的成員默認爲private;
struct聲明的結構體中的成員默認爲public;
六、現在以C++的方式來實現一個Stash
#ifndef STASH_H
#define STASH_H
class Stash{
int size; //number of each spaces
int quantity; //number of storage spaces
int next; //Next empty space
//Dynamically allocated array of bytes:
unsigned char* storage;
void inflate(int increase);
public:
void initialize(int size);
void cleanup();
int add(void* element);
void* fetch(int index);
int count();
};
#endif//STASH_H
注意到函數void inflate(int increase);被加入到了private裏面,因爲它只在add(入棧時發現空間不夠用了才調用),所以不能被輕易訪問,事實上它也確實不需要成爲一個外部接口。
七、句柄類
先說一個實際的問題。對於一個公司的核心技術,技術管理人員可能不想所有人都能看到其代碼,因此交給其他程序員使用的時候,可能是交給他們一個庫文件,一個頭文件。只要在頭文件裏能找到的接口,都可以調用。另一個事實是,當一個文件被修改,或它所依賴的頭文件被修改,都需要重複編譯該文件。這意味着程序員無論何時修改了一個類,無論修改的是公共接口的部分還是私有成員的聲明部分,他都必須編譯包含這個頭文件的所有文件。這就是通常所說的易碎的基類問題(fragile
base-class problem)對於一個大項目而言,這是非常耗時耗力的。
解決辦法是----句柄類(handle class)。有關實現的任何東西都消失了,只剩一個單指針“smile"。改指針指向一個結構,該結構的定義與其所有的成員函數的定義一同出現在實現文件中。這樣,只要接口部分不改變,頭文件就不需要變動。兒實現部分可以任意更改,編譯的時候只需編譯單獨地一個文件就OK。
#ifndef HANDLE_H
#define HANDLE_H
class Handle{
struct Cheshire;
Cheshire *smile;
public:
void initialize();
void cleanup();
int read();
void change(int);
};
#endif
在頭文件中並沒有定義結構體Cheshire。只是聲明瞭而已,但是這對於Cheshire類型的指針smile已經足夠,編譯器已經知道它是一個地址。
接下來你只需要在實現文件Handle.cpp裏實現對結構Cheshire的定義即可。
#include<iostream>
#include "Handle.h"
using namespace std;
struct Handle::Cheshire{
int i;
};
void Handle::initialize()
{
smile = new Cheshire;
smile->i = 0;
}
void Handle::cleanup()
{
delete smile;
smile = NULL;
}
int Handle::read()
{
return smile->i;
}
void Handle::change(int i)
{
smile->i = i;
}
int main()
{
Handle h;
h.initialize();
cout << "smile->i = " << h.read() << endl;
h.change(5);
cout << "smile->i = " << h.read() << endl;
return 0;