C++ Primer Plus 讀書筆記(第8、9章)

來源:互聯網
上載者:User

第八章 函數探幽

內嵌函式的選擇

如果執行函數的編譯代碼的時間比處理函數調用機制的時間長,則節省的時間將只佔整個過程的很小一部分。如果代碼執行時間很短,則內聯調用就可以節省非內聯調用使用的大部分時間。總之:內聯用在定義那些被經常調用且短小的函數,例如在某個迴圈中調用一個函數。

內嵌函式聲明是在函數原型或者是定義前加上inline。通常的做法省略函數的原型,將整個定義放置在調用函數之前,這樣形式上更加說明這個函數的特別。

 

引用變數

C++相比C增加了一種複合類型:引用變數。引用變數就是定義某個變數的一個別名,可以看做是一個偽裝的指標。通常用在函數的參數和函數的傳回值,前者能夠使得大型資料的傳遞不產生一個副本且較指標編碼更加簡潔;後者能夠確保類似cin>>a>>b;這樣的語句變得合乎文法規則,通過先計算”cin>>a”返回一個cin的引用,那麼這個函數”cin>>a”就是cin的一個別名,因此在這之後又可以接上>>b。

正由於引用變數的存在,因此C++的函數參數傳遞就不完全是按值傳遞,多了按引用傳遞,當一個函數的參數聲明為參考型別應儘可能的將這個聲明加上const,因為單單引用可能改變原來變數的值,加上const將更加保險,如果修改的話,能夠在編譯時間就發現錯誤。當我們將一個運算式傳遞給引用時將報錯:傳遞的值不是一個左值,然而加上const後就可以了,編譯器將自動定義一個匿名的變數來儲存這個運算式,再將這個變數傳遞給函數。

const引用變數在兩種情況下將產生臨時變數:

1、實參的類型正確,但不是左值;

2、實參的類型不正確,但可以轉換為正確的類型。

兩個相同定義的匿名結構體不能夠視為相同的變數類型。

如果定義了這樣的一個函數 int & fn(int &x, int y) {return x;}那麼就存在這樣的調用 fn(a, b) = 3;那麼等價於a = 3。返回引用的函數實際上是被引用變數的別名。

不應讓一個函數返回一個臨時變數的引用,這樣非常危險。同樣如果一個函數的引用傳回值能加上一個const又不影響結果的話,最好加上。通常,應避免在設計中添加模糊的特性,因為模糊特性增加了犯錯的機會。

如果某個對象被聲明為常量,那麼只用常成員函數能夠被調用。

C++在調用函數時,單憑從調用函數的語句看無法判斷函數原型是否為引用,這也算一個小小的缺憾。

 

函數重載

函數重載時C++的一個重要特性,也正是存在函數重載,後面就又會出現函數的匹配選擇問題。C++允許程式員定義名稱相同的多個函數,只要這些函數的特性不同(在名稱相同的情況下,參數不相同)即可。

一個一勞永逸的方法是定義個函數模板(參數化型別 parameterized types),那些複雜的庫函數大部分都是利用模板實現的,且模板套模板相當複雜。正是由於存在著這麼多重名的函數所以在調用時,編譯器將進行一個重載解析,讓最適合的被調用,如果有兩個或以上的函數都是“最優的”,那麼編譯器將報錯。

函數模板並不產生函數的定義,而只是在調用的時候再根據調用提供的類型產生相應的函數。也就是給編譯器看的,編譯器機智的協助我們減少代碼量。

關於函數模板又有幾個名詞要知曉:執行個體化、顯式具體化。前者有分為隱式和顯式。三者共稱為具體化。對於一個函數模板例如:

template<typename T>

T max(T a, T b) { if (a > b) return a; else return b; }

如果直接調用max(4, 5)那麼就是隱式執行個體化,而max<double>(4, 5)就是顯示執行個體化。

如果重新定義如下函數template<> max<int>(int a, int b) {return 10000;},那麼max(4, 5)就是顯式具體化,因為重新給定了這個調用的一個函數定義,而不僅僅是模板的執行個體化。

 

名稱修飾

這麼多的同名函數在編譯器看來其實是不一樣,函數名在編譯後將發生變化,函數參數列表的所有資訊都將被加入到新的函數名中,這個規則各個不同編譯器實現不同。

 

重載解析:

重載解析分為以下幾步:

第1步:建立候選函數列表。其中包含與被調用函數的名稱相同的函數的模板函數。

第2步:使用候選函數列表建立可行函數列表。這些都是參數數目正確的函數,為此有一個隱式轉換序列,其中包括實參類型與相應的形參類型完全符合的情況。例如,使用float參數的函數調用可以講該參數轉換為double,從而與double形參匹配,而模板可以為float產生一個執行個體。

第3步:確定是否有最佳的可行函數。如果有,則使用它,否則該函數調用出錯。

 

最複雜的過程就是確定是否有最佳的可行函數的過程。

對於函數模板C++98有如下的規定:非模板函數由於具體化和常規模板,具體化優先於常規模板。另有:(優先順序從高到低)

1、完全符合,但常規函數由於模板。

2、提升轉換(例如char和short自動轉換為int,float自動轉換為double)。

3、標準轉換(例如,int轉換為char,long轉換為double)。

4、使用者定義的轉換,如類聲明中定義的轉換。

