Go基礎學習四之函數function、結構struct、方法method

Go編程語言:支持併發、垃圾回收的編譯型系統級編程語言!本文主要是按照無聞的《Go 編程基礎》開源視頻學習並記錄筆記。編程

1、函數function

一、基本概念

函數是基本的代碼塊,用於執行一個任務。
Go 語言最少有個 main() 函數
函數聲明告訴了編譯器函數的名稱,返回類型,和參數
Go 語言標準庫提供了多種可動用的內置的函數。例如,len() 函數能夠接受不一樣類型參數並返回該類型的長度。若是咱們傳入的是字符串則返回字符串的長度,若是傳入的是數組,則返回數組中包含的函數個數。數組

二、函數定義

函數定義格式以下:閉包

func function_name( [parameter list] ) [return_types] {
   函數體
}

函數定義解析:併發

  • func:函數由 func 開始聲明
  • function_name:函數名稱,函數名和參數列表一塊兒構成了函數簽名。
  • parameter list:參數列表,參數就像一個佔位符,當函數被調用時,你能夠將值傳遞給參數,這個值被稱爲實際參數。參數列表指定的是參數類型、順序、及參數個數。參數是可選的,也就是說函數也能夠不包含參數。
  • return_types:返回類型,函數返回一列值。return_types 是該列值的數據類型。有些功能不須要返回值,這種狀況下 return_types 不是必須的。
  • 函數體:函數定義的代碼集合。

示例:編程語言

/* 函數返回兩個數的最大值 */
func max(num1, num2 int) int {
   /* 聲明局部變量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

三、函數特性

  • Go 函數不支持嵌套、重載和默認參數
  • Go 函數支持無需聲明原型、不定長度變參、多返回值、命名返回值參數、匿名函數、閉包
  • 定義函數使用關鍵字func,且左大括號不能另起一行
  • 函數也能夠做爲一種類型使用
ackage main


import "fmt"  

func main() {
  A(1, 2, 3, 4 ,5)
}


// ... 不定長變參
func A(a ...int) {
    fmt.Println(a)
    
}

輸出:函數

➜  myfirstgo go run func.go
[1 2 3 4 5]

不定長變參特性:
一、不能夠在不定長變參後邊添加其餘參數 func b(a ...int, b string), 這種寫法是錯誤
二、不定長參數能夠放在其餘參數後邊 func b(b string, a ...int)學習

四、匿名函數

func main() {

  // 將一個函數賦值一個變量,該變量是函數類型
  a := func(){
    fmt.Println("匿名函數")
}

  // 調用
  a()
}

五、閉包

/**
* 閉包函數
*
* 該閉包函數接收一個int型參數,其返回值是函數類型
*
*/
func closure(x int) func(int) int {
    fmt.Println("%p\n", &x)
    return func (y int) int {

        fmt.Println("%p\n", &x)
        fmt.Println(x)
        fmt.Println(y)
    
        return x + y
    }
}

func main() {
  f := closure(10);
  fmt.Println(f(1))
  fmt.Println(f(2))
}

打印結果:測試

➜  myfirstgo go run func.go
%p
 0xc42000e228
%p
 0xc42000e228
10
1
11
%p
 0xc42000e228
10
2
12
➜  myfirstgo

六、defer

  • defer 的執行方式相似其餘語言中的析構函數,在函數體執行結束後按照調用順序的相反順序逐個執行
  • 即便函數發生嚴重錯誤也會執行
  • 支持匿名函數的調用
  • 經常使用於資源清理、文件關閉、解鎖以及記錄時間等操做
  • 經過與匿名函數配合可在return以後修改函數計算結果
  • 若是函數體內某個變量做爲defer時匿名函數的參數,則在定義defer時即已經得到了拷貝,不然則是引用某個變量的地址
  • Go沒有異常機制,但有 panic/recover 模式來處理錯誤
  • Panic 能夠在任何地方引起,但 recover 只有defer 調用的函數中有效

簡單的測試:指針

func main() {
  fmt.Println("a")
  
  defer fmt.Println("b")
  defer fmt.Println("c")
}

上邊的執行會打印什麼結果呢? 會打印:a b c 嗎?
讓咱們實際執行一下:code

myfirstgo go run func.go
a
c
b

實際打印的結果爲:a c b

defer 的執行方式相似其餘語言中的析構函數,在函數體執行結束後按照調用順序的相反順序逐個執行

使用閉包

func main() {
  for i := 0; i < 3; i++ {

       // defer 普通調用
       // defer fmt.Println(i)  // 打印 2 1 0

     // 使用閉包,引用局部變量
       defer func () {
           fmt.Println(i)
       }()

  }
  
}

打印結果:

➜  myfirstgo go run func.go
3
3
3

panic 使用示例

func main() {
  A()
  B()
  C()
}

func A() {
    fmt.Println("FUNC A")
}

func B() {

   // 匿名函數,若是沒有參數,則末尾須要使用括號
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recover is B")

        }
        
    }()

    panic("B panic")

}

