緣起
本期繼續討論邊緣領域里的另一個有意思的東西——語言。正如前文所說,邊緣設備是在終端、云端(或者叫后臺系統)中出現的一種新形態的設備。它肯定不是終端,因為它比終端能力強,而且傾向于它是為終端服務的。它肯定也不是后臺系統,因為邊緣設備往往不會部署很多。或者,往往將所有業務系統/組件全部裝在一臺邊緣設備上——這和后臺系統里的微服務化是不太一樣的。主要是不想把后臺系統那套龐大、復雜的東西再搬到邊緣側了。
簡單來說,用一句話來表示邊緣設備上的系統開發的話,就是我們需要在邊緣側開發服務類程序。顯然,java不太合適。java一個是資源消耗大,另外一個是中間件太多。這是我最初的想法,后來發現某些老外也是這么想的,比如下圖的某個項目里的說法:

所以,設備端人才如果要往邊緣計算領域發展的話,go語言我覺得是需要掌握的。除此之外,后臺系統、云端那套微服務、分布式也是可以學的。可以說,邊緣系統給眾多在血海里拼搏的設備端人才開拓了一個新的疆場。BTW,不用擔心后臺系統的人殺向邊緣側。因為邊緣側很難玩轉集群、而且都需要自己動手——我意思是絕對不是簡單的寫寫業務邏輯就行的事情。
說說go語言
go語言是一門比較奇特的語言。我最近大概花了2-3周看了下。以前在搞以太坊錢包的時候看過一點,但這次因為實際操作了下感覺更深刻。這篇不是講go語言特點的。今天只講一些認識和體會,以后有機會再接著講。邊緣側是很多技術領域的匯聚點,比如什么聯邦學習,分布式AI等等。
回到go來,說兩個go語言里我的感受,非技術討論請勿上綱上線
go中的接口和實現
go語言中接口的定義和實現非常有想法(但好像和ObjectC挺像的)。在go語言里,接口僅定義行為——也就是函數。那么誰來實現接口呢?奇怪的地方來了,go語言中沒有顯示實現接口的地方。通過一個代碼來講解會好一點:

上面代碼示例中
第7到第10行定義了一個接口。注意,go語言里,類型是寫在右邊的。可以看到,interface只能定義行為——也就是函數
第11行到第13行定義了一個struct。go語言里,struct里只有數據,沒有行為。
那么,怎么把數據和行為綁定到一起呢——也就是如何實現一個接口呢?通過第15行的函數定義可以看到。第15行和第18行是go里的函數定義,但在func和函數名之間增加了一個叫receiver的部分。這個部分就是將行為和數據綁定的地方——也就是實現接口的方式。這種奇怪的接口實現方式,我理解更多是強迫開發者換一種思考角度。
當然,我目前還沒有適應這種方式。從java/c++程序員轉換過來的話,還是要花一點時間的。先拋開go語言設計者所想到的這種設計思路的優點,對普通開發者而言,很難適應的一個原因是我不知道哪些struct實現了哪些接口!!!!雖然go語言在編譯的時候會把這些信息固化(go語言是靜態編譯語言,這些信息編譯的時候都能算出來),但對程序員而言,我還得看定義了哪些函數才知道......
接著說。上圖中,第22到第27行是數據綁定接口的另外一種方式。對比第15到第20行,我們發現只不過receiver不太一樣。15-20行的receiver是指針,22-27行的receiver是值。
恩,這又是go里實現接口和其它語言不太一樣的地方。通過指針定義的receiver,接口函數里是可以改變receiver內部值的。而通過value定義的receiver,接口函數里無法改變receiver內部值——就是傳值和傳引用的區別,這個不多說了。我們看下一段有意思的地方:

30行定義了x結構體變量。第31行將x賦值給interfaceX變量。32行針對這個interfaceX調用它的api1函數。結果編譯錯誤——原因是上圖中我們定義的receiver是指針類型。而interfaceX不是指針。OK,可以“理解”。接著看下一個代碼:

上圖中,23-28行我們將receiver改成了值引用。再看test函數:
第31行定義了一個x變量。
第32行interfaceX取得是x的指針。
第33行interfaceY取得是x的值
34、35行調用api1和api2。比較奇葩的是,interfaceX明明是x的指針,它居然可以調值引用定義的api1。
值類型的接口調用,api1、api2調用完后,Name都不會變。這就是值和引用(或叫指針也行)的區別。這在其他語言里都是這個表現。不多說了。
但結合前面一個圖,現在你一定糊涂了,go語言里receiver類型的影響:
receiver*指針類型定義的接口,只能通過指針類型變量的調用,否則編譯錯誤
receiver值類型定義的接口,值變量或者指針變量都可以調用!
這是為毛呢?看了好多材料,我感覺都沒說清楚。我想了一種比較簡單的答案(簡單,往往意味著可能是對的):
receiver值類型定義的接口,值變量調用肯定沒問題。指針變量調用也可以沒問題,因為編譯器會先將指針變量解引用到一個臨時變量,再用這個臨時變量去調用。因為receiver值類型定義的接口無法修改原參數的內容,所以相當于編譯器針對指針類型變量做了一個對程序員來說少寫寫行代碼的處理,而且不會有啥問題——因為使用receiver值類型,本身從語義上就已經約定好不能修改原參數。
receiver*指針類型定義的接口,當我們用值變量去調用的時候,編譯器能不能也做個簡化處理,把這個值變量的地址取一下,然后再去調用呢?不行!因為receiver*指針類型接口是會修改原參數的。所以編譯器禁止做這種優化——實際上你要手動多寫1-2行代碼這么干是沒問題的。但這樣原參數的內容就會被更改,不信你試試。
這就是go語言里,interface實現時一個比較“難”的問題的解釋。
interface是指針還是非指針?
還有一個問題牽扯到另外一個比較有意思的東西。看下圖

第27行定義了一個x變量,它的類型是interface{}。這個東西類似C++里的void,也有些像Java里的Object。但請注意,它是一個非指針類型。
什么意思?接著看28行定義了一個整型變量a,值為6。29行把a賦值給了x。由于x不是指針,所以它相當于把a的值拷貝到了x內部的一個區域。而不是讓x指向a的地址。所以,當我們在第30行將x賦值為10的時候,第31行打印出來的就是x=10,a=6,就是說改變x并不影響a。
這和java里的interface不太一樣,從java過來的小伙伴們要務必了解這一點。
今天先說到這。目前感覺這兩點是我最大的體會,后續肯定還有好玩的。
最后的最后
我期望的結果不是朋友們從我的書、文章、博客后學會了什么知識,干成了什么,而應該是說,神農,我可是踩在你的肩膀上的喔。
關于學習方面的問題,我已經討論完了。后面這個公眾號將對一些基礎的技術,新技術做一些學習和分享。也歡迎你的投稿。不過,正如我在公眾號“聯系方式”里說的那樣——鄭淵潔在童話大王《智齒》里有一句話令我印象深刻,大意是“我有權保持沉默,但你說的每一句話都可能成為我靈感的源泉”。所以,影響不是單向的,很可能我從你那學到的東西更多。

神農和朋友們的雜文集
長按識別二維碼關注我們