在有一定java基礎的情況下,學習了Go的知識。記錄下來一些筆記供以後複習。
SDK版本:1.12.7
go是編譯型語言。debug和Java很像。也有垃圾回收機制。因此內存管理不用開發考慮。
go也是一個強類型語言,更快速的開發併發程序。
1.go的所有源文件應位於工作空間內名爲src的目錄中。
改環境變量指定gopath工作空間的位置。環境變量可以指定多個gopath
C:\Users\zhangyong\go-test\src
2.運行go程序。main包下的go程序。
go run workspace/src/main.go
3.go install D:\Go\go-test\src\service\test
創建go的exe(windows)可執行文件。test.exe 需要注意的是這塊必須寫main.go上一級的絕對路徑。
4.go可以類型推斷,因此不顯示聲明數據類型也會被指定一個數據類型,
go也可以一行聲明多個變量 var a,b =100,120 不用加; 會自動補上,加上也沒錯。但是我覺得這種多行聲明不太好、
5.var a int 這種是聲明變量。
:= 是聲明加賦值,但是需要注意的是 :=左邊的變量必須有未聲明過的變量時才能不報錯。
6.由於Go是強類型的,因此聲明屬於一種類型的變量不能賦予另一種類型的值。
age := 29 // age is int
age = "naveen" //error
7.基本數據類型 int float complex uint string bool byte rune
uint是無符號的。byte 8位 1字節。 rune 32位4字節
""雙引號引起來的是無類型的,並不是字符串。
8.Go是強類型語言,所以int + float 是不允許的,只能顯示強制類型轉換。
int轉float float轉int都是可以的。沒有精度限制。
9.常量。const修飾符修飾。
const b = math.Sqrt(4)//not allowed
應在編譯時知道常量的值。因此,它不能分配給函數調用返回的值,因爲函數調用是在運行時進行的。
10.如果不顯示指定數據類型,聲名出來的變量/常量是無類型的,只是在運行時會動態綁定一個數據類型
"hello world"是一個無類型的常量。 const hello string = "hello world"是string類型的常量。
bool類型也同樣適用。 true 是無類型的常量。 const flag bool = true;
var a = 5
var b float = a
var c rune = a
11.對於數字表達式求值 1/3 1/3.0完全不一樣
前者結果爲0,後者則是返回浮點數0.33333
12.函數聲明語法 可以看到go的變量聲明全是類型在變量名之後。
func functionname(parametername type) returntype {
//function body
}
13.多個返回值的函數&&返回值的取捨。
func rectProps(length, width float64)(float64, float64) {
var area = length * width
var perimeter = (length + width) * 2
return area, perimeter
}
_,a := rectProps(1,2)
14.命名的返回值 return 直接返回聲明的返回值變量。
func test(length, width float64)(a, b float64) {
a = length
b = width
return
}
15.package跟Java類似。只是需要指定main函數的package爲main這個是程序的入口方法。
import "a/x" 導包的方式與Java一致。
16.在其他go文件中引用其他包中的方法時,需要保證方法的聲明是首字母大寫的,否則引用不到。
17.init 函數,一個go文件中可以寫多個init函數。
18.對於帶有init函數的整體執行順序
1.加載import的包。 如果import的包中寫了init那麼此時被加載
2.加載全局變量/常量 ---指的是當前go文件
3.加載init ---指的是當前go文件
4.加載main
19.對於導入沒使用的包,是會報異常的 可以使用錯誤消音來規避這個異常,這樣就只是執行了rectangle的初始化函數init
import _"service/rectangle"
20. if/else
else是不可以在}下面聲明的。因爲 } 結束符號go語言會自動補分號; https://golang.org/ref/spec#Semicolons
由於if{...} else {...}是單個語句,因此分號中不應出現分號。因此,需要else在結束後將其置於同一行。
if statement {
}else{
}
21.for是Go中唯一可用的循環。Go沒有while或do while循環
for i:=0; i<10; i++{
fmt.Println(i)
}
22.補充for break continue的作用與其他語言一致。
label: for 通過label指定外層循環可以配合break,continue打破外層循環、
for{ ---這個是無限循環。
}
23.switch 不用寫break每個case後面會自動加break 關鍵字fallthrough表示順延、
switch a {
case 1:
something;
fallthrough ---順延會執行下面的case/default
default:
other;
}
24.數組 a:=[3]int{1,2,3} [n]T這個是數組的類型聲明。
數組的大小是類型的一部分.因此 [3]int [5]int是不能互相賦值的。 可以不指定數組長度。[...]
如果長度沒有被完全賦值,對應數組下標的值會初始化爲默認值。
我理解的沒有指定長度的並不是數組,因爲他是切片的玩法。也就是[] 是切片的玩法。但實際上被稱爲數組
a是切片類型 b,c是數組類型。a不能作爲sub的入參類型 b,c也不能作爲subtactOne的入參
func main() {
a ,b,c:= []int{1,2,3},[...]int{1,2,3},[3]int{1,2,3}
d,e,f:= a,b,c
f[0],e[0],d[0] = 100,100,100
fmt.Println("before: ",a,b,c,d,e,f)
sub(b)
subtactOne(a)
fmt.Println("after: ",a,b,c)
}
func sub(a [3]int) {
for i:= range a{
a[i] -= 2
}
}
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
before: [100 2 3] [1 2 3] [1 2 3] [100 2 3] [100 2 3] [100 2 3]
after: [98 0 1] [1 2 3] [1 2 3]
len(arr) 輸出數組的長度。
結論:如上[]這個是切片的玩法。[3],[...]這個是數組的玩法。數組玩法是值傳遞,切片玩法可以認爲是引用傳遞,實際上是指針的變化。
25.數組的迭代
for index,value:= range a{
fmt.Println(index,"--數組值爲:",value)
}
26.多維數組。 [2][3]int 2個一維數組,每個一維數組有3個元素
27.切片。切片相當於截取數組的某段引用 arr[start:end] 索引左閉右開區間
切片不擁有自己的任何數據。它只是底層數組的表示。對切片所做的任何修改將反映在基礎數組中。
a:=[]int{1,2,3,4,5,6,7,8}
b:=a[3:5] // cap(b) 切片b下標從start開始到末尾的元素數量。 5
fmt.Println(a,b,len(a),cap(a),len(b),cap(b)) //[1 2 3 4 5 6 7 8] [4 5] 8 8 2 5
28.切片不指定start和end
a:=[]int{1,2,3}
b:=a[:] b[0] = 100
c:=a[:] c[1] = 200
此時輸出a,b,c是一樣的。結論是不指定start和end操作的就是一個切片a。
29.切片的make && Appending to a slice (結合33條看)
make是創建一個初始化的切片。切片爲默認值。func make([] T,len,cap)[] T可通過傳遞類型,長度和容量來創建切片
a:=make([]int,5,5) 切片,len,map 注:len肯定要小於等於cap
func append(s []T, x ...T) []T
。
a = append(a,1,2,3,4) append 追加到切片的末尾。append([]T,...T)
將元素添加至切片中,如果超出了容量,將會返回一個當前切片容量二倍的切片
b:= []int{1,2,3}
a=append(a,b...) ...表示的是可變形參 根據測試結果推出的append的原理:append是通過傳入切片的cap值來判斷是否擴容的,如果append的可變形參超過要添加切片的cap值,就擴容爲原cap的兩倍。如果沒超過原切片數組的cap值就在原切片數組操作。(這是一個大坑) 返回值是一個切片,這個切片是新創建出來的切片,與原切片無關。通過main方法中的welcome和welcome2的輸出可以看出對於沒超過原切片數組的cap值位置的元素是可以在append時被覆蓋掉的。觸發原切片數組cap擴容兩倍時,則append不會覆蓋原切片數組的元素。
我是通過下面例子證明的這個結論。
func change(s ...string) {
s[0] = "Go"
s = append(s, "fun1","fun2","fun3") :label
fmt.Println("change func: ",s)
}
func main() {
welcome := []string{"hello", "world","aaa","bbb","ccc","ddd"}
welcome2 := []string{"hello", "world","aaa","bbb","ccc","ddd"}
a:=welcome[:2]
b:=welcome2[3:]
change(a...) //2,5=>3,5
fmt.Println("-----",welcome)
change(b...) //2,2=>3,4
fmt.Println("-----",welcome2)
m:=append(welcome, "eee","fff")
m[0] = "1"
fmt.Println(welcome,m)
}
change func: [Go world fun1 fun2 fun3]
----- [Go world fun1 fun2 fun3 ddd] -------沒觸發cap擴容append的元素會覆蓋原切片welcome。
change func: [Go ccc ddd fun1 fun2 fun3]
----- [hello world aaa Go ccc ddd]
[Go world fun1 fun2 fun3 ddd] [1 world fun1 fun2 fun3 ddd eee fff]
注意label 上面是沒觸發擴容。append3個元素沒超過cap值 6。
上面的label行改爲如下代碼。(添加了幾個元素,使其觸發擴容) 如下
s = append(s, "fun1","fun2","fun3","fun4","fun5","fun6")
change func: [Go world fun1 fun2 fun3 fun4 fun5 fun6]
----- [Go world aaa bbb ccc ddd] -------觸發cap擴容,append的元素不會覆蓋原切片數組welcome
change func: [Go ccc ddd fun1 fun2 fun3 fun4 fun5 fun6]
----- [hello world aaa Go ccc ddd]
[Go world aaa bbb ccc ddd] [1 world aaa bbb ccc ddd eee fff]
嘗試改超過len索引的值還是會index out of range
var sliceArr []int 初始化的sliceArr爲nil
30.切片可以認爲是由結構類型在內部表示的。
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
31. 切片是對基礎數組的引用。多維切片是 [][]int{{},{}} 這種
切片包含對基礎數組的引用。只要切片在內存中,就無法對數組進行垃圾回收。
此時可以通過copy函數來搞出一個新的切片並與引用的數組脫離關係。看輸出c是與a,b不影響的。
a := []int{1,2,3,4,5}
b := a[:len(a)-2]
c := make([]int, len(b))
copy(c,b)
b[0] = 111
fmt.Println(a,b,c) //[111 2 3 4 5] [111 2 3] [1 2 3]
32.可變形參
可變形參的格式如下,b可以傳入0-無窮個int型參數。與java的可變形參差不多。
func hello(a float64,b ...int){
}
33.可變參數函數的工作方式是將可變數量的參數轉換爲可變參數類型的切片。
如上hello(1.2, 1,2,3) 將1,2,3轉成 []int 在傳入函數中。
func hello(a float64,b ...int){
fmt.Printf("type of nums is %T\n", b)
//type of nums is []int
}
This additional slice creation can be avoided when using variadic functions
我們在這使用可變形參代替切片是因爲:可變形參你不傳他就不會在棧內消耗空間,而切片你必須傳參,哪怕[]int這種沒有真實值也得傳,不然它報錯。
切片是不能直接當做可變形參傳入的,需要這麼玩。
b:=[]int{1,3,5}
hello(1,b...) 跟append一個玩法。使用...這個語法來傳。 注意當參數傳進去時候,內部更改b[0]=2是會影響外部的,因爲切片的玩法是引用傳遞。
34.map 使用非常喜歡的數據結構哈希
創建map:make(map[type of key]type of value)
map的零值是nil
var mapVal map[string]int 聲明map變量。
a:= make(map[string]int)
a["zy"] = 22
a["swt"] = 33
a["lzy"] = 33
a["swt"] = 34
value,ok:=a["zy"]
fmt.Println(a,value,ok)
fmt.Printf("type of ok is %T\n", ok)
獲取一個map元素的方法 a["swt"] 就獲取到指定key的value值了。如果沒找到就返回value這個類型的零值。
value,ok:=a["swt"] ok會返回一個bool值。 for range 遍歷map不能保證順序。
for key, value := range a {
fmt.Printf("---[%s] = %d\n", key, value)
} ------遍歷map
func delete(m map[Type]Type1, key Type) ---刪除一個key len(a) 查map的size
map也是一個引用類型,與切片一樣。
a:= make(map[string]int)
a["zy"] = 22
a["swt"] = 33
b:=a
b["zy"] = 1111
fmt.Println(a,b)//map[lzy:33 swt:34 zy:1111] map[lzy:33 swt:34 zy:1111]
map之間比較相等就得自己寫函數了,沒有原生的方法。 ==只能在map和 nil之間比較。