func C() {
    fmt.Println("FUNC C")
}

打印結果:

➜  myfirstgo go run func.go
FUNC A
Recover is B
FUNC C
➜  myfirstgo

2、結構Struct

1.基本概念

Go 語言中數組能夠存儲同一類型的數據,但在結構體中咱們能夠爲不一樣項定義不一樣的數據類型
結構體由一系列具備相同類型或不一樣類型的數據構成的數據集合
結構體表示一項記錄,好比保存圖書館的書籍記錄,每本書有如下屬性:
Title :標題
Author : 做者
Subject:學科
ID:書籍ID

2.結構體定義

結構體定義須要使用 typestruct 語句。struct 語句定義一個新的數據類型,結構體中有一個或多個成員。type 語句設定告終構體的名稱。結構體的格式以下:

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}

一旦定義告終構體類型,它就能用於變量的聲明,語法格式以下:

variable_name := structure_variable_type {value1, value2...valuen}

type 是定義名稱,struct 是結構體類型,如同 int 類型同樣。

結構體能夠包含多種數據類型,數組只能是單一類型的數據集合。

訪問結構體成員
若是要訪問結構體成員,須要使用點號 (.) 操做符,格式爲:"結構體.成員名"
結構體類型變量使用struct關鍵字定義,實例以下:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 聲明 Book1 爲 Books 類型 */
   var Book2 Books        /* 聲明 Book2 爲 Books 類型 */

   /* book 1 描述 */
   Book1.title = "Go 語言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 語言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 語言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   fmt.Printf( "Book 1 title : %s\n", Book1.title)
   fmt.Printf( "Book 1 author : %s\n", Book1.author)
   fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
   fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

   /* 打印 Book2 信息 */
   fmt.Printf( "Book 2 title : %s\n", Book2.title)
   fmt.Printf( "Book 2 author : %s\n", Book2.author)
   fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
   fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}

以上實例執行運行結果爲:

Book 1 title : Go 語言
Book 1 author : www.runoob.com
Book 1 subject : Go 語言教程
Book 1 book_id : 6495407
Book 2 title : Python 教程
Book 2 author : www.runoob.com
Book 2 subject : Python 語言教程
Book 2 book_id : 6495700

3.struct特性

  • Go中的struct與C中的struct很是類似,而且Go沒有class
  • 使用 type <Name> struct{} 定義結構,名稱遵循可見性規則
  • 支持指向自身的指針類型成員
  • 支持匿名結構,可用做成員或定義成員變量
  • 匿名結構也能夠用於map的值
  • 可使用字面值對結構進行初始化
  • 容許直接經過指針來讀寫結構成員
  • 相同相似的成員可進行直接拷貝賦值
  • 支持 == 與 != 比較運算符,但不支持 > 或 <
  • 支持匿名字段,本質上是定義了以某個類型名爲名稱的字段
  • 嵌入結構做爲匿名字段看起來像繼承,但不是繼承
  • 可使用匿名字段指針

示例:

package main


import "fmt"  

type person struct{
    Name string
    Age int
}

func main() {


  a := person{
      Name:"Jam",
      Age:19,
  }
  // a.Age = 20


  fmt.Println(a)


}

打印:

➜  myfirstgo go run struct.go
{Jam 19}

