Golang是我最喜歡的一門語言,它簡潔、高效、易學習、開發效率高、還可以編譯成機器碼…
雖然它一出世,就飽受關注,而且現在在市面上逐漸流行開來,但是,它畢竟是一門新興語言,還有很多讓人不太習慣的地方(即坑,(^__^)),我作為新手,一邊學習,一邊踩坑,希望對其他人有借鑒作用。
檔案名稱字不要輕易以__test.go為結尾
Golang的source檔案的命名和其他語言本無差別,但是Golang內建Unit test,它的unit test有個小規範:所有unit test檔案都要以__test.go為結尾!
所以,當你命名一個非unit test檔案為XXX_test.go,而且執意要編譯時間,就會報錯:no buildable Go source files in XXXXXX(你的檔案路徑)。
所以,切記,以__test.go為結尾的都是unit test的檔案,且切記不要把unit test檔案和普通Go檔案放到一起,一定要把unit test檔案集體放到一個目錄中,否則會編譯不過的。
語句fmt.Println("這裡是漢字:" + 字串變數) 字串變數的值列印不出來的問題
現有如下程式:
package main
import "fmt"
func main() {
m1 := getString()
fmt.Println("現在是:" + m1)
}
func getString()string{
return "abd"
}
運行指令go run test.go
但是單獨列印變數m1卻可以正常顯示
import "fmt"
func main() {
m1 := getString()
fmt.Println(m1)
fmt.Println("現在是:" + m1)
}
func getString()string{
return "abd"
}
這是為什麼呢?很奇怪啊!
其實這要怪IDE,我的IDE是phpstorm + Golang外掛程式包,IDE內建的console對中文的支援很不友好,帶中文的字串列印出來後,容易顯示不全,其實通過terminal列印出來,是正確的!
多個defer出現的時候,多個defer之間按照LIFO(後進先出)的順序執行
package main
import "fmt"
func main(){
defer func(){
fmt.Println("1")
}()
defer func(){
fmt.Println("2")
}()
defer func(){
fmt.Println("3")
}()
}
對應的輸出是:
3
2
1
panic中可以傳任何值,不僅僅可以傳string
package main
import "fmt"
func main(){
defer func(){
if r := recover();r != nil{
fmt.Println(r)
}
}()
panic([]int{12312})
}
輸出:
[12312]
用for range來遍曆數組或者map的時候,被遍曆的指標是不變的,每次遍曆僅執行struct值的拷貝
import "fmt"
type student struct{
Name string
Age int
}
func main(){
var stus []student
stus = []student{
{Name:"one", Age: 18},
{Name:"two", Age: 19},
}
data := make(map[int]*student)
for i, v := range stus{
data[i] = &v //應該改為:data[i] = &stus[i]
}
for i, v := range data{
fmt.Printf("key=%d, value=%v \n", i,v)
}
}
所以,結果輸出為:
key=0, value=&{two 19}
key=1, value=&{two 19}
Go中沒有繼承!沒有繼承!Go中是叫組合!是組合!
import "fmt"
type student struct{
Name string
Age int
}
func (p *student) love(){
fmt.Println("love")
}
func (p *student) like(){
fmt.Println("like first")
p.love()
}
type boy struct {
student
}
func (b * boy) love(){
fmt.Println("hate")
}
func main(){
b := boy{}
b.like()
}
輸出:
like first
love
不管運行順序如何,當參數為函數的時候,要先計算參數的值
func main(){
a := 1
defer print(function(a))
a = 2;
}
func function(num int) int{
return num
}
func print(num int){
fmt.Println(num)
}
輸出:
1
注意是struct的函數,還是* struct的函數
import "fmt"
type people interface {
speak()
}
type student struct{
name string
age int
}
func (stu *student) speak(){
fmt.Println("I am a student, I am ", stu.age)
}
func main(){
var p people
p = student{name:"RyuGou", age:12} //應該改為 p = &student{name:"RyuGou", age:12}
p.speak()
}
輸出:
cannot use student literal (type student) as type people in assignment:
student does not implement people (speak method has pointer receiver)
make(chan int) 和 make(chan int, 1)是不一樣的
chan一旦被寫入資料後,當前goruntine就會被阻塞,知道有人接收才可以(即 “ <- ch”),如果沒人接收,它就會一直阻塞著。而如果chan帶一個緩衝,就會把資料放到緩衝區中,直到緩衝區滿了,才會阻塞
import "fmt"
func main(){
ch := make(chan int) //改為 ch := make(chan int, 1) 就好了
ch <- 1
fmt.Println("success")
}
輸出:
fatal error: all goroutines are asleep - deadlock!
golang 的 select 的功能和 select, poll, epoll 相似, 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作。
select 的代碼形式和 switch 非常相似, 不過 select 的 case 裡的動作陳述式只能是”IO操作”(不僅僅是取值<-channel,賦值channel<-也可以), select 會一直等待等到某個 case 陳述式完成,也就是等到成功從channel中讀到資料。 則 select 語句結束
import "fmt"
func main(){
ch := make(chan int, 1)
ch <- 1
select {
case msg :=<-ch:
fmt.Println(msg)
default:
fmt.Println("default")
}
fmt.Println("success")
}
輸出:
1
success
default可以判斷chan是否已經滿了
import "fmt"
func main(){
ch := make(chan int, 1)
select {
case msg :=<-ch:
fmt.Println(msg)
default:
fmt.Println("default")
}
fmt.Println("success")
}
輸出:
default
success
此時因為ch中沒有寫入資料,為空白,所以 case不會讀取成功。 則 select 執行 default 語句。
Go語言中不存在未初始化的變數
變數定義基本方式為:
var 發量名字 類型 = 運算式
其中類型和運算式均可省略,如果初始設定式被省略,將用零值初始化該變數。
數值變數對應的是0值
布爾變數對應的是false
字串對應的零值是Null 字元串
介面或者參考型別(包括slice,map,chan)變數對應的是nil
數組或者結構體等彙總類型對應的零值是每個元素或欄位對應該類型的零值。
var s string
fmt.Println(s) // ""
:=注意的問題
使用:=定義的變數,僅能使用在函數內部。
在定義多個變數的時候:=周圍不一定是全部都是剛剛聲明的,有些可能只是賦值,例如下面的err變數
in, err := os.Open(infile)
// TODO
out, err := os.Create(outfile)
new在Go語言中只是一個預定義的函數,它並不是一個關鍵字,我們可以將new作為變數或者其他
例如:
func delta(old, new int) int {
return new - old
}
以上是正確的。
並不是使用new就一定會在堆上分配記憶體
編譯器會自動選擇在棧上還是在堆上分配儲存空間,但可能令人驚訝的是,這個選擇並不是由用var還是new聲明變數的方式決定的。
請看例子:
var global *int
func f() {
var x int x=1
global = &x
}
func g() {
y := new(int)
*y = 1
}
f()函數中的x就是在堆上分配記憶體,而g()函數中的y就是分配在棧上。
init函數在同一個檔案中可以包含多個
在同一個包檔案中,可以包含有多個init函數,多個init函數的執行順序和定義順序一致。
Golang中沒有“對象”
package main
import (
"fmt"
)
type test struct {
name string
}
func (t *test) getName(){
fmt.Println("hello world")
}
func main() {
var t *test
t = nil
t.getName()
}
能正常輸出嗎?會報錯嗎?
輸出為:
hello world
可以正常輸出。Go本質上不是物件導向的語言,Go中是不存在object的含義的,Go語言書籍中的對象也和Java、PHP中的對象有區別,不是真正的”對象”,是Go中struct的實體。
調用getName方法,在Go中還可以轉換,轉換為:Type.method(t Type, arguments)
所以,以上代碼main函數中還可以寫成:
func main() {
(*test).getName(nil)
}
Go中的指標*符號的含義
&的意思大家都明白的,取地址,假如你想獲得一個變數的地址,只需在變數前加上&即可。
例如:
a := 1
b := &a
現在,我拿到a的地址了,但是我想取得a指標指向的值,該如何操作呢?用*號,*b即可。
*的意思是對指標取值。
下面對a的值加一
a := 1
b := &a
*b++
*和&可以相互抵消,同時注意,*&可以抵消,但是&*不可以;所以a和*&a是一樣的,和*&*&*&a也是一樣的。
os.Args擷取命令列指令參數,應該從數組的1座標開始
os.Args的第一個元素,os.Args[0], 是命令本身的名字
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args[0])
}
以上代碼,經過go build之後,打包成一個可執行檔main,然後運行指令./main 123
輸出:./main
數組切片slice的容量問題帶來的bug
請看下列代碼:
import (
"fmt"
)
func main(){
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(slice, 50)
newSlice[1] += 1
fmt.Println(slice)
}
請問輸出什麼?
答案是:
[10 21]
如果稍作修改,將以上newSlice改為擴容三次,newSlice := append(append(append(slice, 50), 100), 150)如下:
import (
"fmt"
)
func main(){
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(append(append(slice, 50), 100), 150)
newSlice[1] += 1
fmt.Println(slice)
}
輸出為:
[10 20]
這特麼是什麼鬼?
這就要從Golang切片的擴容說起了;切片的擴容,就是當切片添加元素時,切片容量不夠了,就會擴容,擴容的大小遵循下面的原則:(如果切片的容量小於1024個元素,那麼擴容的時候slice的cap就翻番,乘以2;一旦元素個數超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。)如果擴容之後,還沒有觸及原數組的容量,那麼,切片中的指標指向的位置,就還是原數組(這就是產生bug的原因);如果擴容之後,超過了原數組的容量,那麼,Go就會開闢一塊新的記憶體,把原來的值拷貝過來,這種情況絲毫不會影響到原數組。
建議盡量避免bug的產生。
map引用不存在的key,不報錯
請問下面的例子輸出什麼,會報錯嗎?
import (
"fmt"
)
func main(){
newMap := make(map[string]int)
fmt.Println(newMap["a"])
}
答案是:
0
不報錯。不同於PHP,Golang的map和Java的HashMap類似,Java引用不存在的會返回null,而Golang會返回初始值
map使用range遍曆順序問題,並不是錄入的順序,而是隨機順序
請看下面的例子:
import (
"fmt"
)
func main(){
newMap := make(map[int]int)
for i := 0; i < 10; i++{
newMap[i] = i
}
for key, value := range newMap{
fmt.Printf("key is %d, value is %d\n", key, value)
}
}
輸出:
key is 1, value is 1
key is 3, value is 3
key is 5, value is 5
key is 7, value is 7
key is 9, value is 9
key is 0, value is 0
key is 2, value is 2
key is 4, value is 4
key is 6, value is 6
key is 8, value is 8
是雜亂無章的順序。map的遍曆順序不固定,這種設計是有意為之的,能為能防止程式依賴特定遍曆順序。
channel作為函數參數傳遞,可以聲明為只取(<- chan)或者只發送(chan <-)
一個函數在將channel作為一個類型的參數來聲明的時候,可以將channl聲明為只可以取值(<- chan)或者只可以發送值(chan <-),不特殊說明,則既可以取值,也可以發送值。
例如:只可以發送值
func setData(ch chan <- string){
//TODO
}
如果在以上函數中存在<-ch則會編譯不通過。
如下是只可以取值:
func setData(ch <- chan string){
//TODO
}
如果以上函數中存在ch<-則在編譯期會報錯
使用channel時,注意goroutine之間的執行流程問題
package main
import (
"fmt"
)
func main(){
ch := make(chan string)
go setData(ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
func setData(ch chan string){
ch <- "test"
ch <- "hello wolrd"
ch <- "123"
ch <- "456"
ch <- "789"
}
以上代碼的執行流程是怎樣的呢?
一個基於無緩衝channel的發送或者取值操作,會導致當前goroutine阻塞,一直等待到另外的一個goroutine做相反的取值或者發送操作以後,才會正常跑。
以上例子中的流程是這樣的:
主goroutine等待接收,另外的那一個goroutine發送了“test”並等待處理;完成通訊後,列印出”test”;兩個goroutine各自繼續跑自己的。
主goroutine等待接收,另外的那一個goroutine發送了“hello world”並等待處理;完成通訊後,列印出”hello world”;兩個goroutine各自繼續跑自己的。
主goroutine等待接收,另外的那一個goroutine發送了“123”並等待處理;完成通訊後,列印出”123”;兩個goroutine各自繼續跑自己的。
主goroutine等待接收,另外的那一個goroutine發送了“456”並等待處理;完成通訊後,列印出”456”;兩個goroutine各自繼續跑自己的。
主goroutine等待接收,另外的那一個goroutine發送了“789”並等待處理;完成通訊後,列印出”789”;兩個goroutine各自繼續跑自己的。
記住:Golang的channel是用來goroutine之間通訊的,且通訊過程中會阻塞。