主頁 > 後端開發 > Golang開發必須了解的細節!

Golang開發必須了解的細節!

2021-04-01 16:50:57 後端開發

GO核心編程

簡介

go語言特點:

  • go具有垃圾回識訓制
  • 從語言層面支持并發,goroutine,高效利用多核,基于CPS并發模型實作(重要特點)
  • 吸收了管道通信機制,實作不同goroutine之間的互相通信
  • 函式可以回傳多個值
  • 切片、延時執行defer
  • 繼承C語言很多思想,引入包的概念,用于組織程式結構

golang執行流程分析

第一種方式是go build編譯后生成可執行檔案,在運行可執行檔案即可;第二種方式是直接go run源檔案,兩種方式的區別:

  • 如果我們先編譯生成了可執行檔案,那么我們可以將該可執行檔案拷貝到沒有 go 開發環境的機器上,仍然可以運行
  • 如果我們是直接 go run go 源代碼,那么如果要在另外一個機器上這么運行,也需要 go 開發環境,否則無法執行
  • 在編譯時,編譯器會將程式運行依賴的庫檔案包含在可執行檔案中,所以,可執行檔案變大了很多

真正作業時候需要先編譯在運行!!

go程式開發注意事項

  • Go每個陳述句后不需要分號(Go語言會在每行后自動加分號)
  • Go編譯器是一行行進行編譯的,一行只能寫一條陳述句
  • 存在未使用的包或變數,編譯會通不過

規范代碼風格

撰寫完代碼后可以通過gofmt -w main.go來進行格式化;Go設計者的思想:一個問題盡量只有一個解決方法,

基本語法

變數使用注意事項

  • 如果一次宣告多個全域變數

    var(
    	n3 = 300
    	name = "mary"
    ) 
    //區域變數 var n3, name = 300, "mary"
    
  • //查看變數型別和占用位元組
    fmt.Printf("n1 的 型別 %T\n n1占用的位元組數 %d",n1,unsafe.Sizeof(n1))
    
  • /*
    byte~uint8 存盤字符時候選用byte
    如果保存字符對應碼值大于255,比如漢字,可以考慮使用int型別保存
    如果需要輸出字符,需要格式化輸出
    */
    
    //rune~int32 表示一個Unicode碼
    
  • /* 
    Go中字串是不可變的
    字串兩種表現形式:
    雙引號:會識別轉義字符
    反引號:以字串的原生形式輸出,包括換行和特殊字符,可以實作防止攻擊、輸出源代碼等效果
    */
    
  • Go資料型別不能自動轉換,需要顯示轉換T(v)

    //基本資料型別和string的相互轉換
    //Sprintf會根據format引數生成格式化的字串并回傳該字串
    
  • go語言不支持三元運算子

流程控制使用注意事項

  • Switch...case陳述句中,case后面不再需要添加break,case后面也可以有多個值,用逗號分隔開,如果想要執行下面的陳述句,添加fallthrough關鍵字,叫做switch穿透
  • 回圈遍歷只有一個for關鍵字,可以用for range陳述句來遍歷陣列,

包使用注意事項

  • 一個檔案夾下的所有.go檔案同屬于一個包,一般和檔案夾一樣,在同一個包下不能有相同的函式名和變數名,即使在不同檔案中也一樣,
  • 跨包訪問的函式或變數首字母需要大寫,相當于public,
  • import實際上是import "檔案夾名字",訪問時候是用的包名.函式名,因為包名可以和檔案夾名不一樣
  • 如果要編譯成一個可執行程式檔案,就需要將這個包宣告為main;如果是寫一個庫,包名可以自定義