傳遞指針變量:

package main


import "fmt"  

type person struct{
    Name string
    Age int
}

func main() {


  a := person{
      Name:"Jam",
      Age:19,
  }
  // a.Age = 20
  fmt.Println(a)


  A(a)

  // 值拷貝,若是須要原來的改變,則須要添加指針
  B(&a)

  fmt.Println(a)


}

// per 爲變量名,person表示爲結構體類型
func A(per person) {
    per.Age = 25
    fmt.Println("A", per)
}

// per 爲變量名,person表示爲結構體類型
func B(per *person) {
    per.Age = 18
    fmt.Println("B", per)
}

打印:

➜  myfirstgo go run struct.go
{Jam 19}
A {Jam 25}
B &{Jam 18}
{Jam 18}

或者在初始化結構體時,獲取到變量地址並賦值變量,這樣作的好處是在傳遞參數時,不須要傳遞地址符號了,只需在函數定義時,給參數加星號便可。

package main


import "fmt"  

type person struct{
    Name string
    Age int
}

func main() {

 // 在結構初識化時,咱們習慣取地址符號,這樣a就爲指向某個結構的指針
  a := &person{
      Name:"Jam",
      Age:19,
  }

  a.Name = "Corwien"

  // a.Age = 20
  fmt.Println(a)


  A(a)

  // 值拷貝,若是須要原來的改變,則須要添加指針
  B(a)

  fmt.Println(a)


}

// per 爲變量名,person表示爲結構體類型
func A(per *person) {
    per.Age = 25
    fmt.Println("A", per)
}

// per 爲變量名,person表示爲結構體類型
func B(per *person) {
    per.Age = 18
    fmt.Println("B", per)
}

打印:

➜  myfirstgo go run struct.go
&{Corwien 19}
A &{Corwien 25}
B &{Corwien 18}
&{Corwien 18}

匿名結構:
匿名結構,沒有名稱的結構體

func main() {


// 匿名結構,沒有名稱的結構體
  a := struct {
      Name string
      Age int
  }{
      Name:"Corwien",
      Age: 20,
  }

  fmt.Println(a)

}

打印:

➜  myfirstgo go run struct.go
{Corwien 20}

匿名結構嵌套:

type person struct{
    Name string
    Age int
    Contact struct {
        Phone, City string
        Code int           // 門牌號
    }
}

func main() {
    a := person{Name:"Corwien", Age:15}
    a.Contact.Phone = "10086"
    a.Contact.City = "Guangzhou"
    a.Contact.Code = 2007
  
    fmt.Println(a)

}

打印:

➜  myfirstgo go run struct.go
{Corwien 15 {10086 Guangzhou 2007}}

匿名字段:

匿名字段:結構體沒有命名結構體屬性的字段,只有類型,匿名字段必須嚴格遵照字段類型聲明的順序。

type person struct{
    string
    int
}

func main() {

    // 匿名字段必須嚴格遵照字段類型聲明的順序
    a := person{"Corwien", 12}
    
    fmt.Println(a)

}

打印:

➜  myfirstgo go run struct.go
{Corwien 12}

結構類型比較

type person struct{
    Name string
    Age int
}

func main() {

    // 匿名字段必須嚴格遵照字段類型聲明的順序
    a := person{Name:"Corwien", Age:12}
    b := person{Name:"Corwien", Age:12}
    
    fmt.Println(a == b)

}

打印:

➜  myfirstgo go run struct.go
true

Go如何繼承呢

咱們知道其餘語言有繼承,好比相同的屬性,咱們沒必要重複去寫,只需繼承父類的公共屬性便可。遺憾的是Go沒有繼承,但Go有組合.

package main

import "fmt"  

// 嵌入結構做爲匿名字段
type human struct {
    Sex int
}

type teacher struct {
    human            // Go會將嵌入字段默認做爲屬性名,因此在賦值時須要這樣寫:human: human{Sex: 1}
    Name string
    Age int
}

type student struct {
    human
    Name string
    Age int
}

func main() {

    a := teacher{Name:"Corwien", Age:25, human: human{Sex: 1}}
    b := student{Name:"mark", Age:12, human: human{Sex: 1}}
    
    a.Name = "Jack"
    a.Age = 10
    // a.human.Sex = 0
    a.Sex = 0
    fmt.Println(a, b)

}

打印:

➜  myfirstgo go run struct.go
{{0} Jack 10} {{1} mark 12}

3、方法method

1.基本概念

Go不像其它面相對象語言同樣能夠寫個class,而後在class裏面寫一堆方法,可是它也很巧妙的實現了這種效果,咱們只須要在普通函數前面加個接受者(receiver,寫在函數名前面的括號裏面),這樣編譯器就知道這個函數(方法)屬於哪一個struct了

method是附屬在一個給定的類型上,語法和函數的聲明語法幾乎同樣,只是再func後面增長了一個recevier(也就是method所依從的主體)

2.method定義

func (r ReceiverType) funcName(parameters) (results)

形象一點說,就是 ReceiverType 類型的全部字段,方法 funcName 都是可使用的,能夠認爲 funcName 屬於 ReceiverType

示例:

package main

import (
    "fmt"
    "math"
)

type Rectangle struct {
    width, height float64
}
type Circle struct {
    radius float64
}

func (r Rectangle) area() float64 {
    return r.width * r.height
}
func (c Circle) area() float64 {
    return c.radius * c.radius * math.Pi
}
func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    c1 := Circle{10}
    c2 := Circle{25}
    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
    fmt.Println("Area of c1 is: ", c1.area())
    fmt.Println("Area of c2 is: ", c2.area())
}

輸出:

Area of r1 is:  24
Area of r2 is:  36
Area of c1 is:  314.1592653589793
Area of c2 is:  1963.4954084936207

method 是經過 . 來訪問,就像訪問struct裏面字段同樣。

method 裏面能夠訪問接受者的字段,好比 r1.area() 就能夠訪問 r1 裏面的 width 和 height。

雖然 method 的名字是同樣的,可是不一樣的 receiver 不同,那麼 method 就不同。這一點很重要哦

還有一點,method不只能做用再struct上,也能夠定義再任何自定義的類型、內置類型等各類類型上面。

method 中的 receiver 能夠是值傳遞,也能夠是指針。指針的話,就能夠直接修改 receiver 中的內容。

3.method特性

  • Go中雖沒有class,但依舊有method
  • 經過顯示說明receiver來實現與某個類型的組合
  • 只能爲同一個包中的類型定義方法
  • Receiver能夠是類型的值或者指針
  • 不存在方法重載
  • 可使用值或指針來調用方法,編譯器會自動完成轉換
  • 從某種意義上來講,方法是函數的語法糖,由於receiver其實就是方法所接收的第1個參數)
  • 若是外部結構和嵌入結構存在同名方法,則優先調用外部結構的方法
  • 類型別名不會擁有底層類型所附帶的方法
  • 方法能夠調用結構中的非公開字段

舉例:

package main


import "fmt"  

type A struct {
    Name string
}

type B struct {
    Name string
}

func main() {
    a := A{}
    a.Print()
    fmt.Println(a.Name)

    b := B{}
    b.Print()
    fmt.Println(b.Name)
}

// 指針傳遞
func (a *A) Print() {
    a.Name = "AA"
    fmt.Println("A")
}

func (b B) Print() {
    b.Name = "BB"
    fmt.Println("B")
}

打印:

➜  myfirstgo go run method.go
A
AA
B

➜  myfirstgo

其餘類型的方法綁定

type TZ int

func main() {
    var a TZ
    a.Print()
    fmt.Println(a)
}

func (a *TZ) Print() {
    fmt.Println("TZ")
}

打印:

➜  myfirstgo go run method.go
TZ
0

最後說下訪問權限,由於Go是以大小寫來區分是公有仍是私有,但都是針對包級別的,因此在包內全部的都能訪問,而方法綁定自己只能綁定包內的類型,因此方法能夠訪問接收者全部成員。若是是包外調用某類型的方法,則須要看方法名是大寫仍是小寫,大寫能被包外訪問,小寫只能被包內訪問。

相關文章
相關標籤/搜索