Golang基础知识入门详解
Go語言入門
Go語言入門教程
很多人將 Go 語言 稱為 21 世紀的 C 語言,因為 Go 不僅擁有 C 語言的簡潔和性能,而且還很好的提供了 21 世紀互聯網環境下服務端開發的各種實用特性,讓開發者在語言級別就可以方便的得到自己想要的東西。
在 Go 語言的版本迭代過程中,語言特性基本上沒有太大的變化,基本上維持在 Go1.1 的基準上,并且官方承諾,新版本對老版本下開發的代碼完全兼容。事實上,Go 開發團隊在新增語言特性上顯得非常謹慎,而在穩定性、編譯速度、執行效率以及 GC 性能等方面進行了持續不斷的優化。
Go語言優缺點
優點
Go很容易學習
如果你了解任何一種編程語言,那么通常在學習幾個小時就能夠掌握 Go 的大部分語法,并在幾天后寫出你的第一個真正的程序。閱讀并理解實效 Go 編程,瀏覽一下包文檔,玩一玩 Gorilla 或者 Go Kit 這樣的網絡工具包,然后你將成為一個相當不錯的 Go 開發者。
這是因為 Go 的首要目標是簡單。當我開始學習 Go,它讓我想起我第一次 發現 Java:一個簡單的語言和一個豐富但不臃腫的標準庫。對比當前 Java 沉重的環境,學習 Go 是一個耳目一新的體驗。因為 Go 的簡易性,Go 程序可讀性非常高,雖然錯誤處理添加了一些麻煩。
簡單并發編程
Goroutines 可能是 Go 的最佳特性了。它們是輕量級的計算線程,與操作系統線程截然不同。當 Go 程序執行看似阻塞 I/O 的操作時,實際上 Go 運行時掛起了 goroutine ,當一個事件指示某個結果可用時恢復它。與此同時,其他的 goroutines 已被安排執行。因此在同步編程模型下,我們具有了異步編程的可伸縮性優勢。
Goroutines 也是輕量級的:它們的堆棧隨需求增長和收縮,這意味著有 100 個甚至 1000 個 goroutines 都不是問題。
channel 是 goroutines 的通信方式:它們提供了一個便利的編程模型,可以在 goroutines 之間發送和接收數據,而不必依賴脆弱的低級別同步基本體。channels 有它們自己的一套用法模式。但是,channels 必須仔細考慮,因為錯誤大小的 channels (默認情況下沒有緩沖) 會導致死鎖。下面我們還將看到,使用通道并不能阻止競爭情況,因為它缺乏不可變性。
豐富的標準庫
Go 的標準庫非常豐富,特別是對于所有與網絡協議或 API 開發相關的: http 客戶端和服務器,加密,檔案格式,壓縮,發送電子郵件等等。甚至還有一個 html 解析器和相當強大的模板引擎去生成 text & html,它會自動過濾 XSS 攻擊(例如在 Hugo 中的使用)。
各種 APIs 一般都簡單易懂。它們有時看起來過于簡單:這個某種程度上是因為 goroutine 編程模型意味著我們只需要關心 “看似同步” 的操作。這也是因為一些通用的函數也可以替換許多專門的函數,就像 我最近發現的關于時間計算的問題。
Go性能優越
Go 編譯為本地可執行文件。許多 Go 的用戶來自 Python、Ruby 或 Node.js。對他們來說,這是一種令人興奮的體驗,因為他們看到服務器可以處理的并發請求數量大幅增加。當您使用非并發(Node.js)或全局解釋器鎖定的解釋型語言時,這實際上是相當正常的。結合語言的簡易性,這解釋了 Go 令人興奮的原因。
然而與 Java 相比,在原始性能基準測試中,情況并不是那么清晰。Go 打敗 Java 地方是內存使用和垃圾回收。Go 的垃圾回收器的設計目的是優先考慮延遲,并避免停機,這在服務器中尤其重要。這可能會帶來更高的 CPU 成本,但是在水平可伸縮的體系結構中,這很容易通過添加更多的機器來解決。請記住,Go 是由谷歌設計的,他們從不會在資源上面短缺。
與 Java 相比,Go 的垃圾回收器(GC)需要做的更少:切片是一個連續的數組結構,而不是像 Java 那樣的指針數組。類似地,Go maps 也使用小數組作為 buckets,以實現相同的目的。這意味著垃圾回收器的工作量減少,并且 CPU 緩存本地化也更好。
標準化的測試框架
Go 在其標準庫中提供了一個很好的測試框架。它支持并行測試、基準測試,并包含許多實用程序,可以輕松測試網絡客戶端和服務器。
缺點
Go忽略了現代語言設計的進步
在少既是多中,Rob Pike 解釋說 Go 是為了在谷歌取代 C 和 C++,它的前身是 Newsqueak ,這是他在 80 年代寫的一種語言。Go 也有很多關于 Plan9 的參考,Plan9 是一個分布式操作系統,在貝爾實驗室的 80 年代開發的。
甚至有一個直接從 Plan9 獲得靈感的 Go 匯編。為什么不使用 LLVM 來提供目標范圍廣泛且開箱即用的體系結構?我此處可能也遺漏了某些東西,但是為什么需要匯編?如果你需要編寫匯編以充分利用 CPU ,那么不應該直接使用目標 CPU 匯編語言嗎?
Go 的創造者應該得到尊重,但是看起來 Go 的設計發生在平行宇宙(或者他們的 Plan9 lab?)中發生的,這些編譯器和編程語言的設計在 90 年代和 2000 年中從未發生過。也可能 Go 是由一個會寫編譯器的系統程序員設計的。
函數式編程嗎?不要提它。泛型?你不需要,看看他們用 C++ 編寫的爛攤子!盡管 slice、map 和 channel 都是泛型類型,我們將在下面看到。
Go 的目標是替換 C 和 C++,很明顯它的創建者也沒有關注其他地方。但他們沒有達到目標,因為在谷歌的 C 和 C++ 開發人員沒有采用它。我的猜測是主要原因是垃圾回收器。低級別 C 開發人員強烈拒絕托管內存,因為他們無法控制什么時間發生什么情況。他們喜歡這種控制,即使它帶來了額外的復雜性,并且打開了內存泄漏和緩沖溢出的大門。有趣的是,Rust 在沒有 GC 的情況下采用了完全不同的自動內存管理方法。
Go 反而在操作工具的領域吸引了 Python 和 Ruby 等腳本語言的用戶。他們在 Go 中找到了一種方法,可以提高性能,減少 內存/cpu/磁盤 占用。還有更多的靜態類型,這對他們來說是全新的。Go 的殺手級應用是 Docker ,它在 devops 世界中引起了廣泛的應用。Kubernetes 的崛起加強了這一趨勢。
接口是結構類型
Go 接口 就像 Java 接口或 Scala 和 Rust 特性(traits):它們定義了后來由類型實現的行為(我不稱之為“類”)。與 Java 接口和 Scala 和 Rust 特性不同,類型不需要顯式地指定接口實現:它只需要實現接口中定義的所有函數。所以 Go 的接口實際上是結構化的。
我們可能認為,這是為了允許其他包中的接口實現,而不是它們適用的類型,比如 Scala 或 Kotlin 中的類擴展,或 Rust 特性,但事實并非如此:所有與類型相關的方法都必須在類型的包中定義。
沒有枚舉
Go 沒有枚舉,在我看來,這是一個錯失的機會。iota 可以快速生成自動遞增的值,但它看起來更像一個技巧 而不是一個特性。實際上,由于在一系列的 iota 生成的常量中插入一行會改變下列值的值,這是很危險的。由于生成的值是在整個代碼中使用的值,因此這會導致有趣的(而不是!)意外。
這也意味著沒有辦法讓編譯器徹底檢查 switch 語句,也無法描述類型中允許的值。
沒有泛型
很難想象一種沒有泛型的現代靜態類型化語言,但這就是你在 Go 中看到的:它沒有泛型…或者更精確地說,幾乎沒有泛型,我們會看到它比沒有泛型更糟糕。
內置的 slice、map、array 和 channel 都是泛型。聲明一個 map[string]MyStruct 清楚地顯示了具有兩個參數的泛型類型的使用。這很好,因為它允許類型安全編程捕獲各種錯誤。
然而,沒有用戶可定義的泛型數據結構。這意味著您不能定義可重用的抽象,它可以以類型安全的方式使用任何類型。您必須使用非類型 interface{},并將值轉換為適當的類型。任何錯誤只會在運行時被抓住,會導致 panic。對于 Java 開發人員來說,這就像回到 回退 Java 5 個版本到 2004 年。
Go語言基礎
基本數據類型
變量和常量
普通賦值:
// var 變量名稱 變量類型 = 值 var num int = 1平行賦值:
var num1,num2 int = 1, 2多行賦值:
var (num1 int = 1num2 int = 2 )整數類型的命名和寬度
Go 的 整數類型 一共有 10 個其中計算架構相關的整數類型有兩個,即有符號的整數類型 int 和無符號的整數類型 uint。在不同計算架構的計算機上,它們體現的寬度(存儲某個類型的值所需要的空間)是不一樣的。空間的單位可以是 bit 也可以是字節 byte。
除了這兩個計算架構相關的整數類型之外,還有 8 個可以顯式表達自身寬度的整數類型:
整數類型值的表示法
如果以 8 進制為變量 num 賦值:
num = 039 // 用"0"作為前綴以表明這是8進制表示法如果以 16 進制為變量 num 賦值:
num = 0x39浮點類型
浮點數 類型有兩個:float32/float64。浮點數類型的值一般由整數部分、小數點 “.” 和小數部分組成。另外一種表示方法是在其中加入指數部分。指數部分由 “E” 或 “e” 以及帶正負號的 10 進制整數表示。
復數類型
復數類型有兩個:complex64 和 complex128。實際上,complex64 類型的值會由兩個 float32 類型的值分別表示復數的實數部分和虛數部分。而 complex128 類型的值會由兩個 float64 類型的值表示復數的實數部分和虛數部分。
byte與rune
byte 與 rune 都屬于別名類型。byte 是 uint8 的別名類型,而 rune 是 int32 的別名類型。一個 rune 的類型值即可表示一個 Unicode 字符。一個 Unicode 代碼點通常由 “U+” 和一個以十六進制表示法表示的整數表示。
字符串類型
字符串 的表示法有兩種,即:原生表示法和解釋型表示法。原生表示法,需用用反引號 “`” 把字符序列包起來,如果用解釋型表示法,則需要用雙引號 “”" 包裹字符序列。
var str1 string = “str” var str1 string = `str`二者的區別是,前者表示的是所見即所得的(除了回車符)。后者所表示的值中轉義符會起作用。字符串值是不可變的,如果我們創建了一個此類型的值,就不可能再對它本身做任何修改。
數組類型
一個數組是可以容納若干相同類型的元素的容器。數組的長度是固定的。如下聲明一個數組類型:
type MyNumbers [3]int類型聲明語句由關鍵字 type、類型名稱和類型字面量組成,上面這條類型聲明語句實際上是為數組類型 [3]int 聲明了一個別名類型。這使得我們可以把 MyNumbers 當作數組類型 [3]int 來使用。
我們表示這樣一個數組類型的值的時候。應該把該類型的類型字面量寫在最左邊,然后用花括號包裹該值包含的若干元素,各元素之間以(英文半角)逗號分割,即:
[3]int{1,2,3}現在我們把這個數組字面量賦給一個名為 numbers 的變量:
var numbers = [3]int{1,2,3}這是一條變量聲明語句,它在聲明變量的同時為該變量賦值,另一種方式是在其中的類型字面量中省略代表其長度的數組,例:
var numbers = [...]int{1,2,3}可以用如下方式訪問該變量中的任何一個元素。例:
numbers[0] numbers[1] numbers[2]如果要修改數組值中的某一個元素值,可以:
numbers[1] = 4可以用如下方式獲取數組長度:
var length = len(numbers)如果一個數組沒有賦值,則它的默認值為
[length]type{0,0,0…}切片類型
切片(slice)與數組一樣也是可以若干相同類型元素的容器。與數組不同的是切片類型的長度不確定。每個切片值都會將數組作為其底層數據結構。表示切片類型的字面量如:
[]int或者是:
[]string切片類型的聲明可以這樣:
type MySlice []int對切片值的表示也與數組值相似
[]int{1,2,3}操作數組值的方法同樣適用于切片值。還有一種操作數組的方式叫做“切片”,實施切片操作的方式就是切片表達式。例:
var number3 = [5]int{1,2,3,4,5} var slice1 = numbers3[1:4]上例中切片表達式 numbers3[1:4] 的結果為 []int{2,3,4} 很明顯被切下的部分不包含元素上界索引指向的元素。實際上 slice1 這個切片值的底層數組正是 number3 的值。我們也可以在切片值上實施切片操作:
var slice2 = slice1[1:3]除了長度切片值以及數組值還有另外一個屬性–容量。數組的容量總是等于其長度,而切片值的容量往往與其長度不同。如下圖:
如圖所示,一個切片值的容量即為它的第一個元素值在其底層數組中的索引值與該數組長度的差值的絕對值??梢允褂胏ap()內建函數獲取數組、切片、通道類型的值的容量:
var capacity2 int = cap(slice2)切片類型屬于引用類型,它的零值即為 nil,即空值。如果我們只聲明了一個切片類型而不為它賦值,則它的默認值 nil。切片的更多操作方法有些時候我們可以在方括號中放入第三個正整數。
numbers3[1:4:4]第三個正整數為容量上界索引,它意義在于可以把作為結果的切片值的容量設置的更小。它可以限制我們通過這個切片值對其底層數組中的更多元素的訪問。上節中 numbers3 和 slice 的賦值語句如下:
var numbers3 = [5]int{1,2,3,4,5} var slice1 = numbers3[1:4]這時,變量 slice1 的值是 []int{2,3,4}。但是我們可以通過如下操作將其長度延展與其容量相同:
slice1 = slice1[:cap(slice1)]通過此操作,變量 slice1 的值變為了 []int{2,3,4,5},且其長度和容量均為 4?,F在 number3 的值中的索引值在 (1,5) 范圍內的元素都被體現在了 slice1 的值中。這是以 number3 的值是 slice1 的值的底層數組為前提的。
這意味著我們可以輕而易舉地通過切片訪問其底層數組中對應索引值更大的更多元素。如果我們編寫的函數返回了這樣一個切片值,那么得到它的程序很可能會通過這種技巧訪問到本不應該暴露給它的元素。如果我們在切片中加入了第三個索引(即容量上限索引),如:
var slice1 = numbers3[1:4:4]那么在此之后,我們將無法通過 slice1 訪問到 number3 的值中的第五個元素。雖然切片值在上述方面受到了其容量的限制。但是我們可以通過另外一種手段對其進行不受限制的擴展。這需要用到內建函數 append。append 會對切片值進行擴展并返回一個新的切片值,使用方法如下:
slice1 = append(slice1, 6, 7)通過上述操作,slice1 的值變為了 []int{2,3,4,6,7}。一旦擴展操作超出了被操作的切片值的容量,那么該切片的底層數組就會被替換。最后一種操作切片的方式是 “復制”。該操作的實施方法是調用 copy 函數。
該函數接收兩個類型相同的切片值作為參數,并把第二個參數值中的元素復制到第一個參數值中的相應位置(索引值相同)上。這里有兩點需要注意:這種復制遵循最小復制原則,即:被復制的元素的個數總是等于長度較短的那個參值的長度。
與 append 函數不同,copy 函數會直接對其第一個參數值進行修改。
通過上述復制操作,slice4 會變成 []int{2,3,4,6,7,0,0}。
字典類型
Go 語言的字典(Map)類型是一個哈希表的實現。字典類型的字面量如下:
map[K]T其中,“K” 為鍵的類型,而 “T” 則代表元素(值)的類型。如果我們描述一個鍵類型為 int,值類型為 string 的字典類型的話:
map[int]string字典的鍵類型必須是可比較的,否則會引起錯誤,即鍵不能是切片、字典、函數類型。
字典值的字面量表示法實際上與數組的切片的字面量表示法很相似。最左邊仍然是類型字面量,右邊緊挨著由花括號包裹且有英文逗號分隔的鍵值對。每個鍵值對的鍵和值之間由冒號分隔。以字典類型 map[int]string 為例。他的值的字面量可以是這樣的:
map[int]string{1:"a",2:"b"m,3:"c"}我們可以把這個值賦給一個變量:
mm := map[int]string{1:"a",2:"b",3:"c"}可用索引表達式取出字典中的值:
b := mm[2]可以用索引表達式賦值:
mm[2] = b + "2"這樣 mm 中鍵為 2 的值變為了 “b2”。可以用如下方式向字典中添加一個鍵值對:
mm[4] = ""對于字典值來說,如果指定鍵沒有對應的值則默認為該類型的空值。所以 mm[5] 會返回一個 “”。但是這樣的話我們就不知道 mm[5] 到底是 “” 還是 mm[5] 沒有這個值。所以 go 提供了另外一種寫法:
e, ok := mm[5]針對字典的索引表達式可以有兩個求職結果,第二個求職結果是 bool 類型的。它用于表明字典值中是否存在指定的鍵值對。 從字典中刪除鍵值對的方法非常簡單,僅僅是調用內建函數 delete:
delete(mm, 4)無論 mm 中是否存在以 4 為鍵的鍵值對,delete 都刪除。 字典類型屬于引用類型,它的零值即為 nil。
通道類型
通道(Channel)是 Go 語言中一種非常獨特的數據結構。它可用于在不同 Goroutine 之間傳遞類型化的數據。并且是并發安全的。相比之下,之前幾種數據類型都不是并發安全的。
Goroutine 可以被看作是承載可被并發執行的代碼塊的載體。它們由 Go 語言的運行時系統調度,并依托操作系統線程(又稱內核線程)來并發地執行其中的代碼塊。
通道類型的表示方法很簡單,僅由兩部分組成:
chan T在這個類型字面量中,左邊是代表通道類型的關鍵字 chan,而右邊則是一個可變的部分,即代表該通道類型允許傳遞的數據的類型(或稱通道的元素類型)。
與其他的數據類型不同,我們無法表示一個通道類型的值,因此,我們無法用字面量來為通道類型的變量賦值。只能通過調用內建函數 make 來達到目的。make 參數可接受兩個參數,第一個參數是代表了將被初始化的值的類型的字面量(例: chan int),而第二個參數則是值的長度,例如,若我們想要初始化一個長度為 5 且元素類型為int的通道值,則需要這樣寫:
make(chan int, 5)make 函數也可以被用來初始化切片類型或字典類型的值。暫存在通道值中的數據是先進先出。下面,我們聲明一個通道類型的變量,并為其賦值:
ch1 := make(chan string, 5)這樣一來,我們就可以使用接受操作符 <- 向通道值發送數據了。當然,也可以使用它從通道值接收數據,例如,如果我們要向通道 ch1 發送字符串 “value1”,那么應該這樣做:
ch1 <- “value1"如果我們從 ch1 那里接收字符串,則要這樣:
<- ch1我們可以把接受到字符串賦給一個變量,如:
value := <- ch1與針對字典值的索引表達式一樣,針對通道值的接受操作也可以有第二個結果值:
value, ok := <- ch1這里的 ok 的值是 bool 類型的。它代表了通道值的狀態,true 代表通道值有效,而 false 則代表通道值已無效(或稱已關閉),更深層次的原因是,如果在接受操作進行之前或過程中通道值被關閉了,則接收操作會立即結束并返回一個該通道值的元素類型的零值。
可以通過函數 close 來關閉通道:
close(ch1)對通道值的重復關閉會引發運行時異常,會使程序崩潰。在通道值有效的前提下,針對它的發送操作會在通道值已滿(其中緩存的數據的個數已等于它的長度)時被阻塞。而向一個已被關閉的通道值發送數據會引發運行時異常。針對有效通道值的接收操作會在它已經為空時被阻塞。通道類型屬于引用類型,它的零值為 nil。
流程控制
條件語句
對應的關鍵字為 if、 else 和 else if:
if a := 1; a >= 1 {fmt.Println("OK") }選擇語句
對應的關鍵字為 switch、 case 和 select:
switch i {case 0:fmt.Printf("0")case 1:fmt.Printf("1")case 2:fallthroughcase 3:fmt.Printf("3")case 4, 5, 6:fmt.Printf("4, 5, 6")default:fmt.Printf("Default") }循環語句
對應的關鍵字為 for 和 range:
sum := 0 for i := 0; i < 10; i++ {sum += i }跳轉語句
func myfunc() {i := 0HERE:fmt.Println(i)i++if i < 10 {goto HERE} }函數
概述
首先函數的格式是固定的,func+函數名+ 參數 + 返回值(可選) + 函數體。例 :
func main() {fmt.Println("Hello go") }在 golang 中有兩個特殊的 函數,main 函數和 init 函數,main 函數不用介紹在所有語言中都一樣,它作為一個程序的入口,只能有一個。init 函數在每個 package 是可選的,可有可無,甚至可以有多個(但是強烈建議一個 package 中一個 init 函數),init 函數在你導入該 package 時程序會自動調用 init 函數,所以 init 函數不用我們手動調用,另外它只會被調用一次,因為當一個 package 被多次引用時,它只會被導入一次。
參數傳遞
-
普通變量
使用普通變量作為函數參數的時候,在傳遞參數時只是對變量值得拷貝,即將實參的值復制給變參,當函數對變參進行處理時,并不會影響原來實參的值。
-
指針
函數的變量不僅可以使用普通變量,還可以使用指針變量,使用指針變量作為函數的參數時,在進行參數傳遞時將是一個地址看唄,即將實參的內存地址復制給變參,這時對變參的修改也將會影響到實參的值。
-
數組
和其他語言不同的是,go語言在將數組名作為函數參數的時候,參數傳遞即是對數組的復制。在形參中對數組元素的修改都不會影響到數組元素原來的值。
-
slice, map, chan
在使用 slice, map, chan 作為函數參數時,進行參數傳遞將是一個地址拷貝,即將底層數組的內存地址復制給參數 slice, map, chan 。這時,對 slice, map, chan 元素的操作就是對底層數組元素的操作。
-
函數名字
在 go 語言中,函數也作為一種數據類型,所以函數也可以作為函數的參數來使用。
返回值
go 語言可以返回局部變量的指針,因為 go 語言的回收機制是當銷毀棧上的臨時數據且發現有被外部引用的棧上變量時,會自動轉移到堆上。
閉包
和其他語言類似,golang 也支持閉包函數:
package mainimport "fmt"func adder() func(int) int {sum := 0return func(x int) int {sum += xreturn sum} }func main() {pos, neg := adder(), adder()for i := 0; i < 10; i++ {fmt.Println(pos(i),neg(-2*i),)} }Go語言入門教程總結
很多人將 Go 語言稱為 21 世紀的 C 語言,因為 Go 不僅擁有 C 語言的簡潔和性能,而且還很好的提供了 21 世紀互聯網環境下服務端開發的各種實用特性,讓開發者在語言級別就可以方便的得到自己想要的東西。
總結
以上是生活随笔為你收集整理的Golang基础知识入门详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vc2019(1)
- 下一篇: 学习Spring Boot:(六) 集成