各語言設計思想的獨特之處:C/C++、Java、Python、Objective C、Groovy

說明:本文章純屬個人觀點,不保證絕對正確,歡迎大家批評和指正,同時我自己也會對本文不斷的更新和完善。


本人學過多種語言,有的是工作需要,有的則是因興趣自學,我學習語言目的不完全是爲了使用它開發項目,也不是爲了裝逼,主要是學習各個語言的設計思想,雖說天下語言皆出自Lisp和Smalltalk(此句摘自高級程序員裝逼指南,這個是搞笑的文章,笑笑就行,別當真),但經過多年的分化改進,它們都有了自己獨有的東西,有自己獨特的設計思想和智慧,我是學的是它爲何要這樣設計,有什麼好處,學會這些思想和技巧有助力我自己設計軟件架構,如果把語言本身當成武功的形法,那他的設計思想就可以當成武功的心法,能提升自己的架構設計能力,當然,不用擔心我練的心法太多而走火入魔,因爲我不會使它的100%,只抽取好的思想。


C和C++:人人都說學C如果不學指針等於沒學(悲劇的是我在校主修數學專業的時候,老師就沒教我們指針,理由是認爲說我們聽不懂),這句話一點都不誇張,C確是因爲指針流行的,所以說C和C++最大的特點莫過於指針,指針是什麼,估計大部分人都知道,但考慮到沒過C的和我以前數學班的童鞋,我簡略介紹一下,所謂的指針就是...,唉不好描述,還是先畫個圖吧:


指針看到了吧(不要鑽牛角尖說針沒這麼大,是箭頭),所謂的指針就是一個指向內存的地址,計算機本質就是讀存儲器和寫存儲器(與外設交互設備也是往一個寄存器地址讀寫內容),假如你都可以直接向任何地址讀寫了,你說你還有什麼不能做,所以就造就了C的強大,操作系統和驅動都可以用C編寫。C++就是C的超集,完美兼容C,可以把C++當成C的升級版,英文翻譯是這樣的C Plus Plus,注意世上沒有C Plus,另外千萬別認爲C#是C++的升級版,他們倆沒有親戚關係的,C#的英文翻譯是C Sharp而不是C Plus Plus Plus Plus(這樣讀會累死人),C++是貝爾實驗室出來的,而C#是微軟做的東東,所有C#也只能用於Windows開發,你可能已經發現以前Windows開發主要用VC++,而現在的工作重心慢慢轉移到C#,牛B公司都想搞自己的語言,Apple公司有自己Objective C,不過Objective C與C++相差更是十萬八千里,等下詳述。回到正題,C++主要是在C的基礎上增加了面向對象支持,C++的面向對象直接多繼承和虛繼承,多繼承就是一個類可以繼承多個類,虛繼承是爲了防止多重繼承產生的二義性問題,具體還是問谷歌吧,一言難盡哪。另外,C++還有類指針是其它語言沒有的,在Objective C是用selector代替,Python、Java中是用引用,不過C++也有引用。其它還有泛型是C沒有的,Java也有,Python、Objective C等動態語言不需要泛型。還有C++的類方法不是默認是多態的,要加一個virtual關鍵字話,是因爲C++的多態是用虛表實現的,虛表是一個以指針爲元素表,通過改變指針來實現動態綁定,Java所有的函數都默認支持多態,Python和Objective更加恐怖,前一個是有個__dict__表記錄了所有方法,你調用的時候,就去找個那個方法就行,後一個所有的方法調用都叫做給類發消息,方法不存在都不會掛,等下詳述。


Java:Java是我學的第二門語言,用的次數不少於C++,我在其中學過很多思想,學這門語言使得我後來學習其它語言感覺很容易,同時也使我對C++的理解更深刻了。對我來說,Java最大的特點就是對面向對象的完美詮解,在學Java之前,我對面向對象的理解總是玄乎乎的,猶如水中月,鏡中花,依惜可見卻摸不着,夠不到,不能充分理解,經常用面向過程的思維去寫類,學了Java一兩個月之後,我終於伸手抓住了真正的花。衆所周知,Java中,一切皆類,main函數都要放到類中。

面象對象中最重要的特性包含繼承與多態,繼承很好理解,多態則不然,在學C++時,我知道了多態的實現機制卻還沒有理解多態,理解多態最好的工具就是接口,你可以把接口當成一個特殊的類,它不能有成員變量(除非靜態成員變量),所有方法規定不能實現,而其實現類(抽象類除外)必須實現所有方法,回顧一下多態的概念,即同樣的接口卻有不同的實現,我剛畢業時總不能理解這句話,面試問的時候就背誦這句和那個三角形和正方形的例子,別人多問一句就問倒了,舉個例子,輸入流,用這個好理解一點,輸入流的接口是InputStream,要使用輸入流的人員只需知道InputStream是幹嘛用的,有哪些功能即可,而不要關心他是什麼流,網絡流還是文件流或者模擬的Buffer流,更不用關心它是怎麼實現的,對不瞭解流的人補充一句:輸入流是一個抽象的概念,一般只能從前往後流,想流水一樣,讀過之後就能往回流了(也有特殊的)。比如說你要打印一個文件的二進制,如果文件在本地,就用文件流,如果哪一天把這個文件放到服務器上了,你就得用網絡流了,但無論你引用的哪種流,你從流中讀數據的代碼是一樣,代碼如下:

void foo() {
	// 文件流
	File file = new File(FILE_OUT_SUBDIR_NAME + "/test.xml");
	FileInputStream fileInputStream = new FileInputStream(file);

	// 網絡流
	URL url = new URL("http://192.168.1.*/directory/to/file.txt");
	URLConnection conn = url.openConnection();  
	InputStream netInputStream = conn.getInputStream();
	
	printStream(fileInputStream);
	printStream(netInputStream);
}

void printStream(InputStream inputStream) {
	byte[] byte1 = new byte[1];
	while (inputStream.read(byte1) != -1) {
		System.out.printf("%02X", byte1[0]);
	}
}

從這種代碼裏你還能從new的時候看到具體實現類,如果前面一部分封裝成一個函數,你就連是哪個類都看不到了,Java裏有很多用工廠模式對創建對類進行了封裝,如加密用的JCA,你傳入一個算法的名稱就會獲取相應的類,但你只知道他的接口,看不出具體實現類。設計模式裏更是充分的利用了多態,比如State模式,可以大大減少了if-else出現的次數,囉嗦一句,其實State模式是從另一個維度去看一個程序的邏輯,即把縱向邏輯改爲橫向邏輯,從而把公共的if-else提取出來,這個主題本文暫不詳述,後續推出新文章專門討論設計模式。接口在用界面時主要應用在事件監聽器,其實作用就相當於C裏的回調,所以反過來,意味着C也可以實現面向對象,最好的實例就是Linux內核,回調就是函數指針,再一次證明了指針的強大。

Java還強大的標準庫,我實習的時候問一個C++老員工Java是什麼樣語言,他跟說Java很簡單,就是很多包調來調去,做什麼功能都有相應的包,我估計很多人初識者會這麼認爲,其實他們說的一堆包就是Java強大的標庫,C++面試官司可能會問你懂不懂STL啊,知不知道什麼是Vector和Set啊,Java面試官可不會問這個,因爲這個對Java來說是必須的,最基礎的,我想剛學Java就要用ArrayList吧,也有人因此說做C++好,什麼要自己實現,所以學得多一點,Java什麼都封裝好,沒什麼學的。我不太認同這種觀點,因爲沒必要重複造車輪,這些前人都寫好,直接用就好了啊,你就可以有更多的時間想想你自己軟件的如何設計,話說回來,如果是爲了學習的話,其實這些我們在學校就做過練習寫過(如果你在校沒寫過,也可以業餘時間寫寫啊),只是寫得沒人家好,即便你再寫一篇也未必寫得有人家好,所以每個人都去寫一篇,意義不好,還有一種情況,如果你實在想去寫標準庫,那就寫好你現在的軟件,練好基本功,先學習標準庫中別人優秀作品,等將來羽翼豐滿時,你會有大把機會寫庫給別人用,但最好要理解其設計思想,因爲庫的實現是很簡單的,主要是定出合理的API很難。