函式使用注意事項

  • 基本資料型別和陣列默認都是值傳遞

  • Go中,函式也是一種資料型別,可以賦給一個變數,類似于C語言的函式指標,型別為func(type1,type2)

  • C++中typedef,在Go中變為type

  • 支持對函式回傳值命名

    func getSumAndSub(n1 int,n2 int)(sum int,sub int){
    	sum = n1 + n2
    	sub = n1 - n2
    	return
    }
    
  • 支持可變引數

    func sum(args... int) sum int{
    }
    func sum(n1 int,args... int) sum int{
    }
    //args是slice切片,通過args[index]可以訪問到各個值,可變引數要放在形參串列最后
    
  • 每一個源檔案都可以包含一個 init 函式,該函式會在 main 函式執行前,被 Go 運行框架呼叫,也 就是說 init 會在 main 函式前被呼叫,如果一個檔案同時包含全域變數定義,init 函式和 main 函式,則執行的流程全域變數定義->init函式->main 函式,如果import其他檔案,則先執行其他檔案的初始化!!!

  • 匿名函式

    //方式一
    res1:= func(n1 int) int{
    	return n1+1
    }(10) 
    //方式二
    fun:=func(n1 int) int{
      return n1+1
    }
    res2:=fun(10)
    
  • 閉包

    閉包就是一個函式和與其相關的參考環境組合的一個整體.可以這樣理解: 閉包是類, 函式是操作,n 是欄位,函式和它使用到 n 構成閉包,

    要搞清楚閉包的關鍵,就是要分析出回傳的函式它使用(參考)到哪些變數,因為函式和它引

    用到的變數共同構成閉包

    func makeSuffix(suffix string) func(string) string{
      return func(name string) string{
        //如果name沒有指定后綴,則加上,否則就回傳原來的名字
        if !strings.HasSuffix(name,suffix){
          return name+suffix
        }
      }
    }
    f2 := makeSuffix(".jpg")
    fmt.Println(f2("winter")) //winter.jpg
    fmt.Println(f2("bird.jpg")) //bird.jpg
    

    我們體會一下閉包的好處,如果使用傳統的方法,也可以輕松實作這個功能,但是傳統方法需要每 次都傳入 后綴名,比如 .jpg ,而閉包因為可以保留上次參考的某個值,所以我們傳入一次就可以反復 使用,這個makeSuffix用處有點類似于java的泛型和c++的模版類,生成特定后綴判斷的函式變數

  • defer

    當執行到defer時,暫停不執行,會將defer后面的陳述句壓入到獨立的堆疊(defer堆疊),當函式執行完畢后,再從defer堆疊中取出陳述句執行,在defer陳述句放入到堆疊時,也會將相關的值拷貝同時入堆疊

    func sum(n1 int,n2 int) int{
      defer fmt.Println("ok1 n1=",n1)
      defer fmt.Println("ok2 n2=",n2)
      n1++
      n2++
      res:=n1+n2
      fmt.Println("ok3 res=",res)
      return res
    }
    //執行結果
    //ok3 res=32
    //ok2 n2=20
    //ok1 n1=10
    

    defer 最主要的價值是在,當函式執行完畢后,可以及時的釋放函式創建的資源

  • 函式傳參

    值型別:基本資料型別、陣列和結構體 struct,默認是值傳遞

    參考型別:指標、slice 切片、map、管道 chan、interface 等,默認是參考傳遞

  • 錯誤處理

    Go語言不支持傳統的try...catch...finally處理,引入的處理方式為defer,panic,recover,

    這幾個例外的使用場景可以這么簡單描述:Go 中可以拋出一個 panic 的例外,然后在 defer 中通過 recover 捕獲這個例外,然后正常處理,

    func test(){
    	defer func(){
        err := recover() //recover()內置函式,可以捕獲到例外
        if err != nil{
          fmt.Println("err",err)
        }
      }()
      num1 := 10
      num2 := 0
      res := num1/num2
      fmt.Println("res=",res)
    }
    

    自定義錯誤:

    1.errors.New("錯誤說明") , 會回傳一個 error 型別的值,表示一個錯誤

    2.panic 內置函式 ,接收一個 interface{}型別的值(也就是任何值了,相當于java的Object)作為引數,可以接收 error 型別的變數,輸出錯誤資訊,并退出程式.

陣列和切片

go語言中陣列的名字不在是地址了,陣列的首地址為&arr或者&arr[0],

var arr1 = [3]int{5,6,7} //var slice = []int{1,2,3} 雖然可以這樣,但已經不是一個陣列了,陣列宣告必須指定長度
var arr2 = [...]int{1,3,3}
var arr3 = [...]int{1:800,0:900,2:999}
//for range遍歷方式
for index,value range arr1{
}

陣列使用注意事項

  • func test(arr [3]int){ //值傳遞,不影響原來的
    }
    func test(arr *[3]int){//可以通過傳指標
    }
    //Go語言傳參有嚴格的限制,[3]int型別和[4]int型別不一致!!!
    
  • 二維陣列定義后面的賦值必須嚴格的劃分開,不能省略花括號!!

    arr := [2][2]int{{1,2},{3,4}}
    arr := [...][2]int{{1,2},{3,4}}
    //二維陣列for-range遍歷
    for i,v:= range arr{
      for j,v2:=range v{
      }
    }
    

切片是陣列的一個參考,因此切片是參考型別,是一個可以動態變化的陣列,

slice := ar[1:3] //左開右閉
slice := make([]int,len,[cap])
slice := []int{1,2,3}

方式一和方式二的區別

通過 make 方式創建的切片對應的陣列是由 make 底層維護,對外不可見,即只能通過 slice 去訪問各個元素,方式一直接飲用陣列,這個陣列事先存在,程式員可見,

切片使用注意事項

  • 切片可以繼續切片,因為切片的更改會影響底層陣列的更改,
  • append內置函式可以對切片追加具體元素,也可以追加slice,追加的具體元素如果不超過底層陣列的長度,則會覆寫底層陣列的數值;當超過底層陣列的長度時候,go會創建一個新的陣列,將slice原來包含的元素拷貝到新的陣列然后重新參考newArr,
  • 內置函式copy(dest,source)的引數型別是切片,source長度可以閉dest大

string和slice

  • string底層是一個byte陣列,因此string也可以進行切片

  • string是不可變的,str[0]='z'編譯不通過

    //如果想要改變,可以現將string轉成byte切片,修改完后在轉為string
    arr1 := []byte(str)
    arr1[0] = 'z'
    str = string(arr1)
    //這種轉換僅僅適用于string <---> byte,可以把byte當成char型別
    

    注意:當我們轉成[]byte后,可以處理英文和數字,不能處理中文,因為一個漢字3個位元組,會出現亂碼,解決辦法是將string轉成[]rune即可,因為[]rune是按字符處理的,兼容漢字,

map

宣告一個map是不會分配記憶體的,初始化需要make,分配記憶體后才能賦值和適用,

m := make(map[string]string,10) //容量達到后,會自動擴容
m := make(map[string]string)

新增操作:Map["key"]=value 如果key還沒有就是增加,如果key存在就是修改,

洗掉操作:delete(map,"key"),如果一次性洗掉所有的則需要一個個遍歷key去delete

查找操作:v,ok :=map["tom"]

Slice of map

m := make([]map[string]string,2) //型別為map[string]string的切片,大小為2,第三個map就需要先append在使用了,否則會越界
//切片的資料型別如果是 map,則我們稱為 slice of map,map 切片,這樣使用則 map 個數就可以動態變化了

注意:使用slice和map一定要先make

map中的key是無序的,每次遍歷得到的結果可能不一樣,Go沒有辦法對map進行排序,但是有辦法根據key來順序輸出map,

/*
1. 先將map的key放到切片中
2. 對切片排序
3. 遍歷切片,然后按照key來輸出map值
*/

面向物件編程

結構體

type Person struct{
}
p := Person{"mary",20}
var person *Person = new(Person)
(*person).Name = "smith" //person.Name = "smith"
//go設計者為了程式員使用方便,底層會對person.Name進行處理,加上(*person).Name

結構體使用注意細節:

  • 不同結構體可以相互轉換,前提是需要有完全相同的欄位(名字、個數和型別)
  • struct 的每個欄位上,可以寫上一個 tag, 該 tag 可以通過反射機制獲取,常見的使用場景就是序列化和反序列化,

方法

func (p Person) test(){
	//...
}

