zoukankan      html  css  js  c++  java
  • 数据结构和算法(Golang实现)(2)简单入门Golang-包、变量和函数

    包、变量和函数

    一、举个例子

    现在我们来建立一个完整的程序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
    ", p, p)
        fmt.Printf("type:%T:%v
    ", a, a)
        fmt.Printf("type:%T:%v
    ", b, b)
        fmt.Printf("type:%T:%v
    ", c, c)
        fmt.Printf("type:%T:%v
    ", d, d)
        fmt.Printf("type:%T:%v
    ", e, e)
        fmt.Printf("type:%T:%v
    ", 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
    ", 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
    ", h, i, sum)
    
        // 新建结构体,值
        g := diy.Diy{
            A: 2,
            //b: 4.0, // 小写成员不能导出
        }
    
        // 打印类型,值
        fmt.Printf("type:%T:%v
    ", g, g)
    
        // 小写方法不能导出
        //g.set(1,1)
        g.Set(1, 1)
        fmt.Printf("type:%T:%v
    ", g, g) // 结构体值变化
    
        g.Set2(3, 3)
        fmt.Printf("type:%T:%v
    ", g, g) // 结构体值未变化
    
        // 新建结构体,引用
        k := &diy.Diy{
            A: 2,
        }
        fmt.Printf("type:%T:%v
    ", k, k)
        k.Set(1, 1)
        fmt.Printf("type:%T:%v
    ", k, k) // 结构体值变化
        k.Set2(3, 3)
        fmt.Printf("type:%T:%v
    ", k, k) // 结构体值未变化
    
        // 新建结构体,引用
        m := new(diy.Diy)
        m.A = 2
        fmt.Printf("type:%T:%v
    ", 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
    ", s, cap(s), len(s))
        fmt.Printf("%#v,cap:%#v,len:%#v
    ", s1, cap(s1), len(s1))
        fmt.Printf("%#v,len:%#v
    ", m1, len(m1))
        fmt.Printf("%#v,len:%#v
    ", m2, len(m2))
    
        var ll []int64
        fmt.Printf("%#v
    ", ll)
        ll = append(ll, 1)
        fmt.Printf("%#v
    ", ll)
        ll = append(ll, 2, 3, 4, 5, 6)
        fmt.Printf("%#v
    ", ll)
        ll = append(ll, []int64{7, 8, 9}...)
        fmt.Printf("%#v
    ", ll)
    
        fmt.Println(ll[0:2])
        fmt.Println(ll[:2])
        fmt.Println(ll[0:])
        fmt.Println(ll[:])
    }
    
    

    在相同目录下新建diy文件夹,文件下新建一个diy.go文件(名字任取):

    // 包名
    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语言只有小括号和大括号,不需要使用逗号来分隔代码,只有一种循环for

    接下来我们会分析这个例子。

    二、工程管理:包机制

    每一个大型的软件工程项目,都需要进行工程管理。工程管理的一个环节就是代码层次的管理。

    包,也称为库,如代码的一个包,代码的一个库,英文:Library或者Package。比如,我们常常听到某程序员说:嘿,X哥,我知道Github上有一个更好用的数据加密库,几千颗星呢。

    在高级编程语言层次,也就是代码本身,各种语言发明了包(package)机制来更好的管理代码,将代码按功能分类归属于不同的包。

    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()

    三、变量

    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
    
    

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

    四、基本数据类型

    我们再来看看基本的数据类型有那些:

        // 定义基本数据类型
        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
    ", p, p)
        fmt.Printf("type:%T:%v
    ", a, a)
        fmt.Printf("type:%T:%v
    ", b, b)
        fmt.Printf("type:%T:%v
    ", c, c)
        fmt.Printf("type:%T:%v
    ", d, d)
        fmt.Printf("type:%T:%v
    ", e, e)
        fmt.Printf("type:%T:%v
    ", 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对应的值。

    五、slice 和 map 的特殊说明

    键值结构字典:map使用前必须初始化,如:

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

    如果不对字典进行初始化,作为引用类型,它是一个nil空引用,你使用空引用,往字典里添加键值对,将会报错。

    而切片结构slice不需要初始化,因为添加值时是使用append操作,内部会自动初始化,如:

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

    打印:

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

    同时切片有以下特征:

        ll = append(ll, 2, 3, 4, 5, 6)
        fmt.Printf("%#v
    ", ll)
        ll = append(ll, []int64{7, 8, 9}...)
        fmt.Printf("%#v
    ", 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开始,取后面所有的值。这个表示[:]本身没有作用,它就表示切片本身。

    六、函数

    我们可以把经常使用的代码片段封装成一个函数,方便复用:

    // 函数,两个数相加
    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
    ", 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

  • 相关阅读:
    CF 1119 题解
    CF 582 题解
    CF 1098 题解
    CF 1129 题解
    CF 513 题解
    CF 417 D 题解
    ingress nginx遇到502错误,connect() failed (113 Host is unreachable) while connecting to upstream
    MySQL性能剖析
    MySQL的基准测试
    MySQL架构与历史
  • 原文地址:https://www.cnblogs.com/nima/p/12724766.html
Copyright © 2011-2022 走看看