- 「MoreThanJava」 宣揚的是 「學習,不止 CODE」,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Java 基礎的一個總回顧,旨在 「幫助新朋友快速高質量的學習」。
- 當然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯」 的朋友,歡迎 「關注 + 留言 + 分享」,文末有完整的獲取連結,您的支援是我前進的最大的動力!
知乎問題:如何讓不懂程式設計的人感受到程式設計的魅力?
Part 0. 前言
問一個類似的問題:「如何讓不懂籃球的人感受到籃球的樂趣?」
很明顯,答案取決於人。
對於某些人來說,編碼可能是乏味而艱鉅的,但對於有些人則是非常有趣且有益的。
通常情況下,編寫好程式碼之後,我們很難評判編碼的過程是無聊還是有趣的。
但是我們可以通過更好地瞭解 「計算機程式設計是什麼」,因此您可以自己來評判這是否讓你感到樂趣。
就當代的發展水平來說,只要有電的東西就會涉及到編碼;
Part 1. 計算機發展簡史 | 解決實際的問題
一切的開端 | 織布機
衣食住行是人類的基本需求,世界各地都有紡織和織機的發明。
如何提升紡織的效率,成為一個很重要的課題。
與 「針織物」 不同,「機織物」 由兩條或兩組以上的相互垂直的兩個系統紗線或絲線構成,縱向的紗線 叫 經紗,橫向的紗線 叫 緯紗。
通過兩條線不同規律的交錯,就會形成不同的顏色和排列的變化,也就會帶來不同的紋樣。
最原始的織機是手動完成的。
後來人們發現,織物紋樣的變化總是按照一定規律排列完成的,到了戰國時期,就發明了 多綜式提花織機,通過 綜框 來完成這一項工作:
這或許就是最原始的 編碼:通過把 提起規律相同的經紗 穿入 同一個綜框的綜絲 中,來達到當某一個綜框提起時 (通過腳踏板完成),表達某一個特定紋路的線被提起,也就完成了 特定紋路的編織,加快了工作效率。
織布機的更進一步 | 束綜提花織機
不過採用綜框也有一個明顯的限制,那就是 無法織出比較複雜的紋樣,因為紋樣複雜則代表著需要更多的經緯紗以及經紗提升的規律更復雜,意味著可能引入成百上千次緯紗才能完成一個迴圈。
如果仍然採用綜框控制紗線提升,則可能需要成百上千個綜框,這在機械上實現是非常困難的,因此便有了 束綜提花織機。
束綜提花織機沒有綜框,而是被 設計成兩層,每一根經紗會穿入綜絲中實現 單獨的控制,上層 的人將需要提起的經紗提起,而 下層 的人則再經紗提起後通過梭子將緯紗送入織口,並用打緯裝置將引入的緯紗打牢。
然而通常來說,這類織機上會有成千上萬根緯紗,紋樣複雜,於是聰明的老祖宗們發明了 花本:
簡單來說,花本儲存了紋樣資訊。
圖中花本的豎線連線穿入了經紗的綜絲,橫線儲存了每一次引入緯紗時提花資訊,當豎線越過橫線覆蓋在橫線前方時,表明對應的經紗要被提起。
這就有點兒 編碼規則 的意思。
會說話的「機器」 | 雅卡爾織布機
上面說到的束綜提花織機雖然是一大進步,但可想而知的是,它仍然效率緩慢並且織布工人的勞動量非常大,也非常辛苦。
時間來到 18
世紀的歐洲。1725
年,布喬 開拓性的使用 打孔紙帶 來控制經線的提起和放下,從而讓織出花樣成為一種半自動化的工作:
歷史上第一次,機器能夠讀出儲存介質中的內容,並且照其行事。
在布喬提出構想 65
後的 1790
年,約瑟夫·瑪麗·雅卡爾 根據前人的成果設計了新式織機,最終於 1805
年完成了首臺 自動提花織機:
雅卡爾將 穿孔紙帶 改進為 穿孔卡片,根據紋樣圖案在卡片上打孔,通過孔的有無 帶動一系列機械運動裝置來 控制經紗的提升,一張卡片對應迴圈內一次引緯時經紗提升的資訊,引緯完成後,可通過腳踏板控制卡孔卡片轉動,下一張卡片翻轉至工作位置以控制新一次引緯的提花:
雅卡爾織機大幅度節省了時間和工作量 (全自動且效率是之前的二十五倍),而且只需一位工人,很快就被廣泛使用在工廠生產中,雅卡爾也榮獲了拿破崙授予的榮譽勳章。
穿孔卡片控制織物紋樣的設計成為了程式設計思想的萌芽,為資訊科技的發展開展了一條新的道路。
程式設計思想開始萌芽 | 差分機
時間來到 19
世紀初,法國人 巴貝奇 (Chanles Badbbage) 在賈卡織機的啟發下,設計並製造了 差分機。
故事背景
18
世紀末,法國政府在開創米制之後,決定在數學中統一採用十進位制,竟奇葩地想把原本 90
度的直角劃分成 100
度、把原本 60
秒的 1
分鐘劃分成 100
秒,儘管從現在看來這樣的想法絕逼是一種倒退,但他們在當時真就實施了。這一改制帶來的不光是人們在使用時直觀上的彆扭,原本製作好的數學用表 (如三角函式表) 都需要全部重製。
法國政府將這項喪心病狂的工程交給了 數學家普羅尼 (Gaspard de Prony),普羅尼正頭疼著要如何才能完成這項艱鉅的任務,突然想起著名經濟學家 亞當·斯密 (Adam Smith) 的那本《富國論》,他決定採用書中提出的 勞動分工 的做法,將製表的工作人員分成三組:
- 第一組 由五六名牛逼的數學家組成,他們負責制定運算中所需的公式;
- 第二組 由九到十個擅長數學的人組成,他們負責計算出一些關鍵資料,並把第一組制定好的公式進行簡化;
- 第三組 由約一百名計算人員組成,他們利用第二組提供的關鍵資料和公式,做最簡單的加減操作就能得出最終結果。
第三組的工作簡單到什麼程度,就是他們甚至都不知道自己正在算什麼玩意兒,事實上他們的文化程度大部分都不高,裡頭好多都是理髮師、失業人員什麼的。可見即便文盲都能完成的計算,在那個時代還是得依靠人力去做。
而為了保證用表的正確性,普羅尼要求 每個數至少算兩遍,並且 要在法國的不同地點用不同的方法計算。這項勞民傷財的工程整整進行了十年才完成,然而不幸的是,最終的表裡仍然有錯。說到這一點,可以說,那個時代基本沒有一版數學用表是完全正確的,有些版本甚至錯誤百出,要知道數學用表出錯有時後果會很嚴重,比如航海表一出錯就可能直接導致船毀人亡。
巴貝奇 在瞭解到普羅尼的事蹟後淚流滿面,決心要做一套完全正確的數學用表,為達目的,他嘗試了各種減少錯誤的手段,比如調整紙張和墨水的顏色以提高數字的識別度,直接拿現有的多個版本的表進行謄抄、比對、讓不同人員反覆校對,在 1827
年出版了一個版本,結果裡頭還是有錯。只要是人為的就沒有完美的,巴貝奇徹底跪了,他發誓要造一臺機器,讓機器去生產數學表。
這就是史上著名的 差分機 了。
倫敦科學博物館·差分機設計圖紙&半成品:
第一臺真正意義上的電腦 | 分析機
儘管沒能親手實現差分機,但巴貝奇並不會氣餒,或者說他本來就是根本停不下來的那種人。明知實現不了,巴貝奇仍在一刻不停地改進著自己的設計,直到有一天,他構思出了一種空前的機器——分析機,正式成為現代計算機史上的第一位偉大先驅。 (Father of computing)
1834
年,分析機概念誕生之際,巴貝奇自己都為之感到無比震驚。在此之前,任何一臺計算機器都只能完成其被預定賦予的計算任務,要麼是簡單的加減乘除,要麼像差分機那樣只能做差分運算,它們都屬於 calculator
,而分析機才是真正的 computer
,它不侷限於特定功能,而竟然是可程式設計的,可以用來 計算任意函式——現代人無論如何也無法想象在一坨齒輪上寫程式是怎樣一種體驗吧!
巴貝奇設計的分析機主要包括三大部分:
- 用於儲存資料的計數裝置,巴貝奇稱之為 “倉庫”(store),相當於現在 CPU 中的儲存器,這部分是從差分機上的計數裝置改進而來的,我們很容易想象它的模樣;
- 專門負責四則運算的裝置,巴貝奇稱之為 “工廠”(mill),相當於現在 CPU 中的運算器,這部分的結構相對複雜,巴貝奇針對乘除法還做了一些優化;
- 控制操作順序、選擇所需處理的資料和輸出結果的裝置,巴貝奇沒有起名字,由於其呈桶狀,我們可以叫它 “控制桶”,控制桶顯然相當於現在 CPU 中的控制器。
以上三部分,加上巴貝奇並沒有疏漏的輸入輸出裝置,我們驚訝地發現,分析機的組成部分和現在馮·諾依曼架構所要求的五大部件一模一樣!
巴貝奇另一大了不起的創舉就是將 穿孔卡片(punched card) 引入了計算機器領域,用於控制資料輸入和計算,從那時起,到第一臺電子計算機誕生為止,期間幾乎所有的數字計算機都使用了穿孔卡片。
整個分析機就是在類似這樣的齒輪和拉桿作用下實現可程式設計運算的:先從資料卡片讀入資料到儲存器,再將儲存器中的資料傳輸到運算器,運算器算完後又將資料傳回儲存器。
可惜的是,巴貝奇窮其一生也沒能真正把分析機做出來,留給後世的又是一臺模型機和兩千多張圖紙,以及這樣一段遺言:
「如果一個人不因我一生的借鑑而卻步,仍然一往直前製成一臺本身具有全部數學分析能力的機器……那麼我願將我的聲譽毫不吝嗇地讓給他,因為只有他能夠完全理解我的種種努力以及這些努力所得成果的真正價值。」
倫敦科學博物館·分析機設計圖紙&模型機:
穿孔時代的到來 | 製表機
從 1790
年開始,美國每 10
進行一次人口普查。百年間,隨著人口繁衍和移民的增多,從 1790
年的 400
萬不到,到 1880
年的 5000
多萬,人口總數呈爆炸式地增長。
不像現在這個的網際網路時代,人一出生,各種資訊就已經電子化、登記好了,甚至還能資料探勘,你無法想象,在那個計算裝置簡陋得基本只能靠手搖進行四則運算的 19
世紀,千萬級的人口統計就已經成了當時政府的 “不能承受之重”。
1880
年開始的第 10
次人口普查,歷時 8
年才最終完成,也就是說,他們在休息兩年之後就要開始第 11
次普查了,而這一次普查,需要的時間恐怕要超過 10
年,那第 12
次、13
次呢?本來就是 10
年一次的統計,如果每次耗時都在 10
年以上,這件事情就變得沒有意義了。
這可愁煞了當時的人口調查辦公室,他們決定面向全社會招標,尋求能減輕手工勞動、提高統計效率的發明。正所謂機會都是給有準備的人的,一位畢業於哥倫比亞大學的年輕人 赫爾曼·霍爾瑞斯 (Herman Hollerith) 帶著他在 1884
年申請的專利從眾多方案中脫穎而出。
製表機
他發明的機器叫 製表機 (tabulator/tabulating machine),顧名思義,就是專門用來製作資料統計表的機器。製表機主要由示數裝置、穿孔機、讀卡裝置和分類箱組成。
示數裝置包含 4
行、10
列共 40
個示數錶盤,每個盤面被均勻地分成 100
格,並裝有兩根指標,和鐘錶十分相像,“分針” 轉一圈可計 100
,“時針” 轉一圈則計 10000
。可見,整個示數裝置可以表達很龐大的資料。
製表機的工作是圍繞穿孔卡片展開的:操作員先使用穿孔機制作穿孔卡片,再使用讀卡裝置識別卡片上的資訊,機器自動完成統計並在示數錶盤上實時顯示結果,最後,將卡片投入分類箱的某一格中,進行分類存放,以供下次統計使用。
穿孔卡片的應用
此前的某一天,霍爾瑞斯正在火車站排隊檢票,目光不經意落到檢票員手中咔咔直響的打孔機上。他發現,檢票員會特意根據乘客的性別和年齡段,在車票的不同地方打孔。越來越多的人過檢,他進一步確認了這個規律。一個靈感朝他襲來:如果有一張更大的卡,上面有更多的位置可以打孔,就可以用來表示更多的身份資訊,包括國籍、人種、性別、生日等等。
這就是用在 1890
年人口普查中的穿孔卡片,一張卡片記錄一個居民的資訊。卡片設計長約 18.73cm
,寬約 8.26cm
,正好是當時一張美元紙幣的尺寸,因為霍爾瑞斯直接用財政部裝錢的盒子來裝卡片。
卡片設有 300
多個孔位,與雅卡爾和巴貝奇的做法一樣,靠每個孔位打孔與否來表示資訊。儘管這種形式頗有幾分二進位制的意味,但當時的設計還遠不夠成熟,並沒有用到二進位制真正的價值。舉個例子,我們現在一般用 1
位資料就可以表示性別,比如 1
表示男性,0
表示女性,而霍爾瑞斯在卡片上用了兩個孔位,表示男性就其中一處打孔,表示女性就在另一處打孔。其實性別還湊合,表示日期時浪費得就多了,12
個月需要 12
個孔位,而常規的二進位制編碼只需要 4
位。當然,這樣的侷限也與製表機中簡單的電路實現有關。
細心的讀者可能發現卡片的右下角被切掉了,那不是殘缺,而是為了避免放反而專門設計的,和現在的二維碼只有 3
個角是一個道理。
這類實用的細節設計在穿孔機上表現得更為出色。下圖為一位操作員正在使用穿孔機給卡片打孔的情景,她並不需要在卡片上吃力地搜尋孔位,而是直接對著孔距更大的操作皮膚打孔,一根槓桿將兩者的孔位一一對應。操作皮膚還做成了弧形,頗有一分如今人體工程學鍵盤的風姿。
在製表機前,穿孔卡片(或紙帶)多用於儲存指令而不是資料。比較有代表性的,一是雅卡爾提花機,用穿孔卡片控制經線提沉;二是自動鋼琴,用穿孔紙帶控制琴鍵壓放。美劇《西部世界》中,每次故事迴圈的開始,都會給一個自動鋼琴的特寫,彈奏起看似寧靜安逸、實則詭異違和的背景樂。
是霍爾瑞斯將穿孔卡片作為 資料儲存介質 開來,並開啟了一個嶄新的 資料處理紀元。後來人們也把這類卡片稱為霍爾瑞斯卡片,穿孔卡片和穿孔紙帶作為輸入輸出載體,統治了計算領域整整一個世紀。
單元記錄時代
在製表機的高效運轉下,1890
年的人口普查只花了 6
年時間。1896
年,霍爾瑞斯成立製表機公司(The Tabulating Machine Company)並不斷改進自己的產品,先後與英國、義大利、德國、俄羅斯、澳大利亞、加拿大、法國、挪威、美國波多黎各、古巴、菲律賓等多個國家和地區合作開展了人口普查。
到 1914
年,製表機公司每天生產的穿孔卡片多達 200
萬張。不多久,一些競爭對手逐漸起家,歷史迎來了繁榮的資料處理時代。它們的產品也不再侷限於人口普查,逐漸擴充套件到會計、庫存管理等一些同樣需要跟大資料打交道的領域,這些機器作為製表機的後裔被統稱為單元記錄裝置(unit record equipment)。
圍繞穿孔卡片的制卡、讀卡、資料處理和卡片分類是它們的標準功能,穿孔機、讀卡器、分類器是它們的標準配置。這些部件的自動化程度越來越高,比如手動的讀卡裝置很快被自動讀卡機所取代,讀卡速度從每分鐘 100
張逐步提高至每分鐘 2000
張。隨著識別精度的提高,卡片的孔距也越來越小,具有 80~90
列孔位的卡片成為主流,有些卡片的孔位甚至多達 130
列。
機器的功能也逐漸強大,不再只是簡單地統計穿孔數目,減法、乘法等運算能力陸續登場。1928
年,哥倫比亞大學的科學家們甚至用單元記錄裝置計算月球的執行軌跡,他們在 50
萬張卡片上打了 2000
萬個孔,彰顯著單元記錄裝置的無限潛力。
機器的電路實現越來越複雜,但同時也越來越通用。1890
年所用的那臺製表機的 線路是固定的,遇到新的統計任務,改造起來十分麻煩。
1906
年,霍爾瑞斯便引入了接插線板(plugboard)——一塊佈滿導電孔的板卡,可通過改變導線插腳在板上的位置改變線路邏輯。試想一下,接插線板的內部已經布好了具有各種功能的線路,但它們都處在斷開狀態,各自連線著接插線板上的某兩個孔位,像一窩嗷嗷待哺的小鳥長大著嘴巴,外部的導線就像美味的蟲子,當蟲子的頭尾分別與小鳥的上喙和下喙接觸,線路就被導通,這隻小鳥就開始工作了。如此,每次使用就可以啟用不同的 “小鳥”,從而完成不同的任務。這已經是一種可程式設計性的體現。
1911
年,製表機公司與另外 3
家公司合併成立 CTR 公司 (Computing-Tabulating-Recording Company),製表機公司作為其子公司繼續運營到 1933
年。
1924
年,CTR 更名為 國際商業機器公司(International Business Machines Corporation),就是現在大名鼎鼎的 IBM 公司。可見,在如今眾多年輕的 IT 公司中,擁有百年曆史的 IBM 是位當之無愧的前輩,它完整地參與和見證了整個現代計算機的發展史。IBM 保持了製表機公司在單元記錄市場的龍頭地位,到 1955
年,其每天生產的穿孔卡片多達 7250
萬張。
1937
年開始,單元記錄裝置逐步電子化,與電子計算機的界線漸漸模糊,並最終為後者讓路。隨著 1976
年 IBM 一型最核心的單元記錄產品的停產,短暫的單元記錄時代也宣告謝幕,它彷彿是電子計算時代來臨前的預演和鋪墊,許多設計被沿用下來,比如穿孔卡片和接插線板。
有趣的是,即使電子計算機逐漸普及,許多機構由於用慣了單元記錄裝置,遲遲不願更換,少數機構甚至一直用到了 21
世紀。
一句話總結
程式設計能夠幫助我們解決一些非常實際的問題,用一種非常酷的方式。
感興趣也可以擴充套件閱讀一下:改變世界的程式碼行
Part 2. 二進位制和 CPU 原理 | 編碼中蘊藏的智慧
原來,我們是這樣計數的
在討論「二進位制」和「CPU 如何工作」之前,我們先來討論一下我們生活中最稀疏平常的 數字,我們與之頻繁地打交道:一個約定的時間、一件商品的價格、一個人的身高....卻很少有人細細想過,這些數字是如何表達出來的?為什麼你理所當然地把 1024
理解為「一千零二十四」而不是別的含義?
也許你從未想過,在這簡單的記數中,沉澱著人類的大智慧。
一進位制計數法
早在數字的概念產生之前,人類就學會了使用樹枝、石子、貝殼等自然界隨處可見的小物件表示獵物的、果實的、部落人口的數量。比如在某個角落堆上一堆石子,每打到 1 只獵物,就扔 1 顆石子進去,每吃掉 2 只獵物,就從中取走 2 顆石子。他們並不在意石子的總數,只是時不時地瞅一眼,心底大致有數。
其實這是一種最樸素的記數方式,數學家稱之為 一進位制記數法(unary numeral system)。我們把它符號化一下,比如用斜槓 /
來表示:
1
就是/
;2
就是//
;4
就是////
;
好像沒毛病,我們平時掰手指用的就是這種記數法,但數字一大,場面就要失控了。
符值相加記數法
為了解決記錄大數的問題,於是我們得發明一些其他符號來表示更大的數值,比如用橫槓 -
表示 10
,用十字 +
表示 100
。那麼:
16
就是-//////
;32
就是---//
;128
就是+--////////
;
漂亮....這種靠符號型別和符號數量表示數字的方法被稱為 符值相加記數法(sign-value notation),古埃及和古羅馬用的都是它,只不過符號各不相同。
古埃及的記數符號:
1 | 10 | 100 | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|---|---|
1024
在古埃及就寫作:
你會發現,符值相加記數法的一大優點是,符號的順序可以任意打亂,數字含義不受影響。我國藏族曾用石子表示 1
、木棍表示 10
、果核表示 100
、蠶豆表示 1000
、瓦片表示 10000
,那麼,當你把 1
顆蠶豆、2
根木棍和 4
顆石子胡亂地攥在手裡,別人依然知道它們是 1024
。
古羅馬的做法略有不同,他們對五進位制情有獨鍾:
1 | 5 | 10 | 50 | 100 | 500 | 1000 |
---|---|---|---|---|---|---|
I | V | X | L | C | D | M |
這些符號沿用至今,想必大家(至少對前 3
個)都比較熟悉,許多鐘錶仍保留著使用羅馬數字的習慣,1~12
分別表示為:I
、II
、III
、IV
、V
、VI
、VII
、VIII
、IX
、X
、XI
、XII
。你會發現,羅馬記數法是符值相加記數法的變種,因為它不光「相加」,還「相減」。這種方式就不允許符號亂序了,IV
和 VI
表示的是不同的數字。
那羅馬人何苦要使用這種更復雜的記數法呢?無非是為了讀寫方便。同樣表示 9
,IX
比 VIIII
更簡潔。
其實有一種更好使的方法——用另外一些列符號來表示符號的數量。比如用 A
表示 1
個符號,用 B
表示 2
個符號,以此類推,用 I
表示 9
個符號。
如此,上文表示 256
的 ++-----//////
就可以寫作 B+E-F/
。你一定感覺莫名其妙,這種寫法哪裡方便了。其實中文的數字表示就是這種形式,只不過我們用得太習慣了,以至於沒有發現。
在中文中,個
、十
、百
代替了 /
、-
、+
,而 一
、二
、三
代替了 A
、B
、C
。256
就寫作 二百五十六個
,個
比較累贅,我們通常把它省略了。
其實像日語、英語用的也同樣是這種記數法,簡潔、優雅。
美中不足的是,這種形式雖便於讀寫,卻不便於計算。中國古人為算籌和算盤這類經典算具搭建起廣闊的舞臺,卻沒給筆算留出一席之地。想象一下,如果讓你把這些漢字寫在草稿紙上,列個豎式,你的內心一定非常彆扭。
位值制記數法
公元5世紀,印度數學家阿耶波多(Aryabhata 476–550)創立了現在廣泛使用的 位值制記數法(positional notation/place-value notation),該記數法使用的主要符號,是同為印度人發明的阿拉伯數字:0
、1
、2
、3
、4
、5
、6
、7
、8
、9
。
與符值相加記數法類比,位值制中的 1
、2
、3
代替的是 A
、B
、C
,那 /
、-
、+
呢?是 靠阿拉伯數字的位置來表示的。眾所周知,最右位相當於 /
,次右位相當於 -
。靠每個位置上的數值來表示數字,故名位值制。
嚴謹的數學家用一種多項式高度概括了位值制記數法的本質,在十進位制中,這個多項式是這樣的:
這是一個 n
位十進位制數,ai 就是第 i 位上的數值。為便於直觀理解,舉個 1024
的例子吧:
由於我們熟悉了十進位制,這樣費心費力的展開可能會讓你覺得好笑,但當我們把它推廣到其他進位制時,這個多項式的價值就體現了出來。n 位 b 進位制數的位值製表示:
1024
用二進位制怎麼表示?
因此,1024
的二進位制寫作 10000000000
。
除了最普遍的十進位制和計算機中的二進位制,常見的還有七進位制(如 1
周 7
天)、十二進位制(如 1
年 12
個月)、十六進位制(如古代 1
斤 16
兩)、六十進位制(如六十甲子)等等,只要有意義,任何進位制都可以為你所用。
為什麼使用二進位制
至此,你對「二進位制」或許不再那麼陌生,它僅僅是數制的一種而已。
可為什麼一定是二進位制呢?使用人類習慣的十進位制不好嗎?
理由一:物理上易於實現
計算機依靠電力工作,這也就意味著需要將數字訊號對映到電訊號,實現這種對映最簡單的方法是:
- 0 - 沒有電(0 V)
- 1 - 有點(5 V)
二進位制在技術上最容易實現。這是因為具有兩種 穩定狀態 的物理器件很多,如閘電路的導通與截止、電壓的高與低等,而它們恰好可以對應表示 “1” 和 “0” 這兩個數碼。假如採用十進位制,那麼就要製造具有 10
種 穩定狀態 的物理電路,而這是非常困難的。
理由二:機器可靠性高
為什麼使用更復雜的數字系統是一個問題?
假設我們使用三元(3 位數字)數字系統涉及計算機,如果我們具有從 0 V
到 5 V
的電壓,那麼我們可以進行以下的對映:
- 0 - 0 V;
- 1 - 2.5 V;
- 2 - 5 V;
看起來合理吧?但是,想象一下,我以 2.5 V
的電壓傳送了一個數字。但是由於電路中的一些噪聲,我在輸出端得到 2.3 V
的電壓,因此將其視為 0
。結果是?
有人給我傳送了 1
,但我將其視為 0
。資料丟失可是一個非常嚴重的問題。
使用二進位制則可靠得多,由於電壓的高和低、電流的有和無等都是一種 質的變化,兩種物理狀態穩定、分明,因此,二進位制碼傳輸的抗干擾能力強,鑑別資訊的可靠性高。
為什麼計算機系統必須有時鐘
建立數字系統的目的是 僅在某些時間點測試開/關(二進位制)值,這使電線(或其他裝置)有時間更換。這就是計算機系統有時鐘的原因。
時鐘會週期性地進行訊號的測量,圖中所示的 T1 和 T2 就是可以測量訊號的時間點。
時鐘利用所有這些時間點來保持同步。更快的時鐘意味著每秒可以對電線進行更多次測試,並且整個系統執行得更快。2 GHz
處理器每秒檢查二進位制值 20
億次。在這些時間之間,允許值改變並穩定下來。處理器晶片速度越快,每秒可以測試的次數就越多,每秒可以做出的決策就越多。
理由三:運算規則簡單
數學推導已經證明,對 N
進位制數進行算術求和或求積運算,其運算規則各有 N(N+1)/2
種。如採用十進位制,則 N=10
,就有 55
種求和或求積的運算規則;而採用二進位制,則 N=2
,僅有 3
種求和或求積的運算規則,以上面提到的加法為例:
0+0=0,0+1=1 (1+0=1),1+1=10
因而可以大大簡化運算器等物理器件的設計。
理由四:邏輯判斷方便
採用二進位制後,僅有的兩個符號 “1”
和 “0”
正好可以與邏輯命題的兩個值 “真” 和 “假” 相對應,能夠方便地使用邏輯代數這一有力工具來分析和設計計算機的邏輯電路。
雖然在 1950
年代就造出了更加高效的三元計算機,但在效率和複雜度的取捨上,始終抵不過二進位制。二進位制仍然在當今世界中長期存在。
CPU 的實際工作方式簡單演示
上面我們瞭解到計算機以二進位制的形式執行,它們只有兩種狀態:開(1)和關(0),為了執行二進位制計算,我們需要採用一種特殊的電子元器件,稱為 「電晶體」。暫時我們把它理解為一種開關吧,通電就開啟,沒電流通過就關閉。
利用「開關」搭建邏輯電路
我們知道,給電燈通上電,它就會亮:
於是,結合上開關,我們就能搭建出最基礎的 與門 和 或門。
與門
該電路的邏輯是:只有當 A 和 B 同時開啟時,LED 燈才會亮,也就是認為輸出 1,我們可以利用電訊號來簡單模擬一下:
A | B | Y |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
或門
該電路的邏輯是:當 A 或者 B 開啟時,LED 燈就會亮,也就是認為輸出 1,我們可以利用電訊號來簡單模擬一下:
A | B | Y |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 1 |
其他門
類似地,我們可以藉助更多的電子元器件來創造出基礎的 7
種邏輯閘電路:
這裡需要特別提一下 異或門,我們需要先知道有一種電子元器件可以利用電氣特性對 輸入取反,也就是說輸入 1
則輸出 0
,輸入 0
則輸出 1
,那麼我們就可以 簡單模擬 出異或門邏輯電路(實際會更復雜些,這裡僅展示出異或的意思):
A'
和 B'
分別表示 A
和 B
開關的反值,從圖中我們很容易知道只有當 A
、B
只存在一個輸入 1
時,整個電路才會輸出 1
。
利用邏輯閘製作簡單加法器演示
OK,上面我們瞭解到我們能夠利用 "開關" 來模擬邏輯的運算,我們接下來試著還原一個簡單的加法運算器是如何實現的:
僅需兩個門,就可以完成基本的二進位制加法運算。上圖是利用 logic.ly
建立的半加法器,A
、B
相當於使我們計算的兩個數,最後一塊相當於是我們的數顯晶片,它的功能是根據輸入顯示數字,從上到下的引腳(也就是圖中輸入的地方,通常我們這樣稱呼)分別對應了 20=1、21=2、22=4、23=8 的輸入,沒有任何輸入時顯示為 0
,如果 引腳 1
(對應 20=1)像上圖一樣有輸入,則顯示 0 + 1 = 1
。
我們來理解一下上方的電路:
- 如果僅開啟一個輸入,但不同時開啟兩個輸入,則此處的 XOR 門(異或門)將開啟,此時對應輸入
引腳 1
,顯示數字 1
(類似於1 + 0 和 0 + 1
); - 如果兩個輸入均開啟,則 AND 門(與門)將開啟,此時對應輸入
引腳 2
,顯示數字 2
(類似於1 + 1
); - 如果沒有輸入,則 AND 門和 XOR 門都保持關閉,此時顯示
數字 0
(類似於0 + 0
);
因此,如果兩個都開啟,則 XOR 保持關閉,並且 AND 門開啟,得出正確的答案為 2
:
但這只是最基礎的半加法運算器,不是太有用,因為它只能解決最簡單的數學問題之一。但如果我們把它們兩個與另一個輸入連線,就會得到一個完整的加法器:
仔細思考幾遍,你就會得知這個三個輸入的加法器已經可以計算 3
個二進位制數字的加法運算了,我們如法炮製,可以通過連線更多的"進位"來使這個加法器能夠運算更多的數,這當然也意味著這個計算鏈條更長。
大多數其他數學運算都可以加法完成。乘法只是重複加法,減法可以通過一些奇特的位反轉來完成,而除法只是重複減法。並且,儘管所有現代計算機都具有基於硬體的解決方案以加快更復雜的操作,但從技術上講,您可以使用完整的加法器來完成全部操作。
一句話總結
編碼伴隨探索和學習新知的過程,如果願意,我們能從中獲取很多類似於解出一道數學題的樂趣。
Part 3. 機器指令到高階語言 | 站在先驅巨人的肩膀
機器指令
我們已經瞭解了 二進位制和 CPU 的基本原理,知道了程式執行時,CPU 每秒數以億次、十億次、百億次地震盪著時鐘,同步執行著微小的 「電子操作」,例如:從記憶體讀取一個位元組的資料到 CPU 又或者判斷位元組中的某一位是 0
還是 1
。
CPU 本身有一組 規定好的 可以執行的 「基本動作」(被稱為 機器指令):
- 讀取指令;2. 執行指令;3. 寫暫存器;
這幾乎就是 CPU 工作的全部了。 這些動作雖然每次只能執行一次,但是每秒可以執行數十億次,這個數量級的「小操作」累加成為一個大的「有用的操作」。
處理器所做的一切都是基於這些微小的操作!幸運的是,我們已經不再需要了解這些操作的詳細資訊就可以編寫和使用各類程式。諸如 Java 這一類的 「高階語言」 的 目的 就是 將這些微小的電子操作組織成由人類可讀的「程式語言」表示的大型有用單元。
儘管機器語言有一些反人類,但至少我們可以用它來來編寫程式碼了,如:
00000001 00000010 00000001
00000100 00000100 00000000
似乎看著這一段兒程式碼有點迷糊,並且 可讀性太差了。
如此你就會感知到 上個世紀 的程式設計師使用 打孔卡片:
使用 紙帶:
甚至是 直接插拔線路 or 按下開關:
是一件多麼硬核的事情...
組合語言
PU 的指令都是 二進位制 的,這顯然對於人類來說是 不可讀 的。為了解決二進位制指令的可讀性問題,工程師將那些指令寫成了 八進位制。二進位制轉八進位制是輕而易舉的,但是八進位制的可讀性也不行。
很自然地,最後還是用文字表達,加法指令寫成 ADD
。記憶體地址也不再直接引用,而是 用標籤 表示。
這樣的話,就多出一個步驟,要把這些文字指令翻譯成二進位制,這個步驟就稱為 assembling
,完成這個步驟的程式就叫做 assembler
。它處理的文字,自然就叫做 aseembly code
。標準化以後,稱為 assembly language
,縮寫為 asm
,中文譯為 組合語言。
組合語言演示
舉個簡單的例子,我們需要計算:
(1 + 4) * 2 + 3
我們按照 「字尾表示法」 進行一下轉換:
1,4,+,2,*,3,+
我們平常使用的方法是 「中綴表示法」,也就是把計算符號放中間,例如 1 + 3
,字尾則是把符號放最後,例如 1, 3, +
。
這樣做的好處是沒有先乘除後加減的影響,也沒有括號,直接運算就行了。(例如 1, 3, +
,先把 1
和 3
儲存起來碰到 +
知道是加法則直接相加)
OK,我們從頭開始使用匯編語言來編寫一下程式,首先第一步:把 1
儲存起來(放入暫存器):
MOV 1
之後是 4, +
,那就直接加一下:
ADD 4
然後是 2, *
,那就直接乘一下(SHL
是向左移動一位的意思,二進位制中左移一個單位就相當於乘以 2
,例如 01
表示 1
,而 10
則表示 2
):
SHL 0
最後是 3, +
,再加一下:
ADD 3
完整程式如下:
MOV 1
ADD 4
SHL 0
ADD 3
這似乎看起來比 00001111
這樣的二進位制要好上太多了!程式設計師們感動到落淚:
高階語言
擺脫了 二進位制,我們有了更可讀的 組合語言,但仍然十分繁瑣和複雜,每一條彙編指令代表一個基本操作,例如:「從記憶體 x 位置獲取一個數字並放入暫存器 A」、「將暫存器 A 中的數字新增到暫存器 B 的數字上」。這樣的程式設計風格既費時又容易出錯,並且一旦出錯還很難發現。
例如,我們來看一看 「1969 年阿波羅 11號登月計劃」 用來 防止登月艙計算機耗盡自身資源 的 BAILOUT 程式碼:
POODOO INHINT
CA Q
TS ALMCADR
TC BANKCALL
CADR VAC5STOR # STORE ERASABLES FOR DEBUGGING PURPOSES.
INDEX ALMCADR
CAF 0
ABORT2 TC BORTENT
OCT77770 OCT 77770 # DONT MOVE
CA V37FLBIT # IS AVERAGE G ON
MASK FLAGWRD7
CCS A
TC WHIMPER -1 # YES. DONT DO POODOO. DO BAILOUT.
TC DOWNFLAG
ADRES STATEFLG
TC DOWNFLAG
ADRES REINTFLG
TC DOWNFLAG
ADRES NODOFLAG
TC BANKCALL
CADR MR.KLEAN
TC WHIMPER
- 出處:改變世界的程式碼行 - https://www.infoq.cn/article/5CaYH8NbS6BmptWKRgkX
似乎不太容易讀的樣子...
阿波羅登月計劃的原始碼在 Github 上已經公開,有興趣的可以去下方連結膜拜一下(可以去感受一下當時程式設計師的工程能力):
另外附一下當時程式碼的設計負責人 Margaret Heafield Hamilton(女程式設計師)和完成的堆起來跟人一樣高的程式碼量:
第一個高階語言:FORTRAN
當 John Backus 在 1950
年以一名科學程式設計師的身份加入 IBM 時,已經可以使用諸如 ADD
之類的助記詞代替數字程式碼來編寫程式,也就是我們的組合語言。這使程式設計變得容易一些,但是即使是一個簡單的程式也需要數十次操作,並且仍然很難找到錯誤。
巴克斯認為,應該有可能建立一種程式語言,使一系列計算可以用類似於數學符號的形式來表達。然後,使用特定的翻譯程式(以今天的術語來說是編譯器)可以將其轉換為計算機可以理解的數字程式碼。
Backus 在 1953
年向他的經理提出了這個想法。他得到了預算,並被鼓勵僱用一個小團隊來測試該想法的可行性。三年後,該團隊釋出了一本手冊,其中描述了 IBM Mathematical Formula Translating System(簡稱 FORTRAN)。不久之後, IBM 向 IBM 704 的使用者提供了第一個 FORTRAN 編譯器。
Backus 和他的團隊創造了世界上第一種高階程式語言。科學家和工程師將不再需要將其程式編寫為數字程式碼或冗長的助記符。
FORTRAN 程式碼演示
下面演示計算並輸出 8 * 6
的程式碼例項:
program VF0944
implicit none
integer a, b, c
a= 8
b= 6
c= a*b
print *, 'Hello World, a, b, c= ', a, b, c
end program VF0944
對比彙編程式碼,是不是看上去要清晰(人類可讀)多了呢?
FORTRAN 的意義
FORTRAN 的問世在計算機史上具有劃時代的意義,它使計算機語言從原始的低階組合語言走出來,進入了更高的境界,使得 計算機語言不再是計算機專家的專利,使廣大的工程技術人員有了進行計算機程式設計的手段。
由此計算機更快地深入到了社會之中,它在工業部門中初露頭角,更是在火箭、導彈、人造地球衛星的設計中大顯身手,因此有人稱 FORTRAN 語言使計算機的工業應用成了可能,是推動第二次世界大戰以後西方工業經濟復甦和進入第二次工業革命的無形力量,是 "看不見的蒸汽機"。
一句話總結
計算機先驅們已經為我們架設好了計算機的世界,站在先驅巨人們的肩膀,我們如今很容易也可以看得更高和更遠。
Part 4. 構建自己獨有的程式(世界)
編碼的樂趣有很多,把自己的想法動手實踐就是其中一個。
例如網友玩兒遊戲活動需要不停點滑鼠,於是寫了一個滑鼠連點器:
例如給自己心愛的妹子寫一個戀愛紀念網站:
例如給 FlappyBird 加一個強化學習演算法讓它自己學習如何飛行:(圖示已經能自己飛 4527
步了)
例如網友自己寫的文言文語言:(使用文言文寫程式碼)
// HelloWorld 程式演示
吾有一數。曰三。名之曰「甲」。
為是「甲」遍。
吾有一言。曰「「問天地好在。」」。書之。
云云。
執行輸出:
問天地好在。
問天地好在。
問天地好在。
一句話總結
我們可以動手通過程式設計把我們的很多想法付諸於實現 (前提是不斷探索和學習),並在迎接挑戰和最終實現的過程中獲得無限的樂趣。
小總結
總體而言,IT 是令人興奮的。
素有「軟體吞噬世界」的說法,我們也正生活在計算機當道的世界。
並且程式設計並不是每個人都具備的技能,藉助技術,一切皆有可能,並且現在網際網路時代比以往都更有機會學習和建立「自己的世界」。
- 本文已收錄至我的 Github 程式設計師成長系列 【More Than Java】,學習,不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
- 個人公眾號 :wmyskxz,個人獨立域名部落格:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!
非常感謝各位人才能 看到這裡,如果覺得本篇文章寫得不錯,覺得 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!
創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!