方法使用注意事項

  • Golang 中的方法是作用在指定的資料型別上的(即:和指定的資料型別系結),因此自定義型別,都可以有方法,而不僅僅是 struct,int,floate32等都可以有方法
  • 變數呼叫方法時,該變數本身也會作為一個引數傳遞到方法(如果變數是值型別,則進行值拷貝,如果變數是參考型別,則進行地址拷貝
  • 方法的訪問范圍控制的規則,和函式一樣,方法名首字母小寫,只能在本包訪問,方法首字母 大寫,可以在本包和其它包訪問
  • 如果一個型別實作了 String()這個方法,那么 fmt.Println 默認會呼叫這個變數的 String()進行輸 出

工廠模式

問題來了,如果首字母是小寫的, 比如 是 type student struct {....} 就不不行了,怎么辦---> 工廠模式來解決.

type student struct{
  Name string
  score float64
}

func NewStudent(n string,s float64) *student{
  return &student{
    Name:n,
    Score:s,
  }
}

//首字母小寫的欄位也不能跨包訪問,需要提供一個方法
func (s *student) GetScore() float64{
  return s.score
}

封裝

在 Golang 開發中并沒有特別強調封裝,這點并不像 Java

  • 將結構體、欄位(屬性)的首字母小寫(不能匯出了,其它包不能使用,類似 private)
  • 給結構體所在包提供一個工廠模式的函式,首字母大寫,類似一個建構式
  • 提供一個首字母大寫的 Set /Get方法(類似其它語言的 public)

繼承

在 Golang 中,如果一個 struct 嵌套了另一個匿名結構體,那么這個結構體可以直接訪問匿名結構體的欄位和方法,從而實作了繼承特性,也即匿名結構體的所有東西成為了新的結構體的一部分,

  • 結構體可以使用匿名結構體的所有欄位和方法,大小寫都可以,但是要在同一個包里面去訪問,、
  • 匿名結構體欄位訪問可以簡化,比如b.A.age=19可以寫b.age=19,
  • 當結構體和匿名結構體有相同的欄位或者方法時,編譯器采用就近訪問原則訪問,如希望訪問匿名結構體的欄位和方法,可以通過匿名結構體名來區分
  • 結構體嵌入兩個(或多個)匿名結構體,如兩個匿名結構體有相同的欄位和方法(同時結構體本身 沒有同名的欄位和方法),在訪問時,就必須明確指定匿名結構體名字,否則編譯報錯
  • 如果一個 struct 嵌套了一個有名結構體,這種模式就是組合,如果是組合關系,那么在訪問組合的結構體的欄位或方法時,必須帶上結構體的名字
  • 如一個 struct 嵌套了多個匿名結構體,那么該結構體可以直接訪問嵌套的匿名結構體的欄位和方法,從而實作了多重繼承,盡量不要使用多重繼承

介面

Go采用介面來實作多型,interface 型別可以定義一組方法,但是這些不需要實作,并且 interface 不能包含任何變數,只要一個變數,含有介面型別中的所有方法(注意:一定要是所有),那么這個變數就實作這個介面,

介面使用注意事項

  • 空介面 interface{} 沒有任何方法,所以所有型別都實作了空介面, 即我們可以把任何一個變數賦給空介面

  • 只要是自定義資料型別,就可以實作介面

    type integer int
    func (i integer) say{
    	//...
    }
    

型別斷言

介面要轉成具體型別就要用到型別斷言

var x interface{}
var b2 float32 = 1.1
x = b2
y := x.(float32) //arg.(type)

在進行型別斷言時,如果型別不匹配,就會報 panic, 因此進行型別斷言時,要確保原來的空介面指向的就是斷言的型別

如何在進行斷言時,帶上檢測機制,如果成功就 ok,否則也不要報 panic

if y,ok := x.(float32);ok{
	//convert success
}else{
  //convert fail
}

高級教程

命令列引數

os.Args 是一個 string 的切片,用來存盤所有的命令列引數

for i,v:= range os.Args{
  fmt.Printf("args[%v]=%v\n",i,v)
}//有效引數從Args[1]開始,即第二個

flag包決議命令列引數

前面的方式是比較原生的方式,對決議引數不是特別的方便,特別是帶有指定引數形式的命令列,go 設計者給我們提供了 flag 包,可以方便的決議命令列引數,而且引數順序可以隨意,

	//定義幾個變數,用于接受命令列引數
	var user string
	var pwd int
	flag.StringVar(&user,"u","","用戶名,默認為空")
	flag.IntVar(&pwd,"pwd",0,"密碼,默認為空")
	flag.Parse()
	fmt.Printf("user=%v pwd=%v\n",user,pwd)

序列化和反序列化

json.Marshal(v interface{}) ([]byte,error) //序列化
type monster struct{
}
json.unMarshal([]byte(str),&monster) //序列化

對于結構體的序列化,如果我們希望序列化后的 key 的名字,又我們自己重新制定,那么可以給 struct指定一個 tag 標簽,

在反序列化一個json字串時,要確保反序列化后的資料型別和原來序列化前的資料型別一致

*單元測驗

Go 語言中自帶有一個輕量級的測驗框架 testing 和自帶的 go test 命令來實作單元測驗和性能測驗.testing 框架和其他語言中的測驗框架類似,可以基于這個框架寫針對相應函式的測驗用例,也可以基于該框架寫相應的壓力測驗用例,

  • 測驗用例檔案名必須以 _test.go 結尾, 比如 cal_test.go , cal 不是固定的
  • 測驗用例函式必須以 Test 開頭,一般來說就是 Test+被測驗的函式名,比如 TestAddUpper
  • TestAddUpper(t *tesing.T) 的形參型別必須是 *testing.
  • 當出現錯誤時,可以使用 t.Fatalf 來格式化輸出錯誤資訊,并退出程式,t.Logf 方法可以輸出相應的日志

goroutine

Go 主執行緒(有程式員直接稱為執行緒/也可以理解成行程): 一個 Go 執行緒上,可以起多個協程,你可以這樣理解,協程是輕量級的執行緒[編譯器做優化],(這里只是叫法發生了變化)

Go可以輕輕松松的起上萬個協程,

channel

這個解決的是不同的goroutine如何通信的問題,

全域變數的互斥鎖

lock sync.Mutex
lock.lock
//...
lock.unlock

上面這種方法不完美,主執行緒在等待所有 goroutine 全部完成的時間很難確定;

如果主執行緒休眠時間長了,會加長等待時間,如果等待時間短了,可能還有 goroutine 處于作業狀態,這時也會隨主執行緒的退出而銷毀;

通過全域變數加鎖同步來實作通訊,也并不利用多個協程對全域變數的讀寫操作

在運行某個程式時,如何知道是否存在資源競爭問題, 方法很簡單,在編譯該程式時,增加一個引數 -race 即可

  • channel本質就是一個資料結構-佇列,它是有型別的,是執行緒安全的(多個協程操作同一個管道時,不會發生資源競爭問題)

  • channel必須初始化才能寫入資料,即make后才能使用

    var intChan chan int
    intChan = make(chan int,3)
    
  • 當我們給管寫入資料時,不能超過其容量,它的價值是一邊放一邊取

  • allChan := make(chan interface{},3)
    allChan <- Cat{Name:"tom",Age:18}
    newCat <- allChan
    fmt.Printf("%T\n%v",newCat,newCat) //正常輸出
    fmt.Printf("newCat.Name=%v",newCat.Name) //編譯不通過!!!
    a := newCat.(Cat) //使用型別斷言!!!!
    
  • 使用內置函式 close 可以關閉 channel, 當 channel 關閉后,就不能再向 channel 寫資料了,但是仍然 可以從該 channel 讀取資料,只有關閉后讀完會自動退出

  • 在沒有使用協程的情況下,如果 channel 資料取完了,再取就會報 dead lock ,寫也是一樣,使用協程則會阻塞,

應用實體1

一個讀協程,一個寫協程,操作同一管道,主執行緒需要等待兩個協程都完成作業才能退出,

func writeData(intChan chan int){
	for i:=1;i<=50;i++ {
		//放入資料
		intChan <- i
		fmt.Println("write data",i)
	}
	close(intChan)
}

func readData(intChan chan int,exitChan chan bool){
	for{
		v,ok := <-intChan
		if !ok {
			break
		}
		fmt.Println("read data=https://www.cnblogs.com/yrxing/p/%v/n",v)
	}
	//任務完成
	exitChan<-true
	close(exitChan)
}
func main()  {
	//創建兩個管道
	intChan := make(chan int,10)
	exitChan := make(chan bool,1)
	
	go writeData(intChan)
	go readData(intChan,exitChan)
	
	for{
		_,ok := <- exitChan
		if !ok {
			break
		}
	}
}

管道的阻塞機制

如果只是向管道寫入資料,而沒有讀取資料,就會出現阻塞而dead lock,原因是intChan容量是10,而代碼wirteData會寫入50個資料,因此會阻塞在writeData的ch<-i,但是寫管道和讀管道的頻率不一致,無所謂

應用實體2

統計1-8000的數字中,哪些是素數?將統計素數的任務分配給4個goroutine去完成

//向intChan放入1-8000個數
func putNum(intChan chan int){
	for i:=1;i<=8000;i++ {
		intChan <- i
	}
	close(intChan)
}
//從intChan取出資料,并判斷是否為素數,如果是,就放入primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool)  {
	var flag bool
	for  {
		time.Sleep(time.Microsecond*10)
		//取一個數處理
		num,ok := <-intChan
		if !ok{
			break
		}
		flag = true
		//判斷
		for i:=2;i<num;i++{
			if num%2 ==0 {
				flag=false
				break
			}
		}
		//放入
		if flag {
			primeChan <- num
		}
	}
	fmt.Println("有一個primeNum協程因為取不到資料,退出")
	//這里不能關閉primeChan
	exitChan <- true
}
func main(){
	intChan := make(chan int, 1000)
	primeChan := make(chan int,2000)
	exitChan := make(chan bool,4) //4個
	go putNum(intChan)
	//開啟4個協程,從intChan取出資料判斷
	for i:=0;i<4;i++{
		go primeNum(intChan,primeChan,exitChan)
	}
	go func() {
		for i:=0;i<4;i++{
			<-exitChan
		}
		//當我們從exitChan取出4個結果,就可以放心關閉primeChan
		close(primeChan)
	}()

	for {
		res,ok := <-primeChan
		if !ok{
			break
		}
		fmt.Println("%d\n",res)
	}
}

