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

2022-08-17 06:09:12 字數 3294 閱讀 3305

對於靜態型別語言,其本質目標在於恰當地運算元據,得到期望的值。具體而言,需要:

(1)定義資料型別

你定義的資料是什麼,是整形還是浮點還是字元。該型別的資料可以包含的值的範圍是什麼。

(2)定義操作的含義

操作是嚴格資料型別相關的。操作表明了對了乙個具有特定型別的資料,執行操作後產生什麼樣結果。 

c++就是乙個典型的靜態型別語言。在c++中,無論是"資料型別"還是"操作",都分為內建的和自定義的。

c++的內建資料型別包括:

(1)基本內建型別

整形、浮點、布林、字元....

(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:基於該觀點理解過載

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

自定義操作,包括我們定義的普通函式、成員函式、過載的操作符、建構函式定義的隱式型別轉換、拷貝建構函式定義的拷貝操作...

未完待續

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

對於靜態型別語言,其本質目標在於恰當地運算元據,得到期望的值。具體而言,需要 1 定義資料型別 你定義的資料是什麼,是整形還是浮點還是字元。該型別的資料可以包含的值的範圍是什麼。2 定義操作的含義 操作是嚴格資料型別相關的。操作表明了對了乙個具有特定型別的資料,執行操作後產生什麼樣結果。c 就是乙個...

關於UML的一些隨想

eddiong uml note 關於uml的文章已經有很多博主做過非常詳細的介紹,並且失眠上有很多書籍可供參考。在前面給出一些可供閱讀的文章和書籍,此書給出博主剛接觸uml時的兩處入門資源。有需要加深閱讀的朋友可以看一下以供參考。推薦閱讀 博主 物件導向思考 寫的ea 系列 書籍 uml2 基礎 ...

最近開發的一些隨想

1.關於區塊鏈是乙個加密資料匯流排或加密佇列的想法,區塊鏈相關專案有微眾銀行的weevent,非區塊鏈專案給我啟發的是rocketmq,裡面涉及到的dledger,採用raft演算法,還有tidb也採用raft演算法和hyperledger fabric一樣的共識演算法,所以其實區塊鏈從這個角度只是...