<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ebook學(xué)習(xí)資料

          聯(lián)合創(chuàng)作 · 2023-09-27 08:56

          Go編碼注意事項(xiàng)

          1. new和make的區(qū)別,前者返回的是指針,后者返回引用,且make關(guān)鍵字只能創(chuàng)建channel、slice和map這三個(gè)引用類型。

          2. 如果User結(jié)構(gòu)想實(shí)現(xiàn)Test方法,以下寫法:func (this *User) Test() ,User的實(shí)例和*User都可以調(diào)到Test方法,不同的是作為接口時(shí)User沒有實(shí)現(xiàn)Test方法。

          3. interface 作為兩個(gè)成員實(shí)現(xiàn),一個(gè)是類型和一個(gè)值,var x interface{} = (*interface{})(nil) 接口指針x不等于nil 。下面一段代碼深入展示:

          type User struct {
              Id   int
              Name string
              Tester
          }
          type Tester interface {
              Test()
          }
          func (this *User) Test() {
          	fmt.Println(this)
          }
          func create() Tester {
              var x *User = nil
              return x
          }
          func Test(t *testing.T) {
              var x Tester = create()
              if x != nil {
                 t.Log("not nil ")
              }
              var u *User = x.(*User)
              if u == nil {
                 t.Log("nil ")
              }
          }
          1. 繼承通過(guò)嵌入實(shí)現(xiàn),也可說(shuō)go語(yǔ)言沒有繼承語(yǔ)法。

          2. import關(guān)鍵字前面的"."和"_"的用法,點(diǎn)號(hào)表示調(diào)用這個(gè)包的函數(shù)時(shí)可以省去包名,下劃線表示,純引入包,因?yàn)間o語(yǔ)法內(nèi)沒有使用這個(gè)包是不能導(dǎo)入的,包引入了,系統(tǒng)會(huì)自動(dòng)調(diào)用包的init函數(shù)。

          3. select case必須是chan的io操作,為了避免饑餓問(wèn)題,當(dāng)多個(gè)通道都同時(shí)監(jiān)聽到數(shù)據(jù)時(shí),select機(jī)制會(huì)隨機(jī)性選擇一個(gè)通道讀取,一個(gè)通道被多個(gè)select語(yǔ)句監(jiān)聽時(shí),同理。

          4. 關(guān)閉通道時(shí)所有select 監(jiān)聽都會(huì)收到通道關(guān)閉信號(hào),某種意義上關(guān)閉通道是廣播事件。

          5. goroutine的panic如果沒有捕獲,整個(gè)應(yīng)用程序會(huì)crash ,所以安全起見每個(gè)復(fù)雜的go線都要recover。

          6. 在函數(shù)退出時(shí),defer的調(diào)用順序是寫在后面的先被調(diào)用。

          7. init函數(shù)在main之前調(diào)用,被編譯器自動(dòng)調(diào)用,每個(gè)包理論上允許有多個(gè)init函數(shù),編碼上盡量避免同一個(gè)包內(nèi)出現(xiàn)多個(gè)init函數(shù)。

          8. panic可以中斷原有的控制流程,進(jìn)入一個(gè)令人恐慌的流程中,這一過(guò)程繼續(xù)向上,直到發(fā)生panic的goroutine中所有調(diào)用的函數(shù)返回,此時(shí)程序退出。恐慌可以直接調(diào)用panic產(chǎn)生。也可以由運(yùn)行時(shí)錯(cuò)誤產(chǎn)生,例如訪問(wèn)越界的數(shù)組. recover的用法,recover可以讓進(jìn)入令人恐慌的流程中的goroutine恢復(fù)過(guò)來(lái)。recover僅在defer函數(shù)中有效。在正常的執(zhí)行過(guò)程中,調(diào)用recover會(huì)返回nil,并且沒有其它任何效果。

          9. Array 和Slice的區(qū)別,Array就是一個(gè)數(shù)據(jù)塊,值類型而非引用類型,傳參時(shí)會(huì)進(jìn)行內(nèi)存拷貝,Slice是個(gè)reflect.SliceHeader結(jié)構(gòu)體。Slice由make函數(shù)或者Array[:]創(chuàng)建。

          10. 閉包要注意循環(huán)調(diào)用時(shí),upvalue值一不留意可能只是循環(huán)退出的值。如下代碼:

          func Test(t *testing.T) {
              var data int
              for i:= 0;i<10;i++{
              data ++
                  go func(){
                      listen2(data)
                  }()
              }
              <- time.After(time.Second)
          }
          func listen2(data int) {
              fmt.Print( data)
          }

          輸出:26101010101010106,跟你期望的輸出可能不一樣。

          1. 普通類型向接口類型的轉(zhuǎn)換是隱式的,定義該接口變量直接賦值。接口類型向普通類型轉(zhuǎn)換需要類型斷言:value, ok := element.(T)。

          2. Go設(shè)計(jì)上模糊了堆跟棧的邊界,go編譯器幫程序員做了對(duì)象逃逸分析,優(yōu)化了內(nèi)存分配,t := T{}是可以在函數(shù)里返回的,并不是像C語(yǔ)言中在棧里分配內(nèi)存了。

          3. 無(wú)論以接口或接口指針傳遞參數(shù),接口指向的值都會(huì)被拷貝傳遞,引用類型(Map/Chan/Slice)拷貝該引用對(duì)象,值類型拷貝整個(gè)值(string除外)。

          4. go線程的調(diào)用時(shí)機(jī)是由go runtime決定的。

          func Test(t *testing.T) {
          	for i:= 0;i<10;i++{
          		go listen2(i)
          	}
          	<- time.After(time.Second)
          }
          func listen2(data int) {
          	fmt.Print( data)
          }

          輸出:3456781209

          1. 調(diào)用log.Fatal系列函數(shù)后,會(huì)再調(diào)用 os.Exit(1) 退出程序,Fatal is equivalent to Print() followed by a call to os.Exit(1)

          2. 如果管道關(guān)閉則退出for循環(huán),因?yàn)楣艿狸P(guān)閉不會(huì)阻塞導(dǎo)致for進(jìn)入死循環(huán),如下:

          // 錯(cuò)誤的做法
          func Test_Select_Chan(t *testing.T) {
          	readerChannel:= make(chan int )
          	go func(readerChannel chan int ) {
          		for {
          			select {
          			// 判斷管道是否關(guān)閉
          			case _, ok := <-readerChannel:
          				if !ok {
          					break
          				}
          			}
          			t.Log("for")
          		}
          	}(readerChannel)
          	close(readerChannel)
          	<- time.After(time.Second*2)
          }
          // 正確的做法
          func Test_Select_Chan1(t *testing.T) {
          	readerChannel:= make(chan int )
          	go func(readerChannel chan int ) {
          		for {
          			select {
          			// 判斷管道是否關(guān)閉
          			case _, ok := <-readerChannel:
          				if !ok {
          					goto BB
          					//return
          				}
          			}
          			t.Log("for")
          		}
          	BB:
          	}(readerChannel)
          	close(readerChannel)
          	<- time.After(time.Second*2)
          }

          for select 組合不帶標(biāo)簽的break語(yǔ)法是跳不出循環(huán),如果要跳出循環(huán),要設(shè)置goto 標(biāo)簽或者直接return返回。

          1. map,slice,array,chan的數(shù)據(jù)存取值類型數(shù)據(jù)都是值拷貝賦值,這個(gè)跟很多腳本語(yǔ)言不同,一定要注意:
          var list []mydata
          var hash map[string]mydata
          type mydata struct {
              A int
          }
          func Test(t *testing.T) {
          	list = make([]mydata, 1)
          	data := list[0]
          	data.A = 10
          	hash = make(map[string]mydata)
          	hash["test"] = mydata{}
          	data = hash["test"]
          	data.A = 10
          	t.Log(list[0].A, hash["test"].A)
          }

          輸出:0 0

          1. 外部可見的屬性必須是首字母大寫,當(dāng)轉(zhuǎn)換到j(luò)son數(shù)據(jù)是跟預(yù)期有偏差,必須添加json標(biāo)簽,如下:
          type CMD struct{
                  Cmd string `json:"cmd"`
                  Data Data `json:"data"`
                  UserId string `json:"userId"`
              }
          1. 包的循環(huán)引用編譯錯(cuò)誤,解決方法:提取公共部分到獨(dú)立的包或者定義接口依賴注入。

          2. return XXX不是一條原子指令:

          func Test(t *testing.T) {
          	t.Log(test())
          	t.Log(test1())
          }
          func test() (result int) {
          	defer func() {
          		result++
          	}()
          	return 1
          }
          
          func test1() (result int) {
          	t := 5
          	defer func() {
          		t = t + 5
          	}()
          	return t
          }

          輸出: 2 5

          return XXX 不是一條原子指令,函數(shù)返回過(guò)程是,先對(duì)返回值賦值,再調(diào)用defer函數(shù),然后返回調(diào)用函數(shù) 所以test方法的return 1可以拆分為:

          result = 1
          func()(result int){
              result ++
          }()
          return
          1. 內(nèi)置copy方法,拷貝數(shù)組時(shí),如果要整數(shù)組拷貝,目標(biāo)數(shù)組長(zhǎng)度要和源數(shù)組長(zhǎng)度相同,否則剩下的數(shù)據(jù)不會(huì)被拷貝:
          list := []int{12, 1242, 35, 23, 534, 23, 1}
          listNew := make([]int, 1, len(list))
          copy(listNew, list)
          for i := 0; i < len(listNew); i++ {
          fmt.Println(listNew[i])
          }

          輸出:12

          1. 分割切片slice時(shí),新切片引用的內(nèi)存和老切片引用的是同一塊內(nèi)存:
          list := []int{12, 1242, 35, 23, 534, 23, 1}
          listNew := list[:3]
          listNew2 := list[:5]
          listNew[1] = 999
          fmt.Println(listNew2[1])

          輸出:999

          1. 編譯時(shí)設(shè)置編譯參數(shù)去掉調(diào)試信息,可以讓生成體積更小: go build -o target.exe -ldflags "-w -s" source.go

          2. Go語(yǔ)言里,string字符串類是不可變值類型,字符串的"+"連接操作、字符串和字符數(shù)組之間的轉(zhuǎn)換string([]byte) 都會(huì)生成新的內(nèi)存存放新字符串,當(dāng)要對(duì)字符串頻繁操作時(shí)做好先轉(zhuǎn)換成字符數(shù)組。但是字符串作為參數(shù)傳參時(shí),此處go編譯器作了優(yōu)化,不會(huì)導(dǎo)致內(nèi)存拷貝,引用的是同一塊內(nèi)存。benchmark如下:

          func Benchmark_aa(b *testing.B) {
          	very_long_string:= ""
          	for i := 0; i < b.N; i++ {
          		very_long_string += "test " + "and test "
          	}
          }
          func Benchmark_bb(b *testing.B) {
          	very_long_string:= []byte{}
          	for i := 0; i < b.N; i++ {
          		very_long_string = append(very_long_string,[]byte("test ")...)
          		very_long_string = append(very_long_string,[]byte("and test ")...)
          	}
          }

          輸出: 200000 135817 ns/op 50000000 39.3 ns/op

          1. Go build/run/test 有個(gè)參數(shù) -race ,設(shè)置-race運(yùn)行時(shí)會(huì)進(jìn)行數(shù)據(jù)競(jìng)態(tài)檢測(cè),并把關(guān)鍵代碼打印輸出,不過(guò)數(shù)據(jù)競(jìng)態(tài)不能全依賴race檢測(cè),不一定能全部檢測(cè)出來(lái)。

          2. 如果非必要必要使用反射reflect和unsafe包內(nèi)的函數(shù),一定要使用時(shí),要用runtime.KeepAlive函數(shù)告知SSA編譯器在指定的代碼段內(nèi)不要回收該內(nèi)存塊。

          3. 不要打印整個(gè)map對(duì)象或者對(duì)象里有嵌套map的對(duì)象,打印函數(shù)會(huì)不加鎖遍歷map的每個(gè)元素,如果此時(shí)外部剛好有方法對(duì)map進(jìn)行寫操作,map就進(jìn)入并發(fā)讀寫,runtime會(huì)panic。

          4. 注意range 循環(huán)迭代時(shí)key 的地址,for k,v:=range list 其中k 在迭代時(shí)指向同一個(gè)地址。

          5. append追加切片用法:

          • 如果slice還有剩余的空間,可以添加這些新元素,那么append就將新的元素放在slice后面的空余空間中
          • 如果slice的空間不足以放下新增的元素,那么就需要重現(xiàn)創(chuàng)建一個(gè)數(shù)組;這時(shí)可能是alloc、也可能是realloc的方式分配這個(gè)新的數(shù)組
          • 也就是說(shuō),這個(gè)新的slice可能和之前的slice在同一個(gè)起始地址上,也可能不是一個(gè)新的地址
          • 如果容量不足觸發(fā)realloc,重新分配一個(gè)新的地址
          • 分配了新的地址之后,再把原來(lái)slice中的元素逐個(gè)拷貝到新的slice中并返回
          • 觸發(fā)realloc時(shí),容量小于1024,會(huì)擴(kuò)展到原來(lái)的1倍,如果容量小大于1024,會(huì)擴(kuò)展原來(lái)的1/4
          1. 很多打印函數(shù)打印結(jié)構(gòu)體時(shí)回調(diào)用該結(jié)構(gòu)體的String方法,所以String不能再打印本身這個(gè)對(duì)象。如下:
          type S struct {
          }
          func (this S)String()  string{
          	return fmt.Sprint( "S struct: ",this )
          }
          func Test_print(t *testing.T)  {
          	var s S
          	t.Log(s.String())
          }

          34.循環(huán)語(yǔ)句里正整型迭代值邊界問(wèn)題,迭代值邊界遞減到負(fù)值,下面的代碼會(huì)進(jìn)入死循環(huán):

          for i:= uint8(10);i>=0;i--{
          	t.Log(i)
          }
          1. recover只處理本goroutine調(diào)用棧,goroutine的panic如果沒有捕獲,整個(gè)應(yīng)用程序會(huì)crash ,所以安全起見每個(gè)復(fù)雜的go線都要recover。

          2. map 并發(fā)讀寫的錯(cuò)誤無(wú)法用panic捕獲。

          3. 對(duì)于小對(duì)象,直接將值類型的對(duì)象交由 map 保存,遠(yuǎn)比用該對(duì)象指針高效。這不但減少了堆內(nèi)存分配,關(guān)鍵還在于垃圾回收器不會(huì)掃描非指針類型 key/value 對(duì)象。

          4. Go 使用 channel 實(shí)現(xiàn) CSP 模型。處理雙方僅關(guān)注通道和數(shù)據(jù)本身,無(wú)需理會(huì)對(duì)方身份和數(shù)量,以此實(shí)現(xiàn)結(jié)構(gòu)性解耦。在各文宣中都有 “Don't communicate by sharing memory, share memory by communicating.” 這類說(shuō)法。但這并非鼓勵(lì)我們不分場(chǎng)合,教條地使用 channel。在我看來(lái),channel 多數(shù)時(shí)候適用于結(jié)構(gòu)層面,而非單個(gè)區(qū)域的數(shù)據(jù)處理。原話中 “communicate” 本就表明一種 “message-passing”,而非 “l(fā)ock-free”。所以,它并非用來(lái)取代 mutex,各自有不同的使用場(chǎng)景。

          5. 變量逃逸和函數(shù)內(nèi)聯(lián)狀態(tài)分析 go build -gcflags "-m" -o main.exe main.go

          6. go匯編指令 go build -gcflags "-N -l" -o main.exe main.go && go tool objdump -s "main\.main" main.exe 關(guān)閉內(nèi)聯(lián)優(yōu)化:go build -gcflags "-N -l"

          7. 關(guān)于defer機(jī)制,編譯器通過(guò) runtime.deferproc “注冊(cè)” 延遲調(diào)用,除目標(biāo)函數(shù)地址外,還會(huì)復(fù)制相關(guān)參數(shù)(包括 receiver)。在函數(shù)返回前,執(zhí)行 runtime.deferreturn 提取相關(guān)信息執(zhí)行延遲調(diào)用。這其中的代價(jià)自然不是普通函數(shù)調(diào)用一條 CALL 指令所能比擬的,單個(gè)函數(shù)里過(guò)多的 defer 調(diào)用可嘗試合并。最起碼,在并發(fā)競(jìng)爭(zhēng)激烈時(shí),mutex.Unlock 不應(yīng)該使用 defer,而應(yīng)盡快執(zhí)行,僅保護(hù)最短的代碼片段。

          8. 對(duì)map預(yù)設(shè)容量,map會(huì)按需擴(kuò)張,但須付出數(shù)據(jù)拷貝和重新哈希成本。如有可能,應(yīng)盡可能預(yù)設(shè)足夠容量空間,避免此類行為發(fā)生。

          9. go語(yǔ)言調(diào)用c語(yǔ)言:

          • import "C" 這句代碼必須緊跟偽注釋的C語(yǔ)言代碼后面不能有換行
          • go語(yǔ)言的CGO會(huì)自動(dòng)鏈接編譯.c文件,但是必須用go build 編譯指令,不能指定main.go文件
          1. Go語(yǔ)言命名規(guī)范:
          • 文件命名,全小寫+下劃線
          • 結(jié)構(gòu)體名字、變量名字采用駝峰命名(根據(jù)包外可見性確定首字母是否大寫)
          • 常量命名,全大寫 +下劃線
          1. 刪除切片的指定下標(biāo)元素
          • 低效的做法
          		for j := 0; j < len(array); j++ {
          			if INDEX == j {
          				array = append(array[:j], array[j+1:]...)
          				break
          			}
          		}
          • 高效的做法
          		
          			for j :=INDEX; j < len(array)-1; j++ {
          				array[j] = array[j+1]
          			}
          			array = array[:len(array)-1]
          	
          瀏覽 32
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品+国产+内射 | 国产伦精品一区二区三区妓女 | 骚虎官网| 手机无码在线观看 | 在线观看肏屄视频 |