channel使用注意事項

  • channel可以宣告為只讀或者只寫,可以有效防止誤操作,降低權限,

    /*
    var writeChan chan<-int //只寫
    var readChan <-chan int //只讀
    */
    
  • 傳統方法在遍歷管道時,如果不關閉后阻塞而導致deadlock,在實際開發中,可能我們不好確定什么時候關閉管道,使用select可以解決從管道取資料的阻塞問題,

    for{
      //select里面的case是并發執行的
      select{
        //這里如果intChan一直沒有關閉,不會一直阻塞而deadlock,沒有資料的話會自動到下一個case匹配
      case v:= <-intChan
        fmt.Printf("從intChan讀取的資料%d\n",v)
      case v:= <-stringChan
        fmt.Printf("從stringChan讀取的資料%d\n",v)
      default:
        fmt.Printf("都取不到,程式員可以加入邏輯\n")
        time.Sleep(time.Second)
        return
      }
    }
    
  • goroutine中使用recover,解決協程中出現panic,導致程式崩潰問題,

反射

反射可以在運行時動態獲取變數的各種資訊, 比如變數的型別(type),類別(kind),如果是結構體變數,還可以獲取到結構體本身的資訊(包括結構體的欄位、方法),通過反射,可以修改變數的值,可以呼叫關聯的方法,

