c++的兩大特色是多態和模板。其中多態是通過繼承和虛函數來實現的,其中虛函數是通過每個對象里面的虛表來實現的。如果這個對象的類有虛函數,那么這個類就有一張虛表,存的是每個虛函數的入口地址,而這個類的每個對象,都會有一個4字節的指針,指向這張虛表,這個就是虛指針。
上面一段話很多人都知道,但是如果問普通成員函數,編譯器是怎么找到它的入口地址的呢?也就是說,怎么進行調用?為什么A類一個foo函數和B類一個foo函數,A類的對象.foo就一定是調用A的foo?有人會說運行時類型識別RTTI。假如識別出A類的對象,那么A類的對象a,和A類的對象b,分別調用foo處理成員變量的時候,為什么不會a調用foo處理的是b的數據?這個問題只要問深幾層,RTTI的說法就不攻自破。其實面向對象語言都是用反射,也就是對象把自己的地址作為參數,傳入成員函數的this指針,通過this指針來確定數據的。
還記得函數重載的實現機制吧,在c++里面是通過將函數名和函數的參數列表結合來區分不同的函數,同名但不同參數列表的函數,是不同的函數。例如void foo(int,int)在c里面編譯器是用_foo來識別,在c++里面是用_foo_int_int來識別。
那么A類的foo函數和B類的foo函數,如果參數列表一致,怎么區分它們呢?用A類的一個對象調用成員函數,這個成員函數用到成員變量,怎么知道是在用這個對象的數據呢?這個解釋,就是this指針。A類的foo(int,int)函數,已經被編譯器編譯成_foo_A*_int_int,也就是說隱含已經加入了函數所屬類的信息,調用A類對象a.foo的時候,已經在調用foo(const A * this,int,int)這個函數。這也解釋了,為什么在成員函數內部,使用成員變量,有時候(並不是全部情況下都如此)前面加this和不加this沒有區別,編譯器已經把foo里面用到的成員變量,用this指針指向了。
如果你在全局中,這樣定義這樣一個函數foo(const A * this,int,int)去模仿成員函數的調用,將不能編譯,因為this是關鍵字,this指針已經成為了c++的機制,在類成員函數用this才做形參(foo(int this)或者foo(float this)),也是非法的。這樣試圖和編譯器生成的函數的第一個參數this來一個重定義。
舉個例子,下面一段代碼:
1 class A 2 { 3 public: 4 void foo(){ cout << "A foo" << endl; } 5 }; 6 class B 7 { 8 public: 9 void foo(){ cout << "B foo" << endl; } 10 };
oo將編譯成:
1 void foo(const A* this)( cout << "A foo" << endl; } 2 void foo(const B* this)( cout << "B foo" << endl; }
調用a.foo(),編譯器將轉換成foo(&a)
有趣的是,A* pa = NULL; pa->foo();也沒有異常退出,因為沒有通過this引用任何成員變量,這個時候不過this指針為NULL而已。
靜態成員函數
上面說的只是面向對象的非靜態成員函數,如果說到類里面的靜態成員函數,解釋又是另外一個,請看下文。
1、靜態數據成員
特點:
A、內存分配:在程序的全局數據區分配。
B、初始化和定義:
a、靜態數據成員定義時要分配空間,所以不能在類聲明中定義。
b、為了避免在多個使用該類的源文件中,對其重復定義,所在,不能在類的頭文件中
定義。
c、靜態數據成員因為程序一開始運行就必需存在,所以其初始化的最佳位置在類的內部實現。
C、特點
a、對相於 public,protected,private 關鍵字的影響它和普通數據成員一樣,
b、因為其空間在全局數據區分配,屬於所有本類的對象共享,所以,它不屬於特定的類對象,在沒產生類對象時其作用域就可見,即在沒有產生類的實例時,我們就可以操作它。
D、訪問形式
a、 類對象名.靜態數據成員名
b、 類類型名:: 靜態數據成員名
E、靜態數據成員,主要用在類的所有實例都擁有的屬性上。比如,對於一個存款類,帳號相對 於每個實例都是不同的,但每個實例的利息是相同的。所以,應該把利息設為存款類的靜態數據成員。這有兩個好處,第一,不管定義多少個存款類對象,利息數據成員都共享分配在全局區的內存,所以節省存貯空間。第二,一旦利息需要改變時,只要改變一次,則所有存款類對象的利息全改變過來了,因為它們實際上是共用一個東西。
2、靜態成員函數
特點:
A、靜態成員函數與類相聯系,不與類的對象相聯系。
B、靜態成員函數不能訪問非靜態數據成員。原因很簡單,非靜態數據成員屬於特定的類實例。
作用:
主要用於對靜態數據成員的操作。
調用形式:
A、類對象名.靜態成員函數名()
B、類類型名:: 靜態成員函數名()
上面是一段精辟的分析,但是他沒有說道編譯器是如何實現這個調用的。下面一段代碼,將解釋這個過程:
1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 static int count; 8 void foo(){ cout << "A foo" << endl; } // 如果這樣聲明和定義一個成員函數,將直接產生一個 foo(A& this) 類型的函數 9 static void goo(){ cout <</* this << */"A goo"<< endl; } // 靜態函數沒有 this 指針 10 void too(){ cout << typeid(*this).name() << endl; } 11 }; 12 13 int A::count = 0; 14 15 class B : public A 16 { 17 public: 18 // 如果靜態函數只能通過域運算符來調用的話,那class在靜態意義下就成了命名域的概念了 19 // 如果沒有下面這個函數,A類的goo函數將會繼承下來,說明作為類的命名空間,也可以繼承 20 static void goo(){ cout << "B goo" << endl;} // 一個問題,靜態的成員函數,是怎么區分開的呢? 21 }; 22 23 int main() 24 { 25 A a , *pa; 26 pa->goo(); // 靜態也跟普通函數一樣,沒有多態效果 27 a.goo(); // 是否是直接翻譯成 A::goo() 竟然說我沒引用過 a !- - 28 // a.foo(); // this 是關鍵字,不能拿來作為一個全局函數的參數,在轉成 foo(&a) 的時候,一定是調用 foo(A& this) 這個函數 29 // A::foo(); // 這個 foo 沒有帶參數,只能調用靜態的,靜態的就直接編譯成類似全局函數的不帶 this 參數的類型 30 A::goo(); // 這樣調用是正確的,這說明它沒有 this 指針作為形參 31 system("pause"); 32 return 0; 33 }
編譯運行,將產生一個警告,說對象a和指針pa從來沒引用過!通過對象調用靜態函數,已經通過類型識別,被編譯器替換成A::goo(),這個是由編譯器做的,所以替換之后a就只定義了,但是沒用引用過。換句話,static的成員函數,只能通過域運算符來調用,無論你是用對象調用還是用指針調用。
static的成員變量,也是如此,只能通過翻譯成域運算符來調用。這樣兩者結合在一起,說明了“類其實除了可以定義變量,還有一個重要的作用就是它是個命名域,相當於std::cout這樣。而且,這個命名域,還能繼承下來。”
還記得stl里面的迭代器嗎?迭代器的類,就是為了不污染全局空間,把迭代器類聲明在某個stl容器類里面。這個類里面的類,只能通過容器和域運算符才可見,有趣的是,這個類中類,也可以繼承。
#include <iostream> using namespace std; class A { public: class C{}; // 類中類 }; class B : public A { }; int main() { B::C c; // 除了父類,子類也可見 return 0; }
這里由於某個問題找到了這邊博文,感覺不錯,轉自:http://blog.csdn.net/yuanyirui/article/details/4594805