Java還有一個牛B特性就是反射機制,它可以通過字符串來獲取類的屬性,包括方法和變量,這個機制使得Java的靈活性強大了不止一倍,不過凡事都有兩面性, 在它帶來靈活性的同時,也破壞了封裝,不過還是要用,但不要輕易用,Spring的AOP都知道吧,強大吧,方便吧,我不用看它的代碼也能百分之一百確定是用反射機制實現。


Python: python是我最熟悉的一門動態語言,它很好的詮釋了什麼叫動態,其實在接觸python之前,我接觸過的動態語言還有shell腳本語言和Objective C、但這兩種還不能讓我深刻理解什麼叫動態,可能是它們不夠動,python如此好的動態性以致於其它後來創造的語言都借鑑了它的實現,如groovy,他的創造者公開說了是因爲python才使得他創造了groovy。python的動態性得益於它的一種設計思想:一切皆對象(Java是一切皆類,看來走極端也可以有出息,嗯~呵呵~)。這樣說你可能還不能懂,再詳細一點就是,python中,字符串,元組,就連類和函數都是對象,你說神奇不神奇。python的對象裏都有一個__dict__屬性,是字典類型,這種類型你可能沒聽過,不過你可以當成是Java或C++裏的Map或是Shell裏關聯數組,記錄這個對象所有屬性,包括方法和屬性,調用時,解析器只要搜索一下__dict__就可以了,更神奇的是你還可以通過賦值改變這個__dict__的內容來達到改變這個對象,比如添加字段,這樣有點Java的反射機制吧,不過python更靈活,呵呵。所謂的動態,主要是指變量的動態,python中所有變量都沒有類型,其實可以看成是一個引用或指針,每一個變量只是指向一個對象,而且它可以隨時指向另一個對象,包括函數對象,這種靈活得益於python裏對象的設計。

python的標準庫也挺強大:意味着可以用很少python代碼實現某個功能,而用其它語言則要用很長的代碼,這也成就了python簡潔的威名。我學python的導火線(我很早就打算要學python,但平時工作很忙,也沒有導火線)就是我同事用了一點python代碼寫一個post JSON流到服務器的測試腳本,於是我恨下決心,第二天就是開始擠出時間學python,另外,我曾經用20行以下代碼寫過一個圖片縮放的腳本,簡單的無法想象,做爲分享,我還是貼上來吧,還有一個發送郵件的腳本也一起貼了吧。

縮小圖片的(你可能要下載PIL模塊):

from PIL import Image
import os
import os.path
import sys
path = sys.argv[1]
small_path = (path[:-1] if path[-1]=='/' else path) +'_small'
print small_path
if not os.path.exists(small_path):
    os.mkdir(small_path)
for root, dirs, files in os.walk(path):
    for f in files:
        fp = os.path.join(root, f)
        img = Image.open(fp)
        w, h = img.size
        img.resize((w/4, h/4)).save(os.path.join(small_path, f), "JPEG")
        print fp
發送郵件的:
import smtplib

username="[email protected]"
recipient=username
passwd="123456"
host='smtp.gmail.com'
port=587

body_of_email="body of email"

print 'initializing...'
session = smtplib.SMTP(host, port)
session.ehlo()
session.starttls()
#session.ehlo()
print 'login...'
session.login(username, passwd)

headers = "\r\n".join(["from: " + username,
                       "subject: test",
                       "to: " + recipient,
                       "mime-version: 1.0",
                       "content-type: text/html"])

# body_of_email can be plaintext or html!                    
content = headers + "\r\n\r\n" + body_of_email

# msg = "To:username@domain\r\nFrom: username@domain\r\nSubject: test \r\n\r\ntest mail\r\n"
print 'send...'
session.sendmail(username,username, content)
print 'close...'
session.close()
exit()

Python有良好的擴展性,python每個文件都默認是一個模塊,你只要import就行了,模塊即可以單獨運行還可以當行庫,這是因爲python解釋器根據會向模塊傳一個參數。對Python來說,Java的JNI沒什麼稀奇的,python也可以支持調C/C++,跟Java更是可以無縫融合,所以很多服務器使用python和Java混合開發。

python有對函數式編程的也有支持,關於函數式編程另查資料,這裏不詳述,python支持加量化(Curring)和偏函數,這兩者不是語言的機制,而是像“繼承與多態”一像是一種概念,加量化是利用單參數的函數轉換成多函數,偏函數是指固定函數的部分變量後再調用,這個“偏”字的含義跟高數裏的偏微分差不多,都是指先固定一部分變量,這兩者的區別具體可參考此文,它們的實現主要是通過閉包、lambda和functools模塊來實現的。聽說Java8也要加閉包特性,不過當下的Java可以的用匿名類實現類似的功能,就可以多寫個接口。

Python的裝飾符(Decorator):這也是個很好的功能,可以支持所謂的切面編程,像Spring一樣,其它還有在python裏,一個函數的執行,可以暫停,這個比較新鮮,一般人都沒聽過,也告訴我們平時想問題時要勇於打破常規,think outside the box,這個思想的實現是用了關鍵字yield,如果好奇就學學python吧。

python還有一點比較獨特,就是它使縮進來表達語句邏輯,不允許使用大括號。如:

if True:
    print 'True';
else:
    print 'False'
    print ''hello"
輸出是:
True
而沒有“hello”,如果是這樣:
if True:
    print 'True';
else:
    print 'False'
print ''hello"
輸出則是:
True
hello
這個特點被官方文檔和一些書藉吹得在花亂墜,引用python核心編程第3.1.4節的一段:“使用縮進對齊這種方式組織代碼,不但代碼風格優雅,而且也大大提高了代碼的可讀性。而且它有效的避免了"懸掛 else"(dangling-else)問題,和未寫大括號的單一子句問題。(如果 C 語言中 if 語句沒寫大括號,而後面卻跟着兩個縮近的語句,這會造成不論條件表達式是否成立,第二個語句總會執行。這種問題很難調試,不知道困惑了多少程序員。)”,這段主要是說:一、縮進增加易讀性;二、防止C那樣的“懸掛else”問題。但我不認同他的觀點,關於第一點,我認爲無論什麼語言,給代碼加上良好的縮進是一個程序員都基本的職業素質,而且幾乎所有人都能做到這點,所以python創建者白擔心了;關於第二點,也是大部分人都聽說過“懸掛else”問題,而且一般人都會習慣性給else語句加上大括號,這個問題便很難出現,反而python這種機制困惑了我不止一次,第一種麻煩是如果你無意間刪除或增加了一個Tab,編譯也會正常,只是運行的時候會很怪;第二種麻煩是有時候在網上copy一段代碼,那個縮進很有可能不對,但是你檢查不出;第三種麻煩,你copy了一段代碼,看起來很正確,卻編譯不過,報的錯是縮進不一致,原因是縮進很起來是一樣,但有的是4個空格,有的是一個tab,這個時候你就得找一個能顯示空格和Tab的編輯器,睜大眼睛一個一個找了。


Objective C:初學Objective C的時候,感覺它是語言界面的奇葩,最大特點就是中適號語法了,當人家的方法調用都是點號的時候他用的是中括號,不,它沒有調用的說法,它是發消息,我當時就很驚訝,原來還可以這麼搞啊,真是另闢蹊徑啊,Objective C所有的方法“調用”都給對象發消息,對象收到消息可以不響應,還不會掛,也可以把消息發給空對象,這正是打破常規,這相當像喬幫主的風格(但不是他創建的,他只寫了個庫而己,裏面的標識符都以NS開頭)。說到空,還有一件奇怪事實,Objective C裏有新加了兩種空,nil和Nil,一個表示空對象一個空類。除此之外還有區別的是布爾型使用BOOL、YES和NO關鍵,SET表示一種類型類似於C++的類指針,protocol類型類似於Java的接口,還有一種叫catogory的,使用他可以動態修改和擴展類而不必使用繼承。Objective C2.0有內存管理不過是編譯器支持的,所以不會影響性能,不像Java有個垃圾回收器定期運行。另外Objective C很多關鍵以@開頭,如申明和實現類分別用@interface和@implementation,也許你會有一個疑問,爲何有這麼多新出的關鍵,原因是因爲Objective C也是C的超集,它爲了不跟C的關鍵字衝突才新加了這麼多奇怪的關鍵字,C++和Objective C都是C的超集,而且好像是同年出生的,但兩者卻有着本質,所以不要認爲懂C++就好學Objective C,完全不是,它倆完全出於不同的語系,有不同的父親,C++是Simula系,而Objective C是Smalltalk系的,C++是靜態語言,Objective C是動態語言,它們有着完全不同的設計思想,但都有自己獨特的智慧。網上有個外國人寫了一個C++和Objective C的比較文檔,不過是英文版的,我有時間會翻譯一下放到本博客(blog.csdn.net/yanquan345)。這裏又說明了C強大到可以創造一門新語言。

Objective C還有一個改進的地方,叫property,都知道面向對象的封裝,所以Java建議用setter和getter去操作成員變量,如果是這樣,那假如一個類設計爲存儲數據,當成C的結構體來用,那這個類可能得很多setter和getter,雖然Java的強大IDE可以自動生成代碼,但你看這個類的時候會有點頭大吧,在Objective C裏就不用這樣了,只要你用property修飾一下,說明那裏成員變量可以被set和get就可以,頓時代碼就簡潔了許多,property其實也是自動生成setter和getter代碼,但它是編譯器生成,源文件是保持簡潔的。


Groovy:前面也提到了的誕生離不開python,按照其創造者的說法,他是因爲喜歡python有而Java中缺少的一些靈活的特性,才創造了groovy,這些特性主要包含動態性,對元數據的支持,元數據可以有童鞋不懂,我多說一句,元數據指的就是類的屬性,因爲一個類也有很多屬性,如類名,成員列表等,這些都是元數據,所以python和Objective C都有元類,即類的類,普通則變成了元類的對象。所以groovy和python一樣都很靈活,groovy最大的優勢是可以直接在JVM中運行,可以不編譯,也可以編譯成.class文件,可想到做Java開發的時候可用groovy做一些簡潔的事,我曾想讓groovy做Android開發,但當時失敗了,不過我失敗,可能會有人成功實現這個想法,也有可能我以後會成功。groovy可能是大家聽得最少的,我也是因爲研究Google在IO大會發布的新Android開發IDE(基於IntellJ的)才接觸到的。groovy的元數據有強大用處,就是很方便開發領域語言,領域語言我也要解釋一下,我們說的C++和Java都是通用語言,就是可以做出任何產品,領域語言只能用在特定領域,一般有個解釋器,其實一般都接觸過領域語言,像Makefile,Ant、Maven腳本都是領域語言,你不能用Makefile腳本實現一個QQ吧,groovy對領域語言的支持最好的例子,就是gradle,一種新的自動化構建工具,號稱既有Ant的靈活,也有Maven的強大依賴管理和標準規範。還有個論據是Groovy的強大的元數據使用Java的反射機制和Spring的AOP成了浮雲,在Groovy裏,你一不小心就用上了AOP,Groovy語言簡單易學,它完全兼容Java語法,Java代碼可直接運行,但它可以更簡潔,特別相對學過python的童鞋更容易學,官網有完備的文檔,還算是易懂的英文,因爲詞彙量比較少,建議大家去看,一定會驚訝你。


後言:Lisp不打算討論了,過時了,討論也沒什麼意義了,我自己都差不多忘了。另外,暫不討論JavaScript,一是因爲其特殊之後少,二是本人還不很熟悉,也許後續會加上去,如果有很多童鞋希望討論,那我就豁出吃飯時間也定會加上。


參考資料:
Effective Java, 2nd, Joshua Bloch

Java Cryptography Architecture (JCA)

GoF Design Pattern

Python核心編程,第二版,Wesley.J.Chun,英文版是:Core Python Programming, 2nd, Wesley.J.Chun

From C++ To Objective-Cversion 2.1, Pierre Chatelier

Programming in Objective-C, 4th Edition

Groovy Guide



說明:本文章純屬個人觀點,不保證絕對正確,歡迎大家批評和指正,同時我自己也會對本文不斷的更新和完善。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章