數據結構和算法(Golang實現)(2)簡單入門Golang-包、變量和函數

包、變量和函數

1、舉個例子

如今咱們來創建一個完整的程序main.go程序員

// Golang程序入口的包名必須爲 main
package main // import "golang"

// 導入其餘地方的包,包經過 go mod 機制尋找
import (
    "fmt"
    "golang/diy"
)

// init函數在main函數以前執行
func init() {
    // 聲明並初始化三個值
    var i, j, k = 1, 2, 3
    // 使用格式化包打印
    fmt.Println("init hello world")
    fmt.Println(i, j, k)
}

// 函數,兩個數相加
func sum(a, b int64) int64 {
    return a + b
}

// 程序入口必須爲 main 函數
func main() {
    // 未使用的變量,不容許聲明
    //cannot := 6

    fmt.Println("hello world")

    // 定義基本數據類型
    p := true                             // bool
    a := 3                                // int
    b := 6.0                              // float64
    c := "hi"                             // string
    d := [3]string{"1", "2", "3"}         // array,基本不用到
    e := []int64{1, 2, 3}                 // slice
    f := map[string]int64{"a": 3, "b": 4} // map
    fmt.Printf("type:%T:%v\n", p, p)
    fmt.Printf("type:%T:%v\n", a, a)
    fmt.Printf("type:%T:%v\n", b, b)
    fmt.Printf("type:%T:%v\n", c, c)
    fmt.Printf("type:%T:%v\n", d, d)
    fmt.Printf("type:%T:%v\n", e, e)
    fmt.Printf("type:%T:%v\n", f, f)

    // 切片放值
    e[0] = 9
    // 切片增長值
    e = append(e, 3)

    // 增長map鍵值
    f["f"] = 5

    // 查找map鍵值
    v, ok := f["f"]
    fmt.Println(v, ok)
    v, ok = f["ff"]
    fmt.Println(v, ok)

    // 判斷語句
    if a > 0 {
        fmt.Println("a>0")
    } else {
        fmt.Println("a<=0")
    }

    // 死循環語句
    a = 0
    for {
        if a >= 10 {
            fmt.Println("out")
            // 退出循環
            break
        }

        a = a + 1
        if a > 5 {
            continue
        } else {
            fmt.Println(a)
        }


    }

    // 循環語句
    for i := 9; i <= 10; i++ {
        fmt.Printf("i=%d\n", i)
    }

    // 循環切片
    for k, v := range e {
        fmt.Println(k, v)
    }

    // 循環map
    for k, v := range f {
        fmt.Println(k, v)
    }

    // 定義 int64 變量
    var h, i int64 = 4, 6

    // 使用函數
    sum := sum(h, i)
    fmt.Printf("sum(h+i),h=%v,i=%v,%v\n", h, i, sum)

    // 新建結構體,值
    g := diy.Diy{
        A: 2,
        //b: 4.0, // 小寫成員不能導出
    }

    // 打印類型,值
    fmt.Printf("type:%T:%v\n", g, g)

    // 小寫方法不能導出
    //g.set(1,1)
    g.Set(1, 1)
    fmt.Printf("type:%T:%v\n", g, g) // 結構體值變化

    g.Set2(3, 3)
    fmt.Printf("type:%T:%v\n", g, g) // 結構體值未變化

    // 新建結構體,引用
    k := &diy.Diy{
        A: 2,
    }
    fmt.Printf("type:%T:%v\n", k, k)
    k.Set(1, 1)
    fmt.Printf("type:%T:%v\n", k, k) // 結構體值變化
    k.Set2(3, 3)
    fmt.Printf("type:%T:%v\n", k, k) // 結構體值未變化

    // 新建結構體,引用
    m := new(diy.Diy)
    m.A = 2
    fmt.Printf("type:%T:%v\n", m, m)

    s := make([]int64, 5)
    s1 := make([]int64, 0, 5)
    m1 := make(map[string]int64, 5)
    m2 := make(map[string]int64)
    fmt.Printf("%#v,cap:%#v,len:%#v\n", s, cap(s), len(s))
    fmt.Printf("%#v,cap:%#v,len:%#v\n", s1, cap(s1), len(s1))
    fmt.Printf("%#v,len:%#v\n", m1, len(m1))
    fmt.Printf("%#v,len:%#v\n", m2, len(m2))

    var ll []int64
    fmt.Printf("%#v\n", ll)
    ll = append(ll, 1)
    fmt.Printf("%#v\n", ll)
    ll = append(ll, 2, 3, 4, 5, 6)
    fmt.Printf("%#v\n", ll)
    ll = append(ll, []int64{7, 8, 9}...)
    fmt.Printf("%#v\n", ll)

    fmt.Println(ll[0:2])
    fmt.Println(ll[:2])
    fmt.Println(ll[0:])
    fmt.Println(ll[:])
}

