Golang 筆記 2 函數、結構體、接口、指針

1、函數

  Go中函數是一等(first-class)類型。咱們能夠把函數看成值來傳遞和使用。Go中的函數能夠返回多個結果。
  函數類型字面量由關鍵字func、由圓括號包裹聲明列表、空格以及能夠由圓括號包裹的結果聲明列表組成。其中參數聲明列表中的單個參數聲明之間是由英文逗號分隔的。每一個參數聲明由參數名稱、空格和參數類型組成。參數聲明列表中的參數名稱是能夠被統一省略的。結果聲明列表的編寫方式與此相同。結果聲明列表中的結果也是能夠被省略的,而且在只有一個無名稱的結果聲明時還能夠省略括號。例:函數

func (input1 string, input2 string) string

  這一類型字面量表示了一個接受兩個字符串類型的參數且會返回一個字符串類型的結果的函數。若是咱們在它的左邊加入type關鍵字和一個標識符做爲名稱的話就變成了一個函數類型聲明:ui

type MyFunc func(input1 string, input2 string) string

  函數值(或簡稱函數)的寫法與此不徹底相同。編寫函數的時候須要先寫關鍵字func和函數名稱,後跟參數聲明列表和結果聲明列表,最後是花括號包裹的語句列表。例如:atom

func myFunc(part1 string, part2 string) (result string) { result = part1 + part2 return }

  若是結果聲明是帶名稱的,那麼它就至關於一個已被聲明但未被顯示賦值的變量。咱們能夠爲它賦值且在return語句中省略掉須要返回的結果值。該函數還有一種更常規的寫法:spa

func myFunc(part1 string, part2 string) string { return part1 + part2 }

  函數myFunc是函數類型MyFunc的一個實現。實際上,只要一個函數的參數聲明列表和結果聲明列表中的數據類型順序和名稱與某一個函數類型徹底一致,前者就是後者的一個實現。
  咱們能夠聲明一個函數類型的變量,如:指針

var splice func(string, string) string // 等價於var splice MyFunc

  而後把函數myFunc賦給它:code

splice = myFunc

  如此一來,咱們就能夠在這個變量之上實施調用動做了:接口

splice("1", "2")

  實際上,這是一個調用表達式。上面的代碼能夠簡化爲:字符串

var splice = func(part1 string, part2 string) string { return part1 + part2 }

  在這個示例中,咱們直接使用了一個匿名函數來初始化splice變量。顧名思義,匿名函數就是不帶名稱的函數值。匿名函數直接由函數類型字面量和由花括號包裹的語句列表組成。注意,這裏的函數類型字面量中的參數名稱是不能被省略的。
  還能夠進一步簡化--省去splice變量。例:input

var result = func(part1 string, part2 string) string { return part1 + part2 }("1", "2");

  函數類型的零值是nil。編譯器

2、結構體和方法

  Go語言的結構體類型Struct比函數類型更靈活。它能夠封裝屬性和操做。前者便是結構體類型中的字段,然後者則是結構體類型所擁有的方法。
  結構體類型的字面量由關鍵字type、類型名稱、關鍵字struct以及花括號包裹的若干字段聲明組成,其中,每一個字段聲明獨佔一行並由字段名稱(可選)和字段類型組成。例:

type Person struct { Name string Gender string Age uint8 }

  結構體類型Person中有三個字段,分別是Name、Gender和Age。咱們能夠用字面量建立出一個該類型的值,像這樣:

Person{Name:"Robert",Gender:"Male",Age:33}

  若是這裏鍵值對的順序與其類型中的字段聲明徹底相同的話,能夠統一省略全部字段的名稱,就像這樣:

Person{"Robert", "Male", 33}

  咱們在編寫某個結構體類型的值字面量時能夠只對它的部分字段賦值,甚至不對它的任何字段賦值。這時未被顯式賦值的字段的值則爲其類型的零值。
  與表明函數值的字面量類似。咱們在寫一個結構體值的字面量時不須要先擬好其類型。這樣的結構體字面量被稱爲匿名結構體。與匿名函數相似,咱們在編寫匿名結構體時須要先寫明其類型特徵(包含字段聲明),再寫出它的值初始化部分。咱們依照結構體類型Person建立一個匿名結構體:

p := struct { Name string Gender string Age uint8 }{"Robert", "Male", 33}

  匿名結構體最大的用處是在內部臨時建立一個結構以封裝數據,而沒必要正式爲其聲明相關規則。
  結構體類型能夠擁有若干方法(匿名結構體是不可能擁有方法的)。所謂方法,其實就是一種特殊的函數。它能夠依附於某個自定義類型。方法的特殊在於它的聲明包含了一個接收者聲明。這裏的接收者指代它所依附的那個類型。以結構體類型Person爲例,下面是依附於它的一個名爲Grow的方法的聲明:

func (person *Person) Grow() { person.Age++ }

  如上所示,在關鍵字func和名稱Grow之間的那個圓括號及其包括的內容就是接收者聲明。其中的內容由兩部分組成。第一部分是表明它所依附的那個類型的值的標識符。第二部分是它依附的那個類型的名稱。後者代表了依附關係,而前者則使得在該方法中的代碼可使用到該類型的值(也稱爲當前值)。表明當前值的那個標識符可被稱爲接收者標識符,或簡稱爲接收者。例:

