摘要:本文是從飲水思源BBS C/C++版上一篇文章引發的思考。當時看到文章,突然覺得平時見慣了的,在這裡似乎變得陌生了,究竟存取控制符怎樣起作用,怎樣使用,該怎樣理解,本文試圖給出討論。
原帖如下 1 #include <IOSTREAM>
2 using namespace std;
3
4 class A{
5 public:
6 A(int i_,int j_)
7 {
8 i=i_;
9 j=j_;
10 }
11 void disp(A &a)
12 {
13 cout<<a.i<<endl<<a.j<<endl;
14 }
15
16 private:
17 int i;
18 protected:
19 int j;
20 };
21
22 int main(int argc, char* argv[])
23 {
24 A a(123,456);
25 A b(789,543);
26 a.disp(b);
27 b.disp(a);
28
29 return 0;
30 }
初看起來,倒是會產生疑問。為什麼會這樣,是否有bug?
仔細考究起來,我們其實可以這樣看待類和對象:
類是將資料成員和進行於其上的一系列操作(成員函數)封裝在一起,注意:成員函數可以操作資料成員(可以稱類中的資料成員為泛資料成員)!
對象是類的執行個體化,怎樣理解執行個體化?其實每一個執行個體對象都只是對其中的資料成員初始化,記憶體映像中每個對象僅僅保留屬於自己的那份資料成員副本。而成員函數對於整個類而言卻是共用的,即一個類只保留一份成員函數。
那麼每個對象怎樣和這些可以認為是“分離”的成員函數發生聯絡,即成員函數如何操作對象的資料成員?記住this指標,無論對象通過(.)操作或者(->)操作調用成員函數,編譯時間刻,編譯器都會將這種調用轉換成我們常見的全域函數的形式,並且多出一個參數(一般這個參數放在第一個),然後將this指標傳入這個參數。於是就完成了對象與成員函數的綁定(或聯絡).
執行個體化後就得到同一個類的多個不同的對象,既然成員函數共用的,那麼成員函數就可以操作對象的資料成員。
問題是現在有多個對象,成員函數需要知道操作的是哪個對象的資料成員?
比如有對象obj1和obj2,都屬於A類,A類有public成員函數foo()
如果obj1調用該函數,編譯時間會給foo函數傳入this指標,obj1,foo中操作obj1自身的成員就不用任何修飾,直接存取,因為其中的資料成員自動根據this指標找到。
如果obj1調用該函數,同樣可以訪問同類的其他對象的資料成員!那麼你需要做的是讓foo函數知道是同類對象中哪個對象的資料成員,一個解決辦法是傳入同類其他對象的指標或引用,那麼就可以操作同類其他對象的資料成員。
foo(A &obj)
這樣定義,然後調用:
obj1.foo(obj2)
就可以在obj1訪問obj2的資料成員,而無論這些資料成員是private還是protected
搬出C++ Object Model,可以畫出各個對象的記憶體map就可以更清晰的看出:
總結:C++的存取修飾詞的作用是以類為單位,而不是以對象為單位。
通俗的講,同類的對象間可以“互相訪問”對方的資料成員,只不過訪問途徑不是直接存取.
步驟是:通過一個對象調用其public成員函數,此成員函數可以訪問到自己的或者同類其他對象的public/private/protected資料成員和成員函數(類的所有對象共用),而且還需要指明是哪個對象的資料成員(調用函數的對象自己的成員不用指明,因為有this指標;其他對象的資料成員可以通過引用或指標間接指明)
C++中public,protected,private訪問小結
第一:private,public,protected方法的存取範圍.(public繼承下)
private: 只能由該類中的函數、其友元函數訪問,不能被任何其他訪問,該類的對象也不能訪問.
protected: 可以被該類中的函數、子類的函數、以及其友元函數訪問,但不能被該類的對象訪問
public: 可以被該類中的函數、子類的函數、其友元函數訪問,也可以由該類的對象訪問
註:友元函數包括兩種:設為友元的全域函數,設為友元類中的成員函數
第二:類的繼承後方法屬性變化:
使用private繼承,父類的所有方法在子類中變為private;
使用protected繼承,父類的protected和public方法在子類中變為protected,private方法不變;
使用public繼承,父類中的方法屬性不發生改變;
|
public: |
protected: |
private: |
public繼承 |
public |
protected |
--- |
protected繼承 |
protected |
protected |
--- |
private繼承 |
private |
private |
--- |
protected繼承和private繼承能降低存取權限
再次提到:可以提供訪問行為的主語為“函數”。
類體內的訪問沒有訪問限制一說,即private函數可以訪問public/protected/private成員函數或資料成員,同理,protected函數,public函數也可以任意訪問該類體中定義的成員
public繼承下,基類中的public和protected成員繼承為該子類的public和protected成員(成員函數或資料成員),然後訪問仍然按類內的無限制訪問
對於類域範圍內,成員函數訪問無所謂訪問限制。 對於繼承情況下的基類private成員,在衍生類別中仍然繼承了下來,只不過它不能直接存取, 即使在類裡也不行, 更不用說是類對象了。 可以通過下列例子看到: 1 #include <iostream> 2 using namespace std; 3 4 class C{ 5 public: 6 C(int val) : m_c(val) {} 7 private: 8 int m_c; 9 }; 10 11 class D: public C{ 12 public: 13 D(int val1, int val2) : C(val1), m_d(val2) {} 14 int m_d; 15 }; 16 17 int main() 18 { 19 20 21 cout << sizeof(C) << " " << sizeof(D) << endl; // 4 8 22 D obj(2, 25); 23 cout << &obj << " " << &obj.m_d << endl; //0x0012FF78 0X0012FF7C 24 //cout << obj.m_c; //error, 不能訪問 25 26 D *ptr = &obj; 27 int *iptr = (int *)ptr; 28 cout << *iptr << " " << *(iptr+1) << endl;//2 25 29 30 return 0; 31 } 32 33 同樣,加入虛函數,可以看到VC編譯器將vptr放置在資料區的最開頭 |
下面摘自部落格園的一篇,相當精闢的理解:
下面這個問題摘自論壇的一個文章:
已知3個類O、P和Q,類O中定義了一個私人方法F1、一個公有方法F2和一個受保護的方法F3:類P和類Q是類O的衍生類別,其繼承方式如下所示:
class P : protected O {…};
class Q : public O {…};
關於方法F1的描述中正確的是___(34)___;關於方法F2韻描述中正確的是___(35)___;關於方法F3的描述中正確的是___(36)___。
(34)
A.方法F1無法被訪問
B.只有在類O內才能存取方法F1
C.只有在類P內才能存取方法F1
D.只有在類Q內才能存取方法F1
(35)
A.類O、P和Q的對象都可以存取方法F2
B.類P和Q的對象都可以存取方法F2
C.類0和Q的對象都可以存取方法F2
D.只有在類P內才能存取方法F2
(36)A.類0、P和Q的對象都可以存取方法F3
B.類0、P和Q的對象都不可以存取方法F3
C.類0和Q的對象都可以存取方法F3
D.類P和Q的對象都可以存取方法F3。
有甚麼辦法可以簡單地記住這許多的規則? 下文告訴你一個根本不需要記的辦法。
顧名思義,private/public/protected 分別表示 私人/公開/保護,它們是一組用於存取權限控制的關鍵字。那麼首先,需要澄清的一個關鍵點是,是要控制誰訪問誰的許可權?這個訪問的主語(施事)是誰?賓語(受事)是誰?
我們經常聽到這樣的說法:
1)一個類友元可以訪問該類的任何成員(包括成員變數及成員方法,下同)。
2)private成員只有該類自身可以訪問,protected成員只有該類及其衍生類別可以訪問,public成員所有的人都可以訪問。
賓語(受事)是誰這一點很明確,是類的成員(包括成員變數及成員方法)。主語(施事)是誰?這是讓大家發生混淆的關鍵點。也是這個說法中含糊不清的地方。
想清楚一點,其實主語(施事)指的是一個函數,而不是類(當然更不是變數)。private/public/protected要控制的是一個函數(施事)對一個類的成員(包括成員變數及成員方法)的存取權限。因此比較完整的說法是:
1)一個類友元(包含友元函數或者友元類的所有成員函數)可以訪問該類的任何成員(包括成員變數及成員方法)。
2)除去友元外,private成員只有該類自身的成員函數可以訪問,protected成員只有該類的成員函數及其衍生類別的成員函數可以訪問,public成員則所有的函數都可以訪問。
也就是說,當我們說一個類可以訪問XXX,其實暗指這個類的成員函數可以訪問XXX。瞭解了這一點,外加一條顯而易見的規則,上面的問題就不難回答了。這條規則是:
3)衍生類別在繼承時可削弱成員的存取權限(通過protected/private修飾)。例如上面的例子class P : protected O {…}; 那麼某個函數通過類P訪問O中成員時,該函數對類O中的public成員只有protected許可權。
補充:有一種技術叫Member Spy(類成員間諜),通過該技術衍生類別可將基類的protected成員修改為public許可權。這種技術用到了using關鍵字。舉例如下:
class A
{
protected:
int m_data;
};
class SpyA : public A
{
public:
using A::m_data;
};
void TestSpy(A* pA)
{
SpyA* pSpyA = static_cast<SpyA*>(pA);
// 強制轉換A為SpyA,這要求SpyA沒有成員變數且沒有重載A中的虛函數。
// 現在你可以通過pSpyA訪問m_data了。例如:int data = pSpyA->m_data;
}
由於這種技術用到了強制類型轉換,當謹慎使用。
題目:自己定義一個class,它只有一個資料成員,且為private, 其成員函數都為public, 它產生兩個對象obj1, obj2, 先要求用最少的成員函數實現obj1訪問obj2的private資料成員。
實現一: