C++語言體系設計哲學的一些隨想(未完待續)

對於靜態類型語言,其本質目標在於恰當地操做數據,獲得指望的值。具體而言,須要:ios

(1)定義數據類型數組

你定義的數據是什麼,是整形仍是浮點仍是字符。該類型的數據能夠包含的值的範圍是什麼。函數

(2)定義操做的含義指針

操做是嚴格數據類型相關的。操做代表了對了一個具備特定類型的數據,執行操做後產生什麼樣結果。 對象

 

===========================================blog

 

C++就是一個典型的靜態類型語言。在C++中,不管是"數據類型"仍是"操做",都分爲內置的和自定義的。繼承

C++的內置數據類型包括:編譯器

(1)基本內置類型string

整形、浮點、布爾、字符....io

(2)STL庫定義的類型

例如經常使用的iostream、string、迭代器......

 

此外C++和定義了複合類型機制,包括全部類型的引用、指針、數組,他們能夠做爲一個完整數據類型的一部分。

順便提一下,頂層/底層const、static、volatile...等修飾符,定義了數據的其餘屬性,這些屬性也能夠是一個完整數據類型的組成部分。

 

自定義類型,最經常使用的就是class、struct、union定義,還有函數簽名,固然也可使用複合類型機制定義本身類的引用、指針、數組等。

 

==============================

 

 重點在於,不管是變量仍是常量,必須屬於某一特定的數據類型。由於操做只有基於精確的數據類型,其定義纔有了肯定的含義(在編譯原理中叫作「語義」)。也就是說,在一個肯定的操做集合中(例如C++語言內置的全部操做),只要給一個變量賦於了數據類型,這個變量能夠執行的操做也就肯定了。定義變量nVal爲int類型,那麼nVal就能夠參與加減乘除、關係運算、拷貝、轉換爲double、傳遞給函數形參、做爲數組的下標.........

 

C++的「操做」,其含義很是普遍。其實C++語言已經經過成員函數、操做符重載、函數重載、構造函數定義的隱式類型轉換...等機制,代表了 C++做爲一個靜態類型語言的本質:屬於特定類型的數據,加上其上的操做。能夠這樣理解,任何一個操做,本質就是函數,操做符在C++語言內部也是被看成函數來看待的(這也能解釋C++提供operator操做符重載機制的動機);類的成員函數、友元函數,也是對類自己這個「數據類型」的操做。

更進一步,操做自己也是一種特殊的數據類型。能夠定義函數的指針、函數的數組,成員訪問(->*,.*),只是能夠被看成數據類型來使用的機會很少,也被語言自己限制了。

 

C++的內置操做不太好理解,實際上咱們經常使用的語言機制都是「操做」,具體包含了:

(1)各類各樣的操做符

算術操做符、關係操做符、位運算、取地址、單目運算、解引用、數組元素訪問..... 

 

(2)拷貝操做

拷貝初始化、列表初始化(C++ 11)、賦值運算、函數傳參、函數返回值、類型轉換執行的臨時變量拷貝......等其餘非引用場景

 

(3)數據類型轉換

類型轉換也是一種操做。對於普通的操做,執行前需先匹配要操做的數據的類型。現實中,不可能總能保證在代碼裏提供類型嚴格匹配的數據,所以類型轉換也是C++語言很是廣泛的操做。

該如何理解這樣的操做呢?舉個例子,例如: 

int nVal = 42;

double fVal = 3.14;

double fValTwo;

fValTwo = fVal + nVal ; // nVal類型提高爲double

 上述代碼最後一行的相加操做將執行類型提高。從編譯器的角度看,此時將生成一個匿名的變量,變量的類新和須要匹配的類型(double)相同,以後執行int至double的類型轉換操做,操做結果保存在這個匿名變量中。以後纔會執行「+」操做。也就是說,若是選定了操做,那麼就會期待若干數據類型徹底匹配的操做數,爲了知足這個條件,系統會執行類型轉換。

對於賦值操做,該操做會期待=右邊操做數的數據類型和左邊徹底匹配,此時也會和上述相同,生成匿名變量,執行類型轉換。準備工做完成後,再執行"="操做。

函數的調用也是基於相同的原理,即實參類型和形參類型的匹配。

... ...

C++語言內部定義了異常複雜的類型轉換規則(操做),只不過大多數對使用者是透明的。例如:

整形提高 - char、short、bool會先轉換爲int;

類型提高 - 防止精度損失;

類型下降 -有精度損失,常見於拷貝操做。拷貝操做是將源對象嚴格匹配目標對象,所以不會有算術操做裏的「整形提高」。拷貝包括了拷貝初始化、賦值運算、函數調用實參賦給形參

非bool值均可以轉換爲bool,相反則轉換爲0/1;

任意類新指針均可轉換爲void*;

數組在不用於decltype、sizeof、typeid、取地址&的狀況下,會自動轉換爲指向第一個元素的指針。

非底層const向底層const的轉換 - 指向常量的引用和指針能夠綁定到很是量上,和內置類型的提高與下降不一樣,底層const向非底層const的轉換是非法的

子類向基類的轉換 - 基類指針/引用能夠指向子類,這是多態的基礎。和底層const同樣,相反的轉換是非法的

... ...

-

PS:關於底層const和繼承體系類型轉換的單向性:

本質而言,一個數據的數據類型,能夠執行的操做的集合越小,該數據能夠引用/綁定的對象類型越廣。例如:

數據類型A,能夠執行operA - operZ 共26個操做。數據類新B,能夠執行的操做是A的子集,好比operH-operN。那麼,B的引用/指針能夠綁定到A(B的引用/指針能夠接受A/A的指針賦值),相反則是非法的。

const int *不能修改指向的int,而int *能夠,也就是說,數據類型const int *的操做範圍比int *要小,因此const int *能夠綁定到int*指向的對象(本質上是指const int *能夠接受int*賦值)。
在繼承體系中,基類的操做範圍確定是小於子類的,因此 基類指針指向/基類引用 子類的合法的。

形成這一切的緣由就在於,對靜態類型語言,編譯器始終「執拗」地、「自覺得是」地按照其靜態聲明類型,來決定一個操做是否合法,而不去管這個對象實際指向的類型。能夠想象,編譯器「自覺得是」地認爲經過int *能夠改變這個int,而無論這個int*實際指向的是const int,若是容許底層const向非底層const轉換,就會帶來衝突。

-


PS:基於該觀點理解重載

函數重載、操做符重載的本質,是用同一個名字定義了多個操做。結果是在編譯階段引入了一個肯定具體操做的過程 - 從候選操做中選出最匹配的操做。而上述「類型轉換」操做則是在運行階段進行的。

 

 

 

 

自定義操做,包括咱們定義的普通函數、成員函數、重載的操做符、構造函數定義的隱式類型轉換、拷貝構造函數定義的拷貝操做...

 

 

 

 

未完待續

相關文章
相關標籤/搜索