p := Person{"Robert", "Male", 33} p.Grow()

  咱們能夠直接在Person類型的變量p之上應用調用表達式來調用它的方法Grow。注意,此時方法Grow的接收者標識符person指代的正是變量p的值。這也是「當前值」這個詞的由來。在Grow方法中,咱們經過使用選擇表達式選擇了當前值的字段Age。並使其自增。所以,在語句p.Grow()被執行以後,p所表明的那我的就又年長了一歲(p的Age字段的值已變成34)。
  在Grow方法的接收者聲明中的那個類型是*Person,而不是Person。實際上,前者是後者的指針類型。這也使得person指代的是p的指針,而不是它自己。   結構體類型屬於值類型。它的零值並非nil。而是其中字段的值爲相應類型的零值的值。結構體類型Person的靈值若用字面量來表示的話是Person{}

3、接口

  在Go語言中,一個接口類型老是表明着某一種類型(即全部實現它的類型)的行爲。一個接口類型的聲明一般會包含關鍵字type、類型名稱、關鍵字interface以及由花括號包裹的若干方法聲明。例:

type Animal interface { Grow() Move(string) string }

  接口類型中的方法聲明是普通的方法聲明的簡化形式。它們只包括方法名稱、參數聲明列表和結果聲明列表。其中的參數的名稱和結果的名稱均可以被省略。未省略應該是這樣的:

Move(new string) (old string)

  若是一個數據類型所擁有的方法集合中包含了某一個接口類型中的全部方法聲明的實現,那麼就能夠說這個數據類型實現了那個接口類型。所謂實現了一個接口中的方法是指,具備與該方法相同的聲明而且添加了實現部分(由花括號包裹的若干條語句)。相同的方法聲明意味着徹底一致的名稱、參數類型列表和結果類型列表。其中,參數類型列表即爲參數聲明列表中除去參數名稱的部分。一致的參數類型列表意味着其長度以及順序的徹底相同。對於結果類型列表也是如此。   咱們無需在一個數據類型中聲明它實現了哪一個接口。只要知足了"方法集合爲超集"的條件,就創建了「實現」關係。   如今咱們已經認爲*Person類型實現了Animal接口。可是Go語言編譯器是否也這麼認爲呢?這顯然須要一種顯式的斷定方法。在Go語言中,這種斷定能夠用類型斷言來實現。不過,在這裏,咱們不能在一個非接口類型的值上應用類型斷言來斷定它是否屬於某一個接口類型。咱們必須先把前者轉換成空接口類型的值。這就涉及到了Go的類型轉換。
  Go語言的類型轉換規則定義了是否可以以及怎樣把一個類型的值轉換另外一個類型的值。另外一方面,所謂空接口類型便是不包含任何方法聲明的接口類型,用interface{}表示,常簡稱爲空接口。Go中的包含預約義的任何數據類型均可以被看做是空接口的實現。咱們可直接使用類型轉換表達式把一個*Person類型轉換成空接口類型的值,例:

p := Person{"Robert", "Male", 33, "Beijing"} v := interface{}(&p)

  在類型字面量後跟由圓括號包裹的值(或可以表明它的變量、常量或表達式)就構成了一個類型轉換表達式,意爲將後者轉換爲前者類型的值。在這裏,咱們把表達式,意爲將後者轉換爲前者類型的值。在這裏,咱們把表達式&p的求值結果轉換成了一個空接口類型的值,並由變量v表明。表達式&p(&是取地址符)的求值結果是一個*Person類型的值,及p的指針。
  在這以後,咱們就能夠在v上應用類型斷言了,即:

h,ok := v.(Animal)

  類型斷言表達式v.(Animal)的求值結果能夠有兩個。第一個結果是被轉換後的那個目標類型(這裏是Animal)的值,而第二個結果則是轉換操做成功與否的標誌。顯然,ok表明了一個bool類型的值。它也是這裏斷定實現關係的重要依據。

4、指針

  指針操做涉及到兩個操做符--&和*。這兩個操做符均有多個用途。可是當它們做爲地址操做符出現時,&的做用是取址,*的做用是取值。   當*出如今一個類型以前(如*Person和*[3]string)時就不能被看做是操做符了,而應該被視爲一個符號。如此組合而成的標識符所表達的含義是做爲第二部分的那個類型的指針類型。咱們也能夠把其中的第二部分所表明的類型稱爲基底類型。   如今後頭看結構體類型Person。它及其兩個方法的完整聲明以下:

type Person strcut {
    Name    string Gender string Age uint8 Address string } func (person *Person) Grow() { person.Age++ } func (person *Person) Move(newAddres string) string { old := person.Address person.Address = newAddress return old }

  注意,Person的兩個方法Grow和Move的接收者類型都是*Person,而不是Person。只要一個方法的接收者類型是其所屬類型的指針類型而不是該類型自己,那麼就能夠稱該方法爲一個指針方法。上面的Grow方法和Move方法都是Person類型的指針方法。
  相對的,若是一個方法的接收者類型就是其所屬的類型自己。那麼咱們就能夠把它叫作值方法。

func (person Person) Grow() { person.Age++ }

  方法的接收者標識符所表明的是該方法當前所屬的那個值的一個副本,而不是該值自己。   之因此表達式person.Age成立,是由於若是Go語言發現person是指針而且指向的那個值有Age字段,那麼就會把該表達式視爲(*person).Age。

  擁有指針方法Grow和Move的指針類型*Person是接口類型Animal的實現類型,可是它的基底類型Person卻不是。這樣的表象隱藏了另外一條規則:一個指針類型擁有以它以及以它的基底類型爲接收者類型的全部方法,而它的基底類型卻至擁有以它自己爲接收者類型的方法。

  咱們在基底類型的值上仍然能夠調用它的指針方法。例如,若咱們有一個Person類型的變量bp,則調用表達式bp.Grow()是合法的。這是由於,若是Go語言發現咱們調用的Grow方法是bp的指針方法,那麼它會把調用表達式是爲(&bp).Grow()。

相關文章
相關標籤/搜索