GO的優缺點:
此處引用華為云開發者聯盟的一篇文章:
GO語言的亮點很明顯:
-
GoDoc。 GoDoc的靜態語言分析能力很強大,可以直接從代碼和注釋生成漂亮的文檔。這一點區別于其他的類似工具如JavaDoc, PHPDoc或者JSDoc。這些工具需要添加額外的注解,比較麻煩。
-
GoFmt。代碼格式化一直是程序員編碼的痛點,主要的困境在于沒有統一的標準,Go通過內置的GoFmt工具來解決這個問題。
-
GoLint。代碼語法提示也在Go中通過GoLint工具進行了統一。
-
測試框架內置。這一點區別于其他的流行語言如Java, C#, Javascript,他們需要選擇測試框架進行測試代碼編寫。而Go語言直接內置了測試框架,可以程序員快速生成測試框架代碼,省時,省力。
-
GoRoutines的并行化處理能力。Go對于并行化的支持做得非常徹底。直接把繁瑣的線程創建封裝起來,程序員無需擔心線程創建中可能遭遇的硬件資源不足的問題。
-
使用Interface支持多態。在Go語言中省去了面向對象編程中父類繼承的特征。在使用多態的地方使用Interface的模式實現多態,這樣把代碼結構線性化、平行化,從而降低了代碼的復雜度。
GO語言的缺點:
1.異常和錯誤處理啰嗦。如果你習慣了使用異常處理,那么你就會很討厭Go里面的錯誤處理方式。
-
你定義一個Go函數,返回一個錯誤;
-
調用這個函數時,你要判斷返回錯誤是否為空;
-
-
-
然后判斷是哪種錯誤
-
根據哪種錯誤進行相應的處理
-
-
-
這樣的處理實在是啰嗦
2.空值判斷。
-
在Go中指針類型可以是空,如果一個函數返回指針,在Go中,你需要進行上述第一條所說的啰嗦的錯誤處理,然后才可以使用指針。否則,如果這個指針為空,你使用它的話,會Crash。
3.作用域的限定比較另類。
-
大寫字母開頭可以全局訪問,小寫字母開頭只能在當前文件可見。
-
Go語言假定每個程序員都清楚the whole picture, 這在實際工作場景中是不現實的。
-
為了踐行防御性編程理念,在Go中,不同程序員不得不創建大量的文件和目錄。這樣也不利于管理。
4.Go語言缺乏不可改寫機制。
-
這是可能是因為它強調性能高于潛在的bug的規避。
5.Go語言缺乏泛型支持,我們不得不對潛在的數據類型進行轉換。
6.Go語言缺乏繼承機制,這導致共性功能的代碼重用很難。
7.Go語言沒有枚舉類型,類似的功能不得不使用const,很不方便。
Go語言的應用場景主要集中在后端應用層和工具類開發,用來寫單體服務,微服務以及工具。Go語言相對比較年輕,需要經過長時間的洗禮,大量的項目開發驗證。目前來看,還無法與Java, C#這類語言的生態進行競爭。
綜上,使用哪種語言是一種選擇,適宜、簡潔、高效、安全是核心。
作者:華為云開發者聯盟 鏈接:為什么 Go 語言在某些方面的性能還不如 Java? - 知乎 來源:知乎
package main
?
import "fmt"
?
func main() {fmt.Println("hello world")
}
包的概念
-
每個 Go 文件都屬于且僅屬于一個包。一個包可以由許多以
.go
為擴展名的源文件組成。 -
你必須在源文件中非注釋的第一行指明這個文件屬于哪個包,如:
package main
。package main
表示一個可獨立執行的程序,每個 Go 應用程序都包含一個名為main
的包。 -
一個應用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代碼都寫在一個巨大的文件里:你可以用一些較小的文件,并且在每個文件非注釋的第一行都使用 package main 來指明這些文件都屬于 main 包。如果你打算編譯包名不是為 main 的源文件,如 pack1,編譯后產生的對象文件將會是 pack1.a 而不是可執行程序。另外要注意的是,所有的包名都應該使用小寫字母。
-
如果對一個包進行更改或重新編譯,所有引用了這個包的客戶端程序都必須全部重新編譯。
導包格式:
import "fmt"
import "os"
import "fmt"; import "os"
import ("fmt"; "os")
可見性規則:
當標識符以一個大寫字母開頭則等同于java中的public,以小寫字母開頭則等同于private
因此,在導入一個外部包后,能夠且只能夠訪問該包中導出的對象。假設在包 pack1 中我們有一個變量或函數叫做 Thing(以 T 開頭,所以它能夠被導出),那么在當前包中導入 pack1 包,Thing 就可以像面向對象語言那樣使用點標記來調用:pack1.Thing(pack1 在這里是不可以省略的)。
也可以對包名進行重新設置,如:import fm "fmt"
。
package main
?
import fm "fmt"
?
func main() {fm.Println("hello world")
}
注意事項:
-
如果你導入了一個包卻沒有使用它,則會在構建程序時引發錯誤,如
imported and not used: os
,這正是遵循了 Go 的格言:“沒有不必要的代碼!“。
函數
func functionName();
在 ()
中寫入 0 個或多個函數的參數(使用逗號 ,
分隔),每個參數的名稱后面必須緊跟著該參數的類型。
main函數:
-
main 函數是每一個可執行程序所必須包含的,一般來說都是在啟動后第一個執行的函數(如果有 init () 函數則會先執行該函數)。
-
main 函數既沒有參數,也沒有返回類型(與 C 家族中的其它語言恰好相反)
函數體:
-
函數里的代碼(函數體)使用大括號 {} 括起來。
-
左大括號 { 必須與方法的聲明放在同一行,這是編譯器的強制規定,否則你在使用 gofmt 時就會出現錯誤提示。
-
右大括號 } 需要被放在緊接著函數體的下一行。如果你的函數非常簡短,你也可以將它們放在同一行:
特別注意: Go 語言雖然看起來不使用分號作為語句的結束,但實際上這一過程是由編譯器自動完成,因此才會引發像上面這樣的錯誤
符合規范的函數一般寫成如下的形式:
func functionName(parameter_list) (return_value_list) {…
}
程序正常退出的代碼為 0 即 Program exited with code 0
;如果程序因為異常而被終止,則會返回非零值,如:1。這個數值可以用來測試是否成功執行一個程序。
package main
?
import "fmt"
?
func main() {fmt.Println("hello world")//result := Sum(1, 2)fmt.Println("Sum result:", Sum(1, 3))fmt.Println("____________________")fmt.Println(swap("hello", "world"))
}
func swap(x, y string) (string, string) {return y, x
}
?
func Sum(a, b int) int {return a + b
}
?
類型在變量聲明之后
注釋
單行注釋是最常見的注釋形式,你可以在任何地方使用以 //
開頭的單行注釋。多行注釋也叫塊注釋,均已以 /*
開頭,并以 */
結尾,且不可以嵌套使用,多行注釋一般用于包的文檔描述或注釋成塊的代碼片段。
規范:
-
在首行的簡要注釋之后可以用成段的注釋來進行更詳細的說明,而不必擁擠在一起。
-
在多段注釋之間應以空行分隔加以區分。
類型
-
可以包含數據的變量(或常量),可以使用不同的數據類型或類型來保存數據。
-
使用 var 聲明的變量的值會自動初始化為該類型的零值。
-
類型可以是基本類型,如:int、float、bool、string;
-
結構化的(復合的),如:struct、array、slice、map、channel;(nil 為默認值,在 java 中為null)
-
只描述類型的行為的,如:interface。
注意 Go 語言中不存在類型繼承。
函數也可以是一個確定的類型,就是以函數作為返回類型。這種類型的聲明要寫在函數名和可選的參數列表之后,例如:
func FunctionName (a typea, b typeb) typeFunc
你可以在函數體中的某處返回使用類型為 typeFunc 的變量 var:
return var
一個函數可以擁有多返回值,返回類型之間需要使用逗號分割,并使用小括號 () 將它們括起來,如:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
實例:
package main
?
import "fmt"
?
// 聲明一個函數類型
type Operator func(int, int) int
?
// 定義一個加法函數
func Add(a, b int) int {return a + b
}
?
// 定義一個減法函數
func Subtract(a, b int) int {return a - b
}
?
func main() {// 使用函數類型作為變量類型var op Operator
?// 將加法函數賦值給函數變量op = Addresult := op(5, 3)fmt.Println("5 + 3 =", result)
?// 將減法函數賦值給函數變量op = Subtractresult = op(5, 3)fmt.Println("5 - 3 =", result)
}
package main
?
import ("fmt"
)
?
const c = "C"
?
var v int = 5
?
type T struct{}
?
func init() { // initialization of package
}
?
func main() {var a intFunc1()// ...fmt.Println(a)
}
?
func (t T) Method1() {//...
}
?
func Func1() { // exported function Func1//...
}
Go 程序的執行(程序啟動)順序如下:
-
按順序導入所有被 main 包引用的其它包,然后在每個包中執行如下流程:
-
如果該包又導入了其它的包,則從第一步開始遞歸執行,但是每個包只會被導入一次。
-
然后以相反的順序在每個包中初始化常量和變量,如果該包含有 init 函數的話,則調用該函數。
-
在完成這一切之后,main 也執行同樣的過程,最后調用 main 函數開始執行程序。
類型轉換
Go 語言不存在隱式類型轉換,因此所有的轉換都必須顯式說明,就像調用一個函數一樣(類型在這里的作用可以看作是一種函數):
valueOfTypeB = typeB(valueOfTypeA)
類型 B 的值 = 類型 B (類型 A 的值)
package main
?
import ("fmt""reflect"
)
?
func main() {a := 5.0b := int(a)fmt.Println("the type of a is: ", reflect.TypeOf(a))fmt.Println("the type of a is: ", reflect.TypeOf(b))
}
賦值:
在Go語言中,賦值操作使用 =
運算符。賦值操作符將右側表達式的值賦給左側的變量。下面是一些關于賦值的基本用法:
單個變量賦值
package main
?
import "fmt"
?
func main() {// 單個變量賦值var a inta = 10
?fmt.Println(a)
}
在這個例子中,我們聲明了一個整數變量 a
,然后將值 10
賦給變量 a
。
多個變量同時賦值
package main
?
import "fmt"
?
func main() {// 多個變量同時賦值var b, c intb, c = 20, 30
?fmt.Println(b, c)
}
在這個例子中,我們聲明了兩個整數變量 b
和 c
,然后使用逗號分隔的方式同時給它們賦值。
簡短聲明并賦值
package main
?
import "fmt"
?
func main() {// 簡短聲明并賦值d := 40
?fmt.Println(d)
}
在這個例子中,我們使用 :=
運算符進行了簡短聲明并賦值操作,聲明了一個整數變量 d
并賦值為 40
。
注意:
-
如果在相同的代碼塊中,我們不可以再次對于相同名稱的變量使用初始化聲明
-
函數外的每個語句都必須以關鍵字開始(
var
,func
等等),因此:=
結構不能在函數外使用。
匿名變量
在Go語言中,可以使用下劃線 _
來表示匿名變量,用于占位,表示不關心這個值。
package main
?
import "fmt"
?
func main() {_, e := 50, 60
?fmt.Println(e)
}
在這個例子中,我們使用匿名變量 _
來占位,不關心第一個賦值的值,只關心第二個值 e
。
如果你想要交換兩個變量的值,則可以簡單地使用 a, b = b, a。
(在 Go 語言中,這樣省去了使用交換變量的必要)
空白標識符 _ 也被用于拋棄值,如值 5 在:_, b = 5, 7 中被拋棄。
_ 實際上是一個只寫變量,你不能得到它的值。這樣做是因為 Go 語言中你必須使用所有被聲明的變量,但有時你并不需要使用從一個函數得到的所有返回值。
命名返回值
Go 的返回值可被命名,它們會被視作定義在函數頂部的變量。
返回值的名稱應當具有一定的意義,它可以作為文檔使用。
沒有參數的 return
語句返回已命名的返回值。也就是 直接
返回。
直接返回語句應當僅用在下面這樣的短函數中。在長的函數中它們會影響代碼的可讀性。
package main
?
import "fmt"
?
func split(sum int) (x, y int) {x = sum * 4 / 9y = sum - xreturn
}
?
func main() {fmt.Println(split(17))
}
變量
聲明變量的一般形式是使用 var
關鍵字:var identifier type
。
var
語句用于聲明一個變量列表,跟函數的參數列表一樣,類型在最后。
就像在這個例子中看到的一樣,var
語句可以出現在包或函數級別。
package main
?
import "fmt"
?
var c, python, java bool
?
func main() {var i intfmt.Println(i, c, python, java)
}
?
是為了避免像 C 語言中那樣含糊不清的聲明形式,例如:int* a, b;
。在這個例子中,只有 a 是指針而 b 不是。如果你想要這兩個變量都是指針,則需要將它們分開書寫。而在 Go 中,則可以很輕松地將它們都聲明為指針類型:
var a, b *int
兩種寫法:
-
var a int var b bool var str string
-
var (a intb boolstr string )
這種因式分解關鍵字的寫法一般用于聲明全局變量。
注:變量的命名規則遵循駱駝命名法。如果你的全局變量希望能夠被外部包所使用,則需要將首個單詞的首字母也大寫
格式化輸出:
格式化字符串可以含有一個或多個的格式化標識符,例如:%..,其中 .. 可以被不同類型所對應的標識符替換,如 %s 代表字符串標識符、%v 代表使用類型的默認輸出格式的標識符。這些標識符所對應的值從格式化字符串后的第一個逗號開始按照相同順序添加,如果參數超過 1 個則同樣需要使用逗號分隔
init 函數
變量除了可以在全局聲明中初始化,也可以在 init 函數中初始化。這是一類非常特殊的函數,它不能夠被人為調用,而是在每個包完成初始化后自動執行,并且執行優先級比 main 函數高。(類似java中的構造函數?)
每個源文件都只能包含一個 init 函數。初始化總是以單線程執行,并且按照包的依賴關系順序執行。
基礎類型:
布爾類型 bool
var b bool = ture
布爾型的值只可以是常量 true 或者 false。
兩個類型相同的值可以使用相等 ==
或者不等 !=
運算符來進行比較并獲得一個布爾型的值。
規范:對于布爾值而言,好的命名能夠很好地提升代碼的可讀性。例如以 is 或者 Is 開頭的 isSorted、isFinished、isVisible,使用這樣的命名能夠在閱讀代碼的獲得閱讀正常語句一樣的良好體驗
整型和浮點類型
Go 也有基于架構的類型,例如:int、uint 和 uintptr。
這些類型的長度都是根據運行程序所在的操作系統類型所決定的:
int 和 uint 在 32 位操作系統上,它們均使用 32 位(4 個字節),在 64 位操作系統上,它們均使用 64 位(8 個字節)。 uintptr 的長度被設定為足夠存放一個指針即可。 Go 語言中沒有 float 類型。(Go 語言中只有 float32 和 float64)沒有 double 類型。
-
整數:
-
int8(-128 -> 127)
-
-
int16(-32768 -> 32767)
-
int32(-2,147,483,648 -> 2,147,483,647)
-
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
-
無符號整數:
-
uint8(0 -> 255)
-
-
uint16(0 -> 65,535)
-
uint32(0 -> 4,294,967,295)
-
uint64(0 -> 18,446,744,073,709,551,615)
-
浮點型(IEEE-754 標準):
-
float32(+- 1e-45 -> +- 3.4 * 1e38)
-
float64(+- 5 1e-324 -> 107 1e308)
-
int 型是計算最快的一種類型。
-
注意:
-
整型的零值為 0,浮點型的零值為 0.0。
-
float32 精確到小數點后 7 位,float64 精確到小數點后 15 位。(盡可能地使用 float64,因為
math
包中所有有關數學運算的函數都會要求接收這個類型)
你可以通過增加前綴 0 來表示 8 進制數(如:077),增加前綴 0x 來表示 16 進制數(如:0xFF),以及使用 e 來表示 10 的連乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
復數
Go 擁有以下復數類型:
-
complex64 (32 位實數和虛數)
-
complex128 (64 位實數和虛數)
復數使用 re+imI 來表示,其中 re 代表實數部分,im 代表虛數部分,I 代表根號負 1。
函數 real(c)
和 imag(c)
可以分別獲得相應的實數和虛數部分。
在使用格式化說明符時,可以使用 %v
來表示復數,但當你希望只表示其中的一個部分的時候需要使用 %f
。
字符串
字符串是 UTF-8 字符的一個序列,由于該編碼對占用字節長度的不定性,Go 中的字符串也可能根據需要占用 1 至 4 個字節。(java始終為2字節)
字符串是一種值類型,且值不可變,即創建某個文本后你無法再次修改這個文本的內容;更深入地講,字符串是字節的定長數組。(字符串0值是 " ")
和 C/C++ 不一樣,Go 中的字符串是根據長度限定,而非特殊字符 \0
。(和redis sds很像嘛,哈哈)
一般的比較運算符(==、!=、<、<=、>=、>)通過在內存中按字節比較來實現字符串的對比。你可以通過函數 len() 來獲取字符串所占的字節長度,例如:len(str)。
字符串的內容(純字節)可以通過標準索引法來獲取,在中括號 [] 內寫入索引,索引從 0 開始計數:
-
字符串 str 的第 1 個字節:str[0]
-
第 i 個字節:str[i - 1]
-
最后 1 個字節:str[len(str)-1]
注意:
-
這種轉換方案只對純 ASCII 碼的字符串有效。
-
獲取字符串中某個字節的地址的行為是非法的,例如:&str[i]。
字符串拼接符 +
兩個字符串 s1 和 s2 可以通過 s := s1 + s2 拼接在一起。
s2 追加在 s1 尾部并生成一個新的字符串 s。
字符串api:4.7. strings 和 strconv 包 | 第四章. 基本結構和基本數據類型 |《Go 入門指南》| Go 技術論壇 (learnku.com)
時間和日期
time
包為我們提供了一個數據類型 time.Time
(作為值使用)以及顯示和測量時間和日期的功能函數。
當前時間可以使用 time.Now() 獲取,或者使用 t.Day()、t.Minute() 等等來獲取時間的一部分;你甚至可以自定義時間格式化字符串,例如: fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) 將會輸出 21.07.2011。
常量
常量的聲明與變量類似,只不過是使用 const
關鍵字。
常量可以是字符、字符串、布爾值或數值。
常量不能用 :=
語法聲明。
指針
程序在內存中存儲它的值,每個內存塊(或字)有一個地址,通常用十六進制數表示,如:0x6b0820 或 0xf84001d7f0。
Go 語言的取地址符是 &,放到一個變量前使用就會返回相應變量的內存地址。
例:
package main
?
import "fmt"
?
func main() {var a intfmt.Println("the address of a is: ", &a)
}
一個指針變量可以指向任何一個值的內存地址 它指向那個值的內存地址,在 32 位機器上占用 4 個字節,在 64 位機器上占用 8 個字節,并且與它所指向的值的大小無關。
你可以在指針類型前面加上 * 號(前綴)來獲取指針所指向的內容,這里的 * 號是一個類型更改器。使用一個指針引用一個值被稱為間接引用。
當一個指針被定義后沒有分配到任何變量時,它的值為 nil。
一個指針變量通常縮寫為 ptr。
位運算
位運算只能用于整數類型的變量,且需當它們擁有等長位模式時。
%b
是用于表示位的格式化標識符。
二元運算符
與java類似,主要注意一點:位清除 &^
:將指定位置上的值設置為 0。
運算符
同時,帶有 ++ 和 -- 的只能作為語句,而非表達式,因此 n = i++ 這種寫法是無效的,其它像 f(i++) 或者 a[i]=b[i++] 這些可以用于 C、C++ 和 Java 中的寫法在 Go 中也是不允許的。
格式化說明
在格式化字符串里:
-
%d 用于格式化整數(%x 和 %X 用于格式化 16 進制表示的數字),
-
%g 用于格式化浮點型(%f 輸出浮點數,%e 輸出科學計數表示法),
-
%0d 用于規定輸出定長的整數,其中開頭的數字 0 是必須的。
-
%n.mg 用于表示數字 n 并精確到小數點后 m 位,除了使用 g 之外,還可以使用 e 或者 f。
-
%c
用于表示字符;當和字符配合使用時,%v
或%d
會輸出用于表示該字符的整數; -
%U
輸出格式為 U+hhhh 的字符串 -
%T 返回值的type。
-
%p
指針的格式化標識符 -
%c 格式化輸出char
學習參考資料:
《Go 入門指南》 | Go 技術論壇 (learnku.com)
Go 語言之旅