在相同目錄下新建diy文件夾,文件下新建一個diy.go文件(名字任取):golang

// 包名
package diy

// 結構體
type Diy struct {
    A int64   // 大寫導出成員
    b float64 // 小寫不能夠導出
}

// 引用結構體的方法,引用傳遞,會改變原有結構體的值
func (diy *Diy) Set(a int64, b float64) {
    diy.A = a
    diy.b = b
    return
}

// 值結構體的方法,值傳遞,不會改變原有結構體的值
func (diy Diy) Set2(a int64, b float64) {
    diy.A = a
    diy.b = b
    return
}

// 小寫方法,不能導出
func (diy Diy) set(a int64, b float64) {
    diy.A = a
    diy.b = b
    return
}

// 小寫函數,不能導出,只能在同一包下使用
func sum(a, b int64) int64 {
    return a + b
}

進入文件所在目錄,打開命令行終端,執行:算法

go mod init
go run main.go

會顯示一些打印結果:編程

init hello world
1 2 3
hello world
type:bool:true
type:int:3
type:float64:6
type:string:hi
type:[3]string:[1 2 3]
type:[]int64:[1 2 3]
type:map[string]int64:map[a:3 b:4]
5 true
0 false
a>0
1
2
3
4
5
out
i=9
i=10
0 9
1 2
2 3
3 3
a 3
b 4
f 5
sum(h+i),h=4,i=6,10
type:diy.Diy:{2 0}
type:diy.Diy:{1 1}
type:diy.Diy:{1 1}
type:*diy.Diy:&{2 0}
type:*diy.Diy:&{1 1}
type:*diy.Diy:&{1 1}
type:*diy.Diy:&{2 0}
[]int64{0, 0, 0, 0, 0},cap:5,len:5
[]int64{},cap:5,len:0
map[string]int64{},len:0
map[string]int64{},len:0
[]int64(nil)
[]int64{1}
[]int64{1, 2, 3, 4, 5, 6}
[]int64{1, 2, 3, 4, 5, 6, 7, 8, 9}
[1 2]
[1 2]
[1 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9]

咱們看到Golang語言只有小括號和大括號,不須要使用逗號來分隔代碼,只有一種循環forsegmentfault

接下來咱們會分析這個例子。數組

2、工程管理:包機制

每個大型的軟件工程項目,都須要進行工程管理。工程管理的一個環節就是代碼層次的管理。數據結構

包,也稱爲庫,如代碼的一個包,代碼的一個庫,英文:Library或者Package。好比,咱們經常聽到某程序員說:嘿,X哥,我知道Github上有一個更好用的數據加密庫,幾千顆星呢。併發

在高級編程語言層次,也就是代碼自己,各類語言發明了包(package)機制來更好的管理代碼,將代碼按功能分類歸屬於不一樣的包。app

Golang語言目前的包管理新機制叫go mod數據結構和算法

咱們的項目結構是:

├── diy
│   └── diy.go
└── main.go

每個*.go源碼文件,必須屬於一個包,假設包名叫diy,在代碼最頂端必須有package diy,在此以前不能有其餘代碼片斷,如diy/diy.go文件中:

// 包名
package diy

// 結構體
type Diy struct {
    A int64   // 大寫導出成員
    b float64 // 小寫不能夠導出
}

做爲執行入口的源碼,則強制包名必須爲main,入口函數爲func main(),如main.go文件中:

// Golang程序入口的包名必須爲 main
package main // import "golang"

// 導入其餘地方的包,包經過 go mod 機制尋找
import (
    "fmt"
    "golang/diy"
)

在入口文件main.go文件夾下執行如下命令:

go mod int

該命令會解析main.go文件的第一行package main // import "golang",注意註釋//後面的import "golang",會生成go.mod文件:

module golang

go 1.13

Golang編譯器會將這個項目認爲是包golang,這是整個項目最上層的包,而底下的文件夾diy做爲package diy,包名全路徑就是golang/diy

接着,main.go爲了導入包,使用import ()

// 導入其餘地方的包,包經過 go mod 機制尋找
import (
    "fmt"
    "golang/diy"
)

能夠看到導入了官方的包fmt和咱們自已定義的包golang/diy,官方的包會自動尋找到,不須要任何額外處理,而本身的包會在當前項目往下找。

在包golang/diy中,咱們定義了一個結構體和函數:

// 結構體
type Diy struct {
    A int64   // 大寫導出成員
    b float64 // 小寫不能夠導出
}

// 小寫函數,不能導出,只能在同一包下使用
func sum(a, b int64) int64 {
    return a + b
}

對於包中小寫的函數或者結構體中小寫的字段,不能導出,其餘包不能使用它,Golang用它實現了私有或公有控制,畢竟有些包的內容咱們不想在其餘包中被使用,相似Javaprivate關鍵字。

結構體和函數會在後面的章節介紹,如今只需知道只有大寫字母開頭的結構體或函數,才能在其餘包被人引用。

最後,Golang的程序入口統一在包main中的main函數,執行程序時是從這裏開始的:

package main
import "fmt"

// init函數在main函數以前執行
func init() {
    // 聲明並初始化三個值
    var i, j, k = 1, 2, 3
    // 使用格式化包打印
    fmt.Println("init hello world")
    fmt.Println(i, j, k)
}

// 程序入口必須爲 main 函數
func main() {
}

有個必須注意的事情是函數init()會在每一個包被導入以前執行,若是導入了多個包,那麼會根據包導入的順序前後執行init(),再回到執行函數main()

3、變量

Golang語言能夠先聲明變量,再賦值,也能夠直接建立一個帶值的變量。如:

// 聲明並初始化三個值
var i, j, k = 1, 2, 3

// 聲明後再賦值
var i int64
i = 3

// 直接賦值,建立一個新的變量
j := 5

能夠看到var i int64,數據類型是在變量的後面而不是前面,這是Golang語言與其餘語言最大的區別之一。

同時,做爲一門靜態語言,Golang在編譯前還會檢查哪些變量和包未被引用,強制禁止遊離的變量和包,從而避免某些人類低級錯誤。如:

package main

func main(){
    a := 2
}

若是執行將會報錯:

go run main.go

./main.go:26:2: cannot declared and not used

提示聲明變量未使用,這是Golang語言與其餘語言最大的區別之一。

變量定義後,若是沒有賦值,那麼存在默認值。咱們也能夠定義常量,只需加關鍵字const,如:

const s  = 2

常量一旦定義就不能修改。

4、基本數據類型

咱們再來看看基本的數據類型有那些:

// 定義基本數據類型
    p := true                             // bool
    a := 3                                // int
    b := 6.0                              // float64
    c := "hi"                             // string
    d := [3]string{"1", "2", "3"}         // array,基本不用到
    e := []int64{1, 2, 3}                 // slice
    f := map[string]int64{"a": 3, "b": 4} // map
    fmt.Printf("type:%T:%v\n", p, p)
    fmt.Printf("type:%T:%v\n", a, a)
    fmt.Printf("type:%T:%v\n", b, b)
    fmt.Printf("type:%T:%v\n", c, c)
    fmt.Printf("type:%T:%v\n", d, d)
    fmt.Printf("type:%T:%v\n", e, e)
    fmt.Printf("type:%T:%v\n", f, f)

輸出:

type:bool:true
type:int:3
type:float64:6
type:string:hi
type:[3]string:[1 2 3]
type:[]int64:[1 2 3]
type:map[string]int64:map[a:3 b:4]

數據類型基本有整數,浮點數,字符串,布爾值,數組,切片(slice) 和 字典(map) 。

  1. 布爾值:bool
  2. 整數:int(默認類型,通常視操做系統位數=int32或int64),int32int64
  3. 浮點數:float32float64(默認類型,更大的精度)
  4. 字符:string
  5. 數組,切片(可變長數組),字典(鍵值對結構)。

沒聲明具體變量類型的時候,會自動識別類型,把整數認爲是int類型,把帶小數點的認爲是float64類型,如:

a := 3                                // int
    b := 6.0                              // float64

因此當你須要使用確切的int64float32類型時,你須要這麼作:

var a int64 = 3
    var b float32 = 6.0

Golang有數組類型的提供,可是通常不使用,由於數組不可變長,當你把數組大小定義好了,就再也沒法變動大小。因此Golang語言造出了可變長數組:切片(slice),將數組的容量大小去掉就變成了切片。切片,能夠像切東西同樣。自動調整大小,能夠切一部分,或者把兩部分拼起來。

d := [3]string{"1", "2", "3"}         // array,基本不用到
    e := []int64{1, 2, 3}                 // slice

切片能夠像數組同樣按下標取值,放值,也能夠追加值:

// 切片放值
    e[0] = 9
    // 切片增長值
    e = append(e, 3)

切片追加一個值3進去須要使用append關鍵字,而後將結果再賦給本身自己,這是Golang語言與其餘語言最大的區別之一,實際切片底層有個固定大小的數組,當數組容量不夠時會生成一個新的更大的數組。

同時,由於平常開發中,咱們常常將兩個數據進行映射,相似於查字典同樣,先查字母,再翻頁。因此字典map開發使用頻率極高,因此Golang自動提供了這一數據類型,這是Golang語言與其餘語言最大的區別之一。

字典存儲了一對對的鍵值:

// 增長map鍵值
    f["f"] = 5

    // 查找map鍵值
    v, ok := f["f"]
    fmt.Println(v, ok)
    v, ok = f["ff"]
    fmt.Println(v, ok)

結構如map[string]int64表示鍵爲字符串string,值爲整數int64,而後你能夠將f = 5這種關係進行綁定,須要時能夠拿出鍵f對應的值。

5、slice 和 map 的特殊說明

鍵值結構字典:map使用前必須初始化,如:

m := map[string]int64{}
     m1 = make(map[string]int64)

若是不對字典進行初始化,做爲引用類型,它是一個nil空引用,你使用空引用,往字典裏添加鍵值對,將會報錯。

而切片結構slice不須要初始化,由於添加值時是使用append操做,內部會自動初始化,如:

var ll []int64
    fmt.Printf("%#v\n", ll)
    ll = append(ll, 1)
    fmt.Printf("%#v\n", ll)

打印:

[]int64(nil)
[]int64{1}

同時切片有如下特徵:

ll = append(ll, 2, 3, 4, 5, 6)
    fmt.Printf("%#v\n", ll)
    ll = append(ll, []int64{7, 8, 9}...)
    fmt.Printf("%#v\n", ll)

    fmt.Println(ll[0:2])
    fmt.Println(ll[:2])
    fmt.Println(ll[0:])
    fmt.Println(ll[:])

內置語法append能夠傳入多個值,將多個值追加進切片。而且能夠將另一個切片,如[]int64{7, 8, 9}...,用三個點表示遍歷出裏面的值,把一個切片中的值追加進另一個切片。

在切片後面加三個點...表示虛擬的建立若干變量,將切片裏面的值賦予這些變量,再將變量傳入函數。

咱們取切片的值,除了能夠經過下標取一個值,也能夠取範圍:[下標起始:下標截止(不包括取該下標的值)],如[0:2],表示取出下標爲0和1的值,總共有兩個值,再好比[0:4],表示取出下標爲0,1,2,3的值。若是下標取值,下標超出實際容量,將會報錯。

若是下標起始等於下標0,那麼能夠省略,如[:2],若是下標截止省略,如[2:]表示從下標2開始,取後面全部的值。這個表示[:]自己沒有做用,它就表示切片自己。

6、函數

咱們能夠把常用的代碼片斷封裝成一個函數,方便複用:

// 函數,兩個數相加
func sum(a, b int64) int64 {
    return a + b
}

Golang定義函數使用的關鍵字是func,後面帶着函數名sum(a, b int64) int64,表示函數sum傳入兩個int64整數ab,輸出值也是一個int64整數。

使用時:

// 定義 int64 變量
    var h, i int64 = 4, 6

    // 使用函數
    sum := sum(h, i)
    fmt.Printf("sum(h+i),h=%v,i=%v,%v\n", h, i, sum)

輸出:

sum(h+i),h=4,i=6,10

將函數外的變量hi傳入函數sum做爲參數,是一個值拷貝的過程,會拷貝hi的數據到參數ab,這兩個變量是函數sum內的局部變量,兩個變量相加後返回求和結果。

就算函數裏面改了局部變量的值,函數外的變量仍是不變的,如:

package main

import "fmt"

func changeTwo(a, b int) {
    a = 6
    b = 8
}

func main() {
    a, b := 1, 2
    fmt.Println(a, b)
    changeTwo(a, b)
    fmt.Println(a, b)
}

輸出:

1 2
1 2

變量是有做用域的,做用域主要被約束在各級大括號{}裏面,因此函數裏面的變量和函數體外的變量是沒有關係的,互相獨立。

咱們還能夠實現匿名的函數如:

input := 2

    output := func(num int) int {
        num = num * 2
        return num
    }(input)

    fmt.Println(output)

打印出:

4

原本函數在外部是這樣的:

func A(num int) int {
        num = num * 2
        return num
    }

如今省略了函數名,定義後直接使用:

output := func(num int) int {
        num = num * 2
        return num
    }(input)

input是匿名函數的輸入參數,匿名函數返回的值會賦予output

系列文章入口

我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook

相關文章
相關標籤/搜索