其中完全符合中又有一些是無關緊要的轉換:

Type  ->  Type &

Type & -> Type

Type [] -> Type *

Type (argument-list)  ->   Type (*) (argument-list)

Type -> const Type

Type -> volate Type

Type  * -> const Type *

Type * -> volate Type *

之所以叫做無關緊要的轉換就是因為左邊的實參代入進去的話,那麼聲明為左右兩邊兩種參數的形參的函數時在調用的優先順序是相同的。例如Type -> Type & 當我們定義有fn(int x)和fn(int &x),此時調用fn(a),那麼編譯器將報錯,因為從Type -> Type &的轉換時無關緊要的。它們都是完全符合。

 

然而這裡又有存在兩個函數都完全符合時,仍可完成重載解析。

1、指向非const資料的指標和引用優先於非const指標和引用參數匹配。

2、如果一個函數時非模板函數而另一個不是,在這種情況下,非模板函數將優於模板函數(包括現實具體化)。

3、如果兩個完全符合的函數都是模板函數,則較具體的模板函數優先。例如:

存在template<typename T> T fn(T a , T b) {...} 和 template<typename T> T * fn(T * a, T * b){...} 則如果傳遞一個兩個指標來調用函數時,則後一個模板更加具體,將會被調用。

 

 

 

 

第九章 記憶體模型和名稱空間

該章詳細介紹了關於一個程式的記憶體管理機制、代碼是如何對記憶體進行影響以及程式員如何利用語言來達到預期記憶體配置目的。

 

項目

一個項目將由多個單元(檔案)組成,各個檔案能夠單獨編譯,最後IDE將這些檔案進行一個連結,多檔案的引入帶來了更多的麻煩。檔案可以這樣組織:

標頭檔:包含結構聲明和使用這些結構的函數的原型。

原始碼檔案:包含與結構有關的函數的代碼。

原始碼檔案:包含調用與結構相關的函數的代碼。

這似乎和調用庫函數非常相似,我們通過包含標頭檔(標頭檔裡有我們使用函數的原型)使得所寫的來源程式能夠通過編譯,然後通過連結庫函數來調用函數的定義。

 

標頭檔

標頭檔應該僅包含以下內容:

函數原型。

使用#define或const定義的符號常量。

結構聲明。

類聲明。

內嵌函式。

標頭檔也不會被編譯,所以也不會有*.o的檔案產生,標頭檔就是用來包含在另外一個.cpp的檔案中為其提供調用函數的原型,讓檔案通過編譯,然後再通過連結程式在其他.cpp檔案產生*.o檔案中找到某個函數的定義。

 

程式與記憶體變化的幾個名詞

儲存持久性、範圍、串連性

 

儲存持久性

儲存持久性分為:自動儲存持久性、靜態儲存持久性、動態儲存裝置持久性、線程儲存持久性(C++11)。其強調的是某塊記憶體的生命週期(從被使用到被釋放)。

 

範圍

即一個變數在一個檔案中能夠使用的範圍。先介紹聲明地區:聲明地區分為檔案和代碼塊,這個很好理解。潛在範圍:從定義該變數位置開始到聲明地區結束為潛在範圍。那麼範圍就是排除那些被覆蓋了的潛在範圍。

 

連結性

連結性就是一個變數能否通過連結被其他檔案中的引用申明所使用。

 

單定義規則

單定義規則是指一個變數只能夠被定義聲明一次,但是可以被引用聲明多次。引用聲明的形式是extern type typename 的形式,其餘形式都視為定義聲明,包括extern type typename = value;的這種帶有迷惑性的形式。

 

5種變數儲存方式

 

C++11 auto register

C++11中auto和register關鍵字的意義發生了改變,後者取代了前者的含義,而前者變為自動類型推斷。

 

cv-限定符、mutable

const 以及 volatile就是cv-限定符,前者不必多說,後者的意義在於告訴編譯器該變數可能在代碼中沒有修改記憶體的情況下發生變化,防止編譯器做相關的最佳化。

mutable,用來修飾成員變數。使用mutable修飾的成員變數在整個結構為const限定後仍可以變更。

 

函數的連結性

函數也具有連結性,且預設為外部變數,連結性為外部,須加上static顯示的聲明連結性為內部。這個特性也就滿足了標頭檔中只要有一個引用聲明一個函數即可,也就是我們常說的函數原型。

 

new運算子和定位new運算子

new運算子通過指標來靈活使用動態記憶體。定位new運算子需要包含<new>標頭檔。其能夠指定一個開始地址然後聲明一塊記憶體,可以用來再次初始化已經開闢出來的記憶體塊。

 

名稱空間

名稱的出現將使變數的命名更加的放心,名稱空間可以是全域的,也可以位於另一個名稱空間中,但是不能位於代碼塊中。通過名稱非代碼塊中定義變數都有了一個名稱空間,全域變數為::,因此通過加上範圍解析運算子能夠方位各個不同名稱空間定義的相同名字的變數。C#這門語言乾脆為每個類都要定義一個名稱空間。另外使用using聲明比使用using編譯指令更安全。using聲明斷言該處一定可以使用名稱直接存取,若有衝突則報錯,而using編譯指令在發生衝突時將被覆蓋。using聲明同樣將匯入某個函數的所有重載版本。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.