本文是我在學習c++過程當中的一些思考和總結,主要是c++中關於函數的林林總總。歡迎你們批評和指正,共同窗習。html
os version: ubuntu 12.04 LTS gcc version: gcc 4.6.3 文中以 $ 開頭語句表示 shell command
0.this 指針c++
我以爲首先得講明白這個東東,讓你們明白c++中函數與c語言中函數的區別
什麼是 this 指針? 這裏我直接選自 ISO c++ 中關於 this 定義(注:我會大量援引ISO c++,相信你們應該都看得懂,哈哈)shell
In the body of a non-static member function, the this is a prvalue expression whose value is a address of the object for which the function is called.
這個定義裏面,咱們須要注意兩點:
(1) 只有類中的 non-static member function 纔有隱含的 this 指針,這樣 類中的 static 函數、不屬於類的全局函數 都沒有 this 指針
數據庫
(2) this 是常指針,一直指向調用該函數的類對象,其指向(地址值)不可更改,即 Widget* const thisexpress
class Widget { public: int fun() { return a + b; } private: int a; int b; };
c++編譯器會將 member function 轉化爲對等 non-member function
a.改寫函數原型,安插額外的隱含參數到 member function,用以提供一個存儲通道,使得該類的全部對象均可以調用該函數編程
int Widget::fun(Widget* const this)
b.將每個 non-static data member 的存取操做改寫成 經由 this 指針來存取ubuntu
int Widget::fun(Widget* const this) { return this->a + this->b; }
注:只有屬於類而且 non-static 的 data member 才由 this 指針來存取,若是這樣:int fun(int x, int y) { return x + y; }, 則會以下改寫:socket
int Widget::fun(Widget* const this, int x, int y) { return x + y; }
c.將 member function 從新寫成一個外部函數,對函數進行 "name mangling"處理,使它在程序中成爲獨一無二的詞彙
注: name mangling 確保每個符號都有惟一的名字,函數重載部分我會詳細講講這個
咱們對示例代碼 cpp_function.cc :函數
$ g++ cpp_function.cc -o cpp_function.o -W -g -c -std=c++0x
咱們對生成的 cpp_function.o: 學習
$ nm cpp_function.o | grep -E fun
能夠將 "name mangling"視爲編碼過程,固然有對應的解碼過程"demangling"
$ c++filt _ZN6Widget3funEv
d.如今假如咱們調用 fun 函數:
成員調用符:widget.fun(); 其實是: fun(&widget);
指針調用: pwidget->fun(); 其實是: fun(pwidget);
知識小結:
0.1 任何一個non-static data member 都是且只能經過this指針存取的
0.2 編譯器在每一個 non-static 的 member function的第一個參數都安插一個隱藏this指針參數
0.3 調用 non-static non-virtual 的 member function:
// non-virtual function 調用不須要經過this尋址,而是經過編譯器的符號表 widget.fun() ==> Widget_3fun(&widget); //編譯器安插一個this指針 pwidget->fun() ==> Widget_3fun(pwidget); //編譯器安插一個this指針
0.4 調用 virtual member function:
// virtual function 調用首先須要經過this指針、vptr虛表指針尋址 widget.vfun() ==> (*(widget.vptr[1])) (&widget) //&widget爲this指針,1爲vfun虛表下標 pwidget->vfun() ==> (*(pwidget->vptr[1])) (pwidget)
0.5 調用 static member function:
static member function 無 額外安插的this指針,所以: a.不能存取 non-static data member (這些data必須經過this指針存取) b.不能爲 virtual function (virtual function都是有 this 有vptr的) c.不能爲 const member function(const member function 都是 const Widget* const this) widget.sfun() ==> Widget_4fun(); // 無額外安插的this指針 pwidget.sfun() ==> Widget_4fun(); // 無額外安插的this指針
好吧,this 指針這部分暫時告一段落吧
1. inline 函數
可能有的同窗以爲 inline 函數很簡單,其實這裏面仍是有點門道的
當函數被聲明 inline 函數以後,編譯器可能會將其內聯展開,無需按照一般的函數調用機制調用 inline 函數
這裏我重點突出了兩個字"可能",難道我將一個函數聲明爲 inline,編譯器敢違揹我命令不進行內聯展開嗎? 是的,徹底可能
inline 聲明對編譯器來講只是一個建議,編譯器能夠選擇忽略這個建議
優勢: inline 函數能夠避免函數調用的開銷,令目標碼更加高效
缺點:inline 一個很大的函數將增長目標碼大小
究竟哪些函數應該聲明 inline?
Google c++ 編程規範裏面: 只將小於10行的函數進行 inline
(1)編譯器隱式地將在類內定義的成員函數看成 inline 函數(注:建議在類定義函數時 顯式聲明爲 inline,喜歡她就要最大聲最直白的表達出來,哈哈)
(2)inline 函數應該在頭文件中定義,確保調用函數時所使用的定義是相同的,而且編譯器對 inline 函數的定義保持可見
(3)不要將內含循環語句的函數 inline
(4)大多數編譯器不支持遞歸函數 inline
(5)大多數編譯器不支持 virtual 函數 inline (由於 virtual 函數意味着 運行時肯定調用,而 inline 函數須要編譯時內聯展開)
(6)編譯器通常不支持 構造函數 和 析構函數 inline (由於這兩個函數實際上作的工做比咱們想象的多,尤爲是涉及到繼承時)
(7)編譯器通常不支持 "經過函數指針而進行的調用" 實施 inline
c++ sort 經過函數對象(將 operator()函數 inline)進行比較 快於 c語言中qsort經過自定義comp函數指針進行比較 (詳見Effective STL 第46條)
2. 函數重載
ISO c++: Two delarations in the same scope that declare the same name but with different types are called overloaded
a. 必定要出如今相同做用域
b. 函數具備相同的名字可是函數原型必定不相同
什麼是函數原型?
函數原型 = 函數名 + 函數參數個數 + 函數參數類型
這裏咱們能夠知道 僅僅靠 函數返回值類型不一樣 或者 函數參數名稱不一樣 都不能 構成 重載
咱們在第0部分提到過: 編譯器經過 "name mangling" 確保每個符號都有惟一的獨一無二的名字
class Widget { public: int fun(int a, int b); int fun(double a, double b); int fun(int a, int b, int c); };
咱們由圖中能夠看到 通過 name mangling 事後,每一個符號名字帶有:
類名信息、函數名長度、函數名、全部參數的第一個字母縮寫
若是咱們 添加諸如: int fun(int c, int d); double fun(int a, int b) 都是編譯錯誤
咱們再來看看這3組:
//第一組
int fun(int a); int fun(const int a); //第二組
int fun(int* a); int fun(const int* a); //第3組
int fun(int& a); int fun(const int& a);
上圖中三次編譯分別依次對應於上述三組狀況
咱們能夠看到:
第1組編譯錯誤,不能重載
第2組構成重載,經過符號名字能夠大概猜想到: P表明 pointer,K表明 const
第3組構成重載,經過符號名字能夠大概猜想到:R表明 reference, K表明 const
首先我說說 c++中的值語義和對象語義,這部分具體能夠詳見 http://www.cnblogs.com/solstice/archive/2011/08/16/2141515.html
值語義: 對象的拷貝與原對象無關,兩個對象拷貝以後互相分離,彼此無關。c++的內置類型(bool/int/double/char)都是值語義 eg:int a; int b; a = b; //b值賦值給a後,a與b彼此分離
對象語義:對象拷貝以後與原對象並不分離,而是共享同一資源。c++指針、引用、含有各類資源(內存、文件描述符、socket、TCP鏈接、數據庫鏈接等)的對象 eg:int* a; int* b; b = a; //b值(地址)賦值給a後,a與b並未分離,而是共同指向同一地址
引用也是如此,做用在引用上的全部操做事實上都是做用在該引用綁定的對象上。其實引用最終是靠指針實現的.
好了,咱們再來講這3組
(1)第一組,根據編譯器給出的錯誤提示,感受編譯器直接把 const 吞了,有沒有 const 都同樣,好吧,const 在這裏好沒存在感,哈哈
第一組參數既不是指針,也不是引用,絕對的值語義對象。現假設傳入實參 int b = 1;
兩個函數都發生對實參對象的拷貝,拷貝以後,實參與形參分離,兩個函數都是在操做實參的拷貝,並且這個拷貝已經跟實參沒有任何聯繫了
這樣一來,這兩個函數對於 實參 來講,具備徹底相同的語義,並沒有本質區別,編譯器固然得制止這種行爲,否則顯得太弱智了...
(2)第二組和第三組的緣由其實相同,這裏我只解釋第2組
經過上面介紹,咱們知道第2組傳遞的參數具備對象語義. 現假設傳入實參 &b
第一個函數發生以下的實參拷貝: int* a = &b;
第二個函數發生以下的實參拷貝: const int* a = &b;(注:non-const 對象地址能夠賦值給 const 指針,隱式轉換. 只能 non-const ==> const 反之錯誤)
拷貝以後,實參與形參沒有分離,而是共同指向同一地址
可是,這兩個函數有不一樣的語義:
第一個函數能夠經過形參a 更改 實參b的值,好比:*a = 2; //這時 實參b所指元素變成了2
第二個函數不能經過形參a 更改 實參b的值,由於 const int* a(a is a pointer to const int),a所指元素爲 const,不可修改
這樣一來,這兩個函數對於 實參 來講,具備不一樣的語義(一個能夠改變實參,另外一個不能夠),有本質區別,編譯器得容許這種行爲.
3. const member function
爲何我會在將函數重載以後將這個呢? 哈哈,固然是有因果關係!
class Widget { public: int fun(int a) {...} int fun(int b) const {...} };
當咱們寫出這樣的代碼,編譯器居然沒有抱怨出錯,爲何? 函數名和參數列表(參數名稱和參數個數)都相同,爲何沒有違反函數重載規則??
編譯器處理後的結果:
注意 const member function 通過 "name mangling"以後多了一個"K".
首先 咱們來看看 const member function 的語義: const member function 不能修改調用該函數的對象
編譯器是怎樣保證這種特性的呢? 答案是 this 指針
第0部分 咱們講到 第一個fun 函數能夠改寫爲:
int Widget::fun(Widget* const this, int a); //this 是常指針
第二個fun 函數由於 const 的緣由,改寫爲:
int Widget::fun(const Widget* const this, int a); // this 是常指針 而且指向 常對象(不可修改)
看到這兩個函數改寫形式,是否是有點眼熟? 正是! 正是第2部分 函數重載中第2組狀況,因此這部分我就此打住
仍是多說一句吧:const member function 有兩種不一樣的語義(我的以爲很噁心),詳見 Effective c++ 第3條
4. static function
class Widget { public: int fun1() {...} virtual int fun2() {...} static int fun2() {...} private: int m1; static int m2; };
static 成員函數和成員數據都獨立於該類的任意對象而存在.不是類對象的組成部分
static 成員遵循正常的 public/private 訪問規則
(1)static member function
static 成員是類的組成部分但不是任何對象的組成部分,所以,static 成員函數沒有 this 指針
static 函數沒有 this 指針,因此 static 函數不能是 const member function 和 virtual function
(2)static member data
static member data 不屬於任何對象,因此不是經過構造函數進行初始化的
static member data 在類定義體外部定義,且在外部定義時進行初始化(static const int a = 0 能夠在類內定義. 真心不喜歡這種打補丁式的特性設計)
static double ClassName::StaticData = 0.0;
(3)調用規則
ClassName::StaticFunc(...);
static function 能夠直接使用該類的 static data,不能使用該類的 non-static data(由於全部 non-static data 必須經由 this 指針調用)
static function 與 non-static function 之間:
static function 屬於類,在該類實例化對象以前已經定義而且分配內存空間,而 static function 必須在類實例化對象以後才定義分配內存空間,故:
static function 調用 non-static function 是錯誤的
non-static function 調用 static function 是正確的
5. virtual function
class Base { public: virtual void func() const = 0; }; class Derived : public Base { public: virtual void func() const; }; Base* pBase = new Base; Base* pDerived = new Derived;
(1)對象的靜態類型:對象在程序中被聲明時所採用的類型
pBase聲明爲 Base*,因此 pBase 靜態類型爲 Base*(不論真正指向的對象類型)
pDerived聲明爲 Base*,因此 pDerived 靜態類型爲 Base*(不論真正指向的對象類型)
(2)對象的動態類型:目前實際所指對象的類型
pBase真正指向的對象類型爲 Base*,因此 pBase 動態類型爲 Base*
pDerived真正指向的對象類型爲 Derived*,因此 pDerived 動態類型爲 Derived*
virtual 函數系動態綁定而來,最終調用哪一份函數實現代碼,取決於發出調用的那個對象的動態類型
class Shape { public: virtual void draw() const = 0; virtual void error(const std::string& msg); int objectID() const; }; class Rectangle : public Shape {...}; class Elllipse : public Shape {...};
成員函數的接口老是被繼承,由於 public 繼承意味着 is-a,因此對base class 爲真的任何事情必定也對其 derived class 爲真
咱們如今將類的 non-static function 分爲三類:
a. pure virtual member function b. non-pure virtual member function c. non-virtual member function
(1) pure virtual member function:
pure virtual 函數有兩個最突出的特性:
a.必須在全部 Derived class 中重定義該函數
b.它們在 抽象基類裏面一般沒有定義
聲明一個 pure virtual 函數是爲了讓 derived class 只繼承 函數接口
(2) non-pure virtual member function:
non-pure virtual 函數是爲了讓 derived class 繼承 函數接口 和 函數缺省實現
函數爲 non-pure virtual 函數,代表 Base class 能夠選擇是否爲該函數提供一個缺省實現,而且讓 Derived class 繼承該函數缺省實現
a. 若 Base class 沒有提供缺省實現,Derived class 也沒有重定義該函數,固然會編譯錯誤
b. 若 Base class 沒有提供缺省事項,Derived class 重定義了該函數,調用函數的重定義版本
c. 若 Base class 提供了缺省實現,Derived class 沒有重定義該函數,則默認繼承該函數的缺省實現
d. 若 Base class 提供了缺省實現,Derived class 重定義了該函數,則觸發c++多態機制,最終調用的函數取決於發出調用的對象的動態類型
(3) non-virtual member function:
聲明 non-virtual 函數是爲了讓 derived class 繼承 函數接口 和 函數強制性實現(注意區別於 函數缺省實現)
實際上 non-virtual 函數表現出 不變性(全部Derived class 都不能重定義該函數,而且都共享 Base class 的同一份強制性函數實現)
6.To be continue
最初學c++時,最初的印象就是一個函數貌似有好多的關鍵詞能夠修飾, inline、const、static、virtual...
最大的迷惑就是這些關鍵詞到底誰和誰能夠在一塊兒,誰和誰不能在一塊兒?
慢慢的學習,陸續的理解了這些關鍵詞背後的語義以後,以爲也不過如此
在這裏我根據本身的理解,結合本文最後奉上一幅圖
ps:我沒有加入 friend function(這是c++封裝與訪問機制的妥協物,是一個典型的c++補丁式特性)
寫在最後的話:
每次我看到這幅函數圖,我都想仰天長嘆,喃喃自語: c++啊,想說愛你不容易...
歡迎你們批評指正,共同窗習...
轉載請註明出處,原文地址:http://www.cnblogs.com/wwwjieo0/p/3452930.html