golang學習筆記(一)——golang基礎和相關數據結構


小白前端一枚,最近在研究golang,記錄本身學習過程當中的一些筆記,以及本身的理解。前端

  • go中包的依賴管理
  • go中的切片
  • byte 和 string
  • go中的Map
  • go中的struct結構體
  • go中的方法
  • go中的interface接口
  • interface{}

原文在個人博客中:github.com/fortheallli…git

歡迎star~github

一、go中包的依賴管理

首先要了解的是GOPATH的含義,GOPATH是go命令依賴的重要變量,能夠經過:golang

go env
複製代碼

來查看相應的開發環境中GOPATH的值,也能夠經過export GOPATH指定:數組

export GOPATH = /usr/local/go
複製代碼

指定GOPATH目錄後, GOPATH目錄包含了3個子目錄:安全

  • src存放源代碼(好比.go、.h文件等)
  • pkg編譯時生成的中間文件
  • bin編譯後生成的可執行文件

此外,go的依賴管理中提供了3個主要的命令go build、go get和 go install。函數

  • go build: 會編譯目錄下或者指定的.go文件,獲得一個可執行的文件
  • go install: 只能在GOPATH目錄下使用,與go build大體相同會編譯目錄下執行的.go文件,此外go install還會將可執行文件或庫文件安裝到GOPATH目錄下。
  • go install + 遠程地址: 會將相應的代碼下載到GOPATH同時會編譯該遠程包
  • go get + 遠程地址: 跟go install+遠程地址相同,會下載且編譯
  • go get u + 遠程地址: 下載並更新相應的遠程地址的包,但不會自動編譯

典型的例子,好比下載一個dep包:學習

go get -u github.com/golang/dep/cmd/dep
複製代碼

上述的go get和go install + 遠程包的方式,不能應用於須要版本管理依賴等場景,能夠經過安裝dep包,來實現依賴管理。dep提供了幾個經常使用的命令,分別用於安裝和更新相應的go包。ui

  • dep init 初始化一個項目依賴
  • dep ensure 安裝項目所依賴的全部包
  • dep ensure -update 更新項目中的全部包
  • dep ensure -add github.com/pkg/errors 爲項目添加單個依賴包

此外經過Gopkg.toml裏面能夠指定所依賴包的git分支,版本號等等,且在dep ensure -add中也能夠指定分支和版本號,好比:spa

dep ensure -add github.com/pkg/foo@^1.0.1
複製代碼

提到包(package),必須補充一句,在go中若是在其餘包中引用變量,是經過:

包名.變量名
複製代碼

的形式,在這裏變量名必須是大寫的,也就是說在go的包中,變量可否導出是根據變量的大小寫來肯定的,廣泛認爲若是變量是大寫的就是在包內導出的,若是是變量小寫的就是默認是包的私有變量。

二、go中的切片

在go的函數調用中,若是傳遞的參數是一個較大的數組,顯然若是直接將數組做爲實參傳入,在執行函數的過程當中,實際上會拷貝一份該數組,會形成內存的浪費等。標準的作法,是傳入數組的指針,或者對於數組的部分引用。

這裏關於數組的部分引用,就是slice切片

(1)、go中的切片簡介

數組和切片之間存在着緊密的聯繫,slice提供了訪問數組子序列的功能。所謂的切片是對於數組的部分引用,slice由三部分組成指針、長度和容量。

  • 指針: 指向第一個slice元素所對應的數組元素的地址
  • 長度: slice中元素的數目
  • 容量: slice中最多可容納元素的數目

切片的定義方式:

var slice1 []type = make([]type, len, cap)
複製代碼

分別指定切片的類型,長度以及容量。

切片的初始化:

s := [] int { 1,2,3 }
複製代碼

或者經過已經存在的數組來實現切片的初始化,

arr = [10]int {1,2,3,4,5,6,7,8,9,10}
s:=arr[1:5] // arr[startIndex:endIndex]
複製代碼

(2)、go中的切片注意點

go中的slice切片有一個注意點,就是如何判斷切片爲空,邊界狀況大體以下所示:

var s []int   //len(s)==0,s==nil
s = nil       //len(s)==0,s==nil
s = []int(nil)//len(s)==0,s==nil
s = []int{}   //len(s)==0,s!=nil
複製代碼

顯然若是經過s==nil來判斷,不能區別第四種場景,所以判斷切片爲空的正確方式是len(s)==0.

三、byte 和 string

下述的方法將返回一個byte的切片:

var test:= []byte("hello")
複製代碼

go遍歷slice動態刪除 map遍歷刪除安全.

四、go中的Map

map是一個無序的key/value對的集合,其中在每個map中key是惟一的。go中的map只要坑在於map是無序的。

