計算機編程語言種類繁多,目前能夠查詢到的有 600 多種,常用的不超過 20 種,TIOBE 每個月都會發布世界編程語言排行榜,統計前 50 名編程語言的市場份額以及它們的變動趨勢。該榜單反映了編程語言的熱門程度,程序員可以據此來檢查自己的開發技能是否跟得上趨勢,公司或機構也可以據此做出戰略調整。
這些編程語言根據不同的標准可以分為不同的種類,根據“在定義變量時是否需要顯式地指明數據類型”可以分為強類型語言和弱類型語言。
1) 強類型語言
強類型語言在定義變量時需要顯式地指明數據類型,並且一旦為變量指明了某種數據類型,該變量以後就不能賦予其他類型的數據了,除非經過強制類型轉換或隱式類型轉換。典型的強類型語言有 C/C++、Java、C# 等。
下面的代碼演示了如何在 C/C++ 中使用變量:
int a = 100; //不轉換
a = 12.34; //隱式轉換(直接捨去小數部分,得到12)
a = (int)"http://c.biancheng.net"; //強制轉換(得到字符串的地址)
下面的代碼演示了如何在 Java 中使用變量:
int a = 100; //不轉換
a = (int)12.34; //強制轉換(直接捨去小數部分,得到12)
Java 對類型轉換的要求比 C/C++ 更為嚴格,隱式轉換只允許由低向高轉,由高向低轉必須強制轉換。
2) 弱類型語言
弱類型語言在定義變量時不需要顯式地指明數據類型,編譯器(解釋器)會根據賦給變量的數據自動推導出類型,並且可以賦給變量不同類型的數據。典型的弱類型語言有 JavaScript、Python、PHP、Ruby、Shell、Perl 等。
下面的代碼演示了如何在 JavaScript 中使用變量:
var a = 100; //賦給整數
a = 12.34; //賦給小數
a = "http://c.biancheng.net"; //賦給字符串
a = new Array("JavaScript","React","JSON"); //賦給數組
var 是 JavaScript 中的一個關鍵字,表示定義一個新的變量,而不是數據類型。
下面的代碼演示了如何在 PHP 中使用變量:
$a = 100; //賦給整數
$a = 12.34; //賦給小數
$a = "http://c.biancheng.net"; //賦給字符串
$a = array("JavaScript","React","JSON"); //賦給數組
$ 是一個特殊符號,所有的變量名都要以 $ 開頭。PHP 中的變量不用特別地定義,變量名首次出現即視為定義。
這裡的強類型和弱類型是站在變量定義和類型轉換的角度講的,並把 C/C++ 歸為強類型語言。另外還有一種說法是站在編譯和運行的角度,並把 C/C++ 歸為弱類型語言。本節我們只關注第一種說法。
類型對於編程語言來說非常重要,不同的類型支持不同的操作,例如
class Student
類型的變量可以調用 display() 方法,
int
類型的變量就不行。不管是強類型語言還是弱類型語言,在編譯器(解釋器)內部都有一個類型系統來維護變量的各種信息。
對於強類型的語言,變量的類型從始至終都是確定的、不變的,編譯器在編譯期間就能檢測某個變量的操作是否正確,這樣最終生成的程序中就不用再維護一套類型信息了,從而減少了內存的使用,加快了程序的運行。
不過這種說法也不是絕對的,有些特殊情況還是要等到運行階段才能確定變量的類型信息。比如 C++ 中的多態,編譯器在編譯階段會在對象內存模型中增加虛函數表、type_info 對象等輔助信息,以維護一個完整的繼承鏈,等到程序運行後再執行一段代碼才能確定調用哪個函數,這在《C++多態與虛函數》一章中進行了詳細講解。
對於弱類型的語言,變量的類型可以隨時改變,賦予它什麼類型的數據它就是什麼類型,編譯器在編譯期間不好確定變量的類型,只有等到程序運行後、真的賦給變量一個值了,才能確定變量當前是什麼類型,所以傳統的編譯對弱類型語言意義不大,因為即使編譯了也有很多東西確定不下來。
弱類型語言往往是一邊執行一邊編譯,這樣可以根據上下文(可以理解為當前的執行環境)推導出很多有用信息,讓編譯更加高效。我們將這種一邊執行一邊編譯的語言稱為解釋型語言,而將傳統的先編譯後執行的語言稱為編譯型語言。
強類型語言較為嚴謹,在編譯時就能發現很多錯誤,適合開發大型的、系統級的、工業級的項目;而弱類型語言較為靈活,編碼效率高,部署容易,學習成本低,在 Web 開發中大顯身手。另外,強類型語言的 IDE 一般都比較強大,代碼感知能力好,提示信息豐富;而弱類型語言一般都是在編輯器中直接書寫代碼。
為了展示弱類型語言的靈活,我們以 PHP 為例來實現上節中的 Point 類,讓它可以處理整數、小數以及字符串:
class Point{
public function Point($x, $y){ //構造函數
$this -> m_x = $x;
$this -> m_y = $y;
}
public function getX(){
return $this -> m_x;
}
public function getY(){
return $this -> m_y;
}
public function setX($x){
$this -> m_x = $x;
}
public function setY($y){
$this -> m_y = $y;
}
private $m_x;
private $m_y;
}
$p = new Point(10, 20); //處理整數
echo $p->getX() . ", " . $p->getY() . "<br />";
$p = new Point(24.56, "東京180度"); //處理小數和字符串
echo $p->getX() . ", " . $p->getY() . "<br />";
$p = new Point("東京180度", "北緯210度"); //處理字符串
echo $p->getX() . ", " . $p->getY() . "<br />";
運行結果:
10, 20
24.56, 東京180度
東京180度, 北緯210度
看,PHP 不需要使用模板就可以處理多種類型的數據,它天生對類型就不敏感。C++ 就不一樣了,它是強類型的,比較“死板”,所以後來 C++ 開始支持模板了,主要就是為了彌補強類型語言“不夠靈活”的缺點。
模板所支持的類型是寬泛的,沒有限制的,我們可以使用任意類型來替換,這種編程方式稱為泛型編程(Generic Programming)。相應地,可以將參數 T 看做是一個泛型,而將 int、float、string 等看做是一種具體的類型。除了 C++,Java、C#、Pascal(Delphi)也都支持泛型編程。
C++ 模板也是被迫推出的,最直接的動力來源於對數據結構的封裝。數據結構關注的是數據的存儲,以及存儲後如何進行增加、刪除、修改和查詢操作,它是一門基礎性的學科,在實際開發中有著非常廣泛的應用。C++ 開發者們希望為線性表、鏈表、圖、樹等常見的數據結構都定義一個類,並把它們加入到標准庫中,這樣以後程序員就不用重復造輪子了,直接拿來使用即可。
但是這個時候遇到了一個無法解決的問題,就是數據結構中每份數據的類型無法提前預測。以鏈表為例,它的每個節點可以用來存儲小數、整數、字符串等,也可以用來存儲一名學生、教師、司機等,還可以直接存儲二進制數據,這些都是可以的,沒有任何限制。而 C++ 又是強類型的,數據的種類受到了嚴格的限制,這種矛盾是無法調和的。
要想解決這個問題,C++ 必須推陳出新,跳出現有規則的限制,開發新的技術,於是模板就誕生了。模板雖然不是 C++ 的首創,但是卻在 C++ 中大放異彩,後來也被 Java、C# 等其他強類型語言采用。
C++ 模板有著復雜的語法,可不僅僅是前面兩節講到的那麼簡單,它的話題可以寫一本書。C++ 模板也非常重要,整個標准庫幾乎都是使用模板來開發的,STL 更是經典之作。
STL(Standard Template Library,標准模板庫)就是 C++ 對數據結構進行封裝後的稱呼。