反射常見的應用場景

  • 不知道介面呼叫哪個函式,根據傳入引數在運行時確定呼叫的具體介面,這種需要對函式或方法反射,
  • 對結構體序列化時,如果結構體有指定tag,也會使用反射生成對應的字串

概念

  • reflect.TypeOf()/reflect.ValueOf()

  • 變數、interface{}、reflect.Value是可以相互轉換的

    func reflectTest(b interface{})  {
    	//通過反射獲取傳入變數的type,kind,值
    	rType := reflect.TypeOf(b)
    	fmt.Println("rType=",rType)
    
    	rVal := reflect.ValueOf(b)
    	n:= 2+rVal.Int()
    	fmt.Println("n=",n)
    	fmt.Printf("rVal=%v rVal type=%T\n",rVal,rVal)
    
    	iV:=rVal.Interface()
    	n2:=iV.(int)
    	fmt.Println("n2=",n2)
    }
    

反射使用注意細節

  • Reflect.Vlaue.Kind獲取變數的類別,回傳一個常量,type和kind有時候一樣有時候不一樣,stu Type是Student,Kind是struct
  • 通過反射的來修改變數, 注意當使用 SetXxx 方法來設定需要通過對應的指標型別來完成, 這樣才能改變傳入的變數的值, 同時需要使用到 reflect.Value.Elem()方法(相當于獲取指標指向變數的值)

反射最佳實踐

使用反射遍歷結構體的欄位,呼叫結構體的方法,并獲取結構體標簽的值

type Monster struct{
	Name string `json:"name"`
	Age int `json:"monster_age"`
	Score float32 `json:"成績"`
	Sex string
}

func (s Monster) GetSum(n1,n2 int) int  {
	return n1+n2
}

func (s Monster) Set(name string,age int,score float32,sex string){
	s.Name=name
	s.Age=age
	s.Score=score
	s.Sex=sex
}

func (s Monster) Print(){
	fmt.Println("----start---")
	fmt.Println(s)
	fmt.Println("-----end-----")
}

func TestStruct(a interface{}){
	typ := reflect.TypeOf(a)
	rval := reflect.ValueOf(a)
	kd := rval.Kind()
	if kd != reflect.Struct{ //如果不是struct就退出
		fmt.Println("expect struct")
		return
	}
	//獲取結構體有幾個欄位
	num := rval.NumField()
	fmt.Printf("structs has%d fileds\n",num)
	for i:=0;i<num;i++{
		fmt.Printf("Filed %d值為%v\n",i,rval.Field(i))
		tagVal := typ.Field(i).Tag.Get("json")
		//如果該欄位有tag就顯示,否則就不顯示
		if tagVal !=""{
			fmt.Printf("File%d:tag為%v",i,tagVal)
		}
	}
	//獲取結構體有多少個方法
	numOfMethod:=rval.NumMethod()
	fmt.Printf("struct has %d methods\n",numOfMethod)
	//方法的排序默認是按照函式名排序
	rval.Method(1).Call(nil)//獲取到第二個方法即Print,呼叫它,因此沒有引數
	//呼叫結構體的第一個方法Method(0)
	var params []reflect.Value
	params = append(params,reflect.ValueOf(10))
	params = append(params,reflect.ValueOf(40))
	
	res:=rval.Method(0).Call(params)//傳入引數是[]reflect.Value
	fmt.Println("res=",res[0].Int())//回傳結果是[]reflect.Value

}

TCP編程

埠分類:0保留埠;1-1024固定埠;1025-65525動態埠,程式員可以使用,一個埠只能被一個程式監聽,服務器要盡可能少用埠,

服務端代碼

func process(conn net.Conn){
	defer conn.Close()

	for{
		buf:=make([]byte,1024)
		//等待客戶端conn發送資訊,如果客戶端沒有發送,那么協程就阻塞在這里
		fmt.Printf("服務器在等待客戶端%s 發送資訊\n",conn.RemoteAddr().String())
		n,err := conn.Read(buf)
		if err != nil{
			fmt.Printf("客戶端退出 err=%v",err)
			return //!!!
		}
		//顯示客戶端發送的內容到服務器的終端
		fmt.Print(string(buf[:n]))
	}

}
func main()  {
	fmt.Println("服務器開始監聽...")
	listen,err:= net.Listen("tcp","0.0.0.0:8888")
	if err!= nil{
		fmt.Println("listen err=",err)
		return
	}
	defer listen.Close() //延時關閉listen
	//回圈等待客戶端來連接我
	for{
		fmt.Println("等待客戶端來連接...")
		conn,err:=listen.Accept()
		if err != nil{
			fmt.Println("Accept() err=",err)
		}else{
			fmt.Printf("Accept() success con=%v 客戶端ip=%v\n",conn,conn.RemoteAddr().String())
		}
		//這里準備一個協程為客戶端服務
		go process(conn)
	}
}

客戶端代碼

func main(){
	conn,err := net.Dial("tcp","0.0.0.0:8888")
	if err != nil{
		fmt.Println("client dial err=",err)
		return
	}

	//客戶端可以發送單行資料,然后就退出
	reader := bufio.NewReader(os.Stdin)
	for {
		//從終端讀一行用戶輸入,并準備發送給服務器
		line, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("readString err=", err)
		}
		//如果用戶輸入的是exit就退出
		line = strings.Trim(line, " \r\n")
		if line == "exit" {
			fmt.Println("客戶端退出..")
			break
		}
		//再將line發送給服務器
		_, err = conn.Write([]byte(line + "\n"))
		if err != nil {
			fmt.Println("conn Write err=", err)
		}
		//fmt.Printf("客戶端發送了%d位元組的資料,并退出",n)
	}
}

Redis的使用

REmote Dictionary Server(遠程字典服務器),Redis性能非常高,單機能夠達到15w qps,通常適合做快取,也可以持久化,是完全開源的,高性能的k-v分布式記憶體資料庫,基于記憶體運行并支持之久化的NoSQL資料庫,

Redis安裝好后,默認有16個資料庫,初始默認使用0號庫,編號0...15,select 1`切換1號資料庫,

golang操作redis

  • 安裝第三方開源redis庫

    cd $GOPATH
    go get github.com/garyburd/redigo/redis
    
  • Set/Get介面

    func main()  {
    	//連接到redis
    	conn,err := redis.Dial("tcp","127.0.0.1:6379")
    	if err!= nil{
    		fmt.Println("redis.Dial err=",err)
    		return
    	}
    	defer conn.Close()
    	//通過go向redis寫入資料string[key-val]
    	_,err = conn.Do("Set","name","tomjerry_cat")
    	if err!= nil{
    		fmt.Println("set err=",err)
    		return
    	}
    	//通過go向redis讀取資料
    	r,err:=redis.String(conn.Do("Get","name"))
    	if err!= nil{
    		fmt.Println("get err=",err)
    		return
    	}
    	//因為回傳r是interface{},name對應的值是string,因此我們需要轉換
    	//nameString := r.(string)
    	fmt.Println("操作ok",r)
    }
    
  • redis鏈接池

    事先初始化一定數量的鏈接,放入到鏈接池,當 Go 需要操作 Redis 時,直接從 Redis 鏈接池取出鏈接即可,這樣可以節省臨時獲取 Redis 鏈接的時間,從而提高效率,

    //定義一個全域的pool
    var pool *redis.Pool
    
    //當啟動程式時,就初始化鏈接池
    func init()  {
    	pool = &redis.Pool{
    		MaxIdle: 8,//最大空閑鏈接數
    		MaxActive: 0,//表示和資料庫的最大鏈接數,0表示沒有限制
    		IdleTimeout: 100,//最大空閑時間
    		Dial: func() (redis.Conn, error) {//初始化鏈接代碼
    			return redis.Dial("tcp","localhost:6379")
    		},
    	}
    }
    func main()  {
    	//先從pool取出一個鏈接
    	conn:=pool.Get()
    	defer conn.Close()
    	_,err:=conn.Do("Set","name","Tom cat!!")
    	if err!=nil{
    		fmt.Println("conn.Do err=",err)
    		return
    	}
    	
    	//...
    }
    

經典專案-海量用戶即時通訊系統

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/270590.html

標籤:Go

上一篇:[go-linq]-Go的.NET LINQ式查詢方法

下一篇:RabbitMQ 入門 (Go) - 3. 模擬傳感器,生成資料并發布

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more