(1)、Map簡介

聲明一個map:

var ages map[string]int  //一樣初始的狀況下,ages = nil
ages == nil // true
複製代碼

若是聲明瞭可是沒有賦值,那麼嘗試插入一對key/value會報錯,好比上述聲明但沒有初始化的狀況下:

age["jony"] = 25 // 會panic
複製代碼

解決方法,就是給age定義後賦值:

ages = make(map[string]int)
複製代碼

或者定義的時候同時賦值:

ages := map[string]int{

}
複製代碼

此後插入不存在的key/value就不會報錯。

注意:嘗試從map中去一個不存在的key,默認的value值爲0

(2)、Map無序性

咱們從map的遍歷結果,來講明map是無序的。好比咱們以這麼一個map爲例:

var ages = map[string]int{
"a":21,
"b":22,
"c":23,
};
for name,age := range ages {
  fmt.Printf("%s\t%d\n",name,age);
}
複製代碼

經過for range能夠遍歷map對象,分別執行三次遍歷後,來看遍歷的結果

  • 第一次輸出:

    c 23 a 21 b 22

  • 第二次輸出: c 23 b 22 a 21

  • 第三次輸出: a 21 b 22 c 23

從上述的結果咱們也能夠看出map的每次遍歷的結果都是不肯定的。

注意:Map的value類型不只僅能夠是基本類型,也能夠是聚合類型,好比map或者slice。

5 、go中的struct結構體

跟C++中的結構體相似,go中的結構體是一種聚合數據類型,由0個或者多個任意值聚合成實體。

(1)、結構體簡介

聲明一個結構體很簡單,好比咱們聲明瞭一個Person結構體:

type Person struct {
   name string
   age int
   salary int
}
複製代碼

而後能夠聲明一個Person類型的變量:

var person Person
複製代碼

而後能夠經過點操做符訪問和賦值。

person.age = 25
複製代碼

此外,能夠經過取地址符號加點操做符來訪問和賦值,下述取地址的方式效果與上述是相同的。

(&person).age = 25
複製代碼

此外,結構體也支持嵌套。

六、go中的方法

在go中沒有明確的定義類,可是能夠將結構體struct來類比其餘語言中的class。

go中的方法與結構體相關,爲了說名go中的方法,咱們先從go中的函數講起。

(1)、go中的函數簡介

在go中函數聲明包括函數名、形參列表、返回值列表(可省略 不傲視無返回值)以及函數體。

func name (parameter-list)(result-list){


}
複製代碼

好比咱們有一個count函數能夠如此簡單的定義:

func count(x,y int) int {
   return x + y
}
複製代碼

(2)、go中方法簡介

在函數定義的基礎上咱們來介紹一下,如何定義方法。在函數聲明時,在函數名前放上一個變量,這個變量稱爲方法的接收器,通常是結構體類型的。

固然也不必定是結構體,基本類型數值、字符串、slice和map上面均可以做爲接收器來定義方法。

聲明方法的方式具體能夠以下所示:

func (receive Receive) name(parameter-list)(result-list){


}
複製代碼

從上述的聲明中也能夠看出來只不過在函數的技術上增長了第一個參數接收器,爲相應的接收器增長了該名稱的方法。好比咱們定一個Person結構體,併爲其聲明sellHello方法:

type Person struct {
   name string
   age int
   salary int
}

func (person Person) sayHello() string{
  return "Hello "+ person.name
}

p := Person{
   name: "Jony",
   age: 25,
   salary:100
}

fmt.Println(p.sayHello());//輸出Hello Jony
複製代碼

上述就是在結構體Person上定義了一個sayHello方法,在結構體被初始化後,能夠經過p.sayHello()的方式直接調用。

除此以外,咱們前面將到定義方法時的接收器不必定是一個結構體,接收器也能夠接受基本類型等,好比:

type Mystring string;

func (mystring Mystring)sayHello() string{
  return "Hello"+ string(mystring);
}

var m Mystring
m = "Jony"
fmt.Println(m.sayHello());
複製代碼

上述的例子一樣會輸出Hello Jony.

甚至nil也能夠做爲方法的接收器,這裏就不具體舉例。

(3)、基於指針對象的方法

在函數調用時,是對實參的一個拷貝,若是函數須要更新一個變量,或者傳遞的參數過大,默認拷貝太爲負責,咱們常常會使用指針的形式,對於方法而言也一樣如此,也就是說方法的接收器能夠是指針類型。

對比於上述非指針類型的方法,聲明指針類型的方法具體以下所示:

func (receive *Receive) name(parameter-list)(result-list){


}
複製代碼

指針類型的參數做爲接收器,能夠修改傳入參數的實際變量的值。

type Person struct {
  name string
  age int
  salary int
}
func (person *Person) changeAge(newAge int){
  (*person).age = newAge
}
p.changeAge(30);
fmt.Println(p.age); //輸出了30,發現age確實發生了改變。
複製代碼

七、go中的interface接口

咱們前面也說過go不是一種傳統的面向對象的語言,沒有類和繼承的概念,go裏面經過interface接口能夠實現不少面向對象的特性。

接口的通俗定義:

接口提供了一種方式來講明對象的行爲,接口定義了一組方法,可是不包含實現。

(1)、interface接口簡介

能夠經過以下格式來定義接口:

type Namer interface {
   Method1(param_list) return_type
   Method2(param_list) return_type
   ...
}
複製代碼

go中的接口都很簡短,通常包含了0-3個方法。

同時咱們能夠經過:

var ai Namer 
複製代碼

來定義一個接口類型的變量,初始值爲nil.接口類型的變量是一個指針,聲明而未賦值的狀況下就爲nil。

go中的接口有如下須要注意的點:

  • 一個類型能夠實現多個接口
  • 接口類型能夠包含一個實例的引用,該實例的類型實現了此接口
  • 即便接口在類型以後定義,兩者存在不一樣的包中,被單獨編譯,可是隻要類型實現了接口中的方法,它就實現了此接口
  • 實現某個接口的類型,除了實現接口方法外,還能夠有其餘的方法

上述幾點都比較好理解,具體第二點,舉例來講:

type Person struct {
 name string
 age int
 salary int
}
type Say interface {
  sayHello() string
}
func (person Person) sayHello() string {
  return "Hello "+person.name
}

func main() {
  p := new(Person)
  p.name = "Jony"

  var s Say;
  s = p;
  fmt.Println(s)
}
複製代碼

上述例子中,咱們首先new了一個Person結構體類型的變量,並賦值給p,由於Person接口體中實現了Say接口中的全部方法sayHello等。所以咱們就說Person實現了Say接口,所以Person的實例p,能夠賦值給一個Say接口類型的變量s。

此時的s是個指針,指向Person結構體實例p。

(2)、interface接口類型斷言

任何類型只要實現了接口中的全部方法,咱們就說該類型實現了該接口。這樣一個接口類型的變量varI能夠包含任何類型的值,在go中提供了一種安全的方式來檢測它的動態類型。

if v,ok := varI.(T);ok {
   Process(v)
   return
}
複製代碼

若是轉化合法,那麼v是varI轉化到類型T的值,ok會是true,不然v是類型T的零值,ok是false。這是一種安全的轉化方式不會有錯誤發生。

咱們仍是接着上面的代碼來說咱們的例子:

type Person struct {
 name string
 age int
 salary int
}

type Say interface {
  sayHello() string
}

func (person Person) sayHello() string {
  return "Hello "+person.name
}

func main() {
  p := new(Person)
  p.name = "Jony"

  var s Say;
  s = p;
  if t,ok := s.(*Person);ok {
    fmt.Printf("The type of s is:%T\n",t);
  }
}
複製代碼

輸出的結果爲The type of s is:*main.Person。也可使用特殊的type-switch來判斷。

switch t:= s.(*Person){
   case *Person:
      fmt.Printf("The type of s is:%T\n",t);
   case nil:
      ...
   default:
      ...
}
複製代碼

八、interface{}

interface{}是一個空接口,任何類型的值均可以複製給interface{}類型的變量。

好比,咱們首先聲明一個類型爲interface{}的變量:

var test interface{}
複製代碼

任意類型的值均可以複製給test,好比下列基本類型的值複製給test是有效的:

var test interface{}
test = 1
test = true
test ="Hello"
複製代碼

此外,複雜的派生類型也能夠賦值給test,咱們以指針類型舉例:

var test interface{}
var a = 1
test = &a 
複製代碼

interface類型的變量是沒有類型的,可是咱們能夠人爲的進行類型轉換:

var test interface{}
var a string
test = "hello"
a = test.(string)
複製代碼

上述,能夠將test轉化成string類型,這樣就能夠賦值給string類型變量a了。經過.(類型名)的方法能夠將interface{}類型的變量轉化成任意的類型。

最後舉一個簡單的例子:

func main() {
   a := make([]interface{},10)
   b :=1
   a[1]=&b
   fmt.Println(*(a[1].(*int)))
}
複製代碼

上述代碼發現,將interface{}類型切片中的某一元素的值複製給了int指針類型,而後進行了類型轉化,將interface{}類型的變量轉換成了int指針類型。

相關文章
相關標籤/搜索