zoukankan      html  css  js  c++  java
  • golang快速入门

    1.打包和工具链

    1.1 包

    所有 Go 语言的程序都会组织成若干组文件,每组文件被称为一个包。
    net/http/
        cgi/
        cookiejar/
            testdata/
        fcgi/
        httptest/
        httputil/
        pprof/
        testdata/
    在 http 目录下的所有文件都属于 http 包
    
    所有的.go 文件,除了空行和注释,都应该在第一行声明自己所属的包。每个包都在一个单 独的目录里。不能把多个包放到同一个目录中,也不能把同一个包的文件分拆到多个不同目录中。 这意味着,同一个目录下的所有.go 文件必须声明同一个包名。
     
    在 Go 语言里,命名为 main 的包具有特殊的含义。所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包。

    1.2 导入

    import (
        "fmt"
        "strings")
    编译器先查找$GOROOT下的包,然后查找$GOPATH下的包
    1.推荐所有自定义包不放在$GOROOT下,该目录下一般是标准库,自定义包放在$GOPATH下,如果升级go版本,直接替换/usr/local/go相关文件,而不用重新替换自定义包
    2.go语言中字符串使用双引号"",注意与Python习惯区别
    
    go支持远程导入,如果import内的包名是一个地址,如"github.com/spf13/viper",在go run之前使用go get命令,程序会下载对应的包的$GOPATH的包目录下(该功能需要git支持)
    
    命名导入和未使用包标注
    import {
        myfmt "mylib/fmt"    //重命名包名,在有多个包重名的情况下使用,同Python中的import package as pkg
        _ "fmt"   //go语言不允许导入包而不使用,所以用下划线标注没有使用的包,实际不会导入
    }
    

    1.3 go工具介绍

    • go build file.go         //编译文件
    • go clean file.go        //删除编译生成的可执行文件
    • go vet file.go           //检查常见错误Printf类型匹配错误的参数,定义函数时方法签名错误,错误结构变量等
    • go fmt file.go        //自动整理文件格式,对齐
    • go doc pkg              //在终端查看包相关的文档
    • godoc -http=:80     //启动一个go文档web服务器,如果开发人员按照godoc规则写代码,能自动包含在文档中

    2.数组、切片和映射

    2.1 数组
    // 声明一个包含5个元素的整型数组
    // 一旦声明,数组里存储的数据类型和数组长度就都不能改变
    var array [5]int                //数组默认值为0
    array := [5]int{10, 20, 30, 40, 50}
    array := [...]int{10, 20, 30, 40, 50}
    array := [5]int{1: 10, 2: 20}                  //指定索引为1元素为10,索引为2元素为20
    
    // 声明包含 5 个元素的指向整数的数组
    // 用整型指针初始化索引为 0 和 1 的数组元素
    array := [5]*int{0: new(int), 1: new(int)}
    *array[0] = 10    // 为索引为0的元素赋值
    
    var array_ [5]int
    array_ = array
    // 数组变量的类型包括数组长度和每个元素的类型。只有这两部分都相同的数组,才是类型相 同的数组,才能互相赋值
    
    // 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
    var array [4][2]int
    
    // 使用数组字面量来声明并初始化一个二维整型数组
    array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
    
    // 声明并初始化外层数组中索引为 1 个和 3 的元素
    array := [4][2]int{1: {20, 21}, 3: {40, 41}}
    
    // 声明并初始化外层数组和内层数组的单个元素
    array := [4][2]int{1: {0: 20}, 3: {1: 41}}
    
    // 声明一个需要 8 MB 的数组
    var array [1e6]int
    // 将数组传递给函数
    foo foo(array)
    //函数 foo 接受一个 100 万个整型值的数组
    func foo(array [1e6]int)
    { ... }
    每次函数 foo 被调用时,必须在栈上分配 8 MB 的内存。之后,整个数组的值(8 MB 的内 存)被复制到刚分配的内存里。虽然 Go 语言自己会处理这个复制操作,不过还有一种更好且更 有效的方法来处理这个操作。可以只传入指向数组的指针,这样只需要复制 8 字节的数据而不是 8 MB 的内存数据到栈上,优化如下
    // 分配一个需要 8 MB 的数组
    var array [1e6]int
    // 将数组的地址传递给函数
    foo foo(&array)
    // 函数 foo 接受一个指向 100 万个整型值的数组的指针
    func foo(array *[1e6]int)
    { ... }
    这个操作会更有效地利用内存,性能也更好。不过要意识到,因为现在传递的是指针, 所以如果改变指针指向的值,会改变共享的内存。使用切片能更好地处理这类共 享问题。
    

    2.2 切片

    切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法
    切片有三个字段的数据结构:指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长 到的元素个数(即容量)
    // 创建一个字符串切片
    // 其长度和容量都是 5 个元素
    slice := make([]string, 5)
    
    // 创建一个整型切片
    // 其长度为 3 个元素,容量为 5 个元素,容量小于长度的切片会编译出错
    slice := make([]int, 3, 5)
    slice := []int{10, 20, 30}
    // 使用空字符串初始化第 100 个元素
    slice := []string{99: ""}
    
    // 创建 nil 整型切片
    var slice []int
    slice := make([]int, 0)
    slice := []int{}
    
    如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值 的时候,才会创建切片
    

    切片的使用

    // 其长度和容量都是 5 个元素
    slice := []int{10, 20, 30, 40, 50}
    
    // 其长度为 2 个元素,容量为 4 个元素,该切片不能看见底层数组第0号元素
    newSlice := slice[1:3]
    
    // 使用原有的容量来分配一个新元素
    // 将新元素赋值为 60
    newSlice = append(newSlice, 60)
    
    因为 newSlice 在底层数组里还有额外的容量可用,append 操作将可用的元素合并到切片 的长度,并对其进行赋值。由于和原始的 slice 共享同一个底层数组,slice 中索引为 3 的元 素的值也被改动了
    
    如果切片的底层数组没有足够的可用容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值
    
    综上:创建切片的时候尽量使长度和容量一致,如果增加append新值是新开一个底层数组,而不是直接修改
    
    e.g:
        source := []int{0, 1, 2, 3}
        slice1 := source[0:3]
        fmt.Println(slice1)
        // [0 1 2]
        slice2 := append(slice1, 100)
        slice2[0] = 99
        fmt.Println(slice1)
        fmt.Println(slice2)
        // [99 1 2]
        // [99 1 2 100]
    
    迭代切片
    slice := []int{10, 20, 30, 40}
    // 迭代每一个元素,并显示其值
    for index, value := range slice {
        fmt.Printf("Index: %d Value: %d
    ", index, value)
    }
    如果需要忽略index值,使用下划线占位
    当迭代切片时,关键字 range 会返回两个值。第一个值是当前迭代到的索引位置,第二个 值是该位置对应元素值的一份副本
    slice := []int{10, 20, 30, 40}
    for index, value := range slice {
        // 输出值和地址
        fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X
    ",
            value, &value, &slice[index])
    }
    
    //第二种迭代方式
    for index := 0; index < len(slice); index++ {
        fmt.Printf("Index: %d Value: %d
    ", index, slice[index])
    }
    关键字 range 总是会从切片头部开始迭代。如果想对迭代做更多的控制,依旧可以使用传 统的 for 循环
    

    多维切片

    // 创建一个整型切片的切片
    slice := [][]int{{10}, {100, 200}}
    // 为第一个切片追加值为 20 的元素
    slice[0] = append(slice[0], 20
    

    切片属于引用类型,在函数间传递开销很小

    举例理解切片和底层数组

    // 长度为3, 容量为5
    slice1 := make([]int, 3, 5)   // 修改5为3,创建一个长度与容量一致的切片
    // 切片所有默认值都是0
    slice1[1] = 1
    slice1[2] = 2
    // 切片2与1共享一个底层数组
    slice2 := slice1[0:3]
    // 在切片2上面增加一个数据
    slice3 := append(slice2, 200)
    // 在切片1上面增加一个数据
    slice4 := append(slice1, 100)
    fmt.Println(slice1) // [0, 1, 2]
    fmt.Println(slice2) // [0, 1, 2]
    fmt.Println(slice3) // [0, 1, 2, 100]
    fmt.Println(slice4) // [0, 1, 2, 100]
    
    再次说明:内置函数 append 会首先使用可用容量。一旦没有可用容量,会分配一个 新的底层数组。这导致很容易忘记切片间正在共享同一个底层数组。一旦发生这种情况,对切片 进行修改,很可能会导致随机且奇怪的问题。对切片内容的修改会影响多个切片,却很难找到问 题的原因。
    2.3 映射

    映射是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。但映射是无序的 集合,意味着没有办法预测键值对被返回的顺序。即便使用同样的顺序保存键值对,每次迭代映 射的时候顺序也可能不一样。

    // 创建一个映射,键的类型是 string,值的类型是
    int dict := make(map[string]int)
    // 创建一个映射,键和值的类型都是 string
    // 使用两个键值对初始化映射
    dict := map[string]string{"a": "1", "b": "2"}
    // 重新赋值
    dict["a"]="3"
    // 删除
    delete(dict, "a")
    
    // 判断key是否存在
    value, exists := dict["a"]
    if exists{
        fmt.Println(value)
    }
    
    // 使用ranged迭代
    for key, value := range dict{
        fmt.Printf("Key: %s Value: %s
    ", key, value)
    }
    
    映射的键可以是任何值。这个值的类型可以是内置的类型,也可以是结构类型,只要这个值 可以使用==运算符做比较。
    切片、函数以及包含切片的结构类型这些类型由于具有引用语义, 不能作为映射的键,使用这些类型会造成编译错误
    e.g1:
    dict := map[[]string]int{}
    fmt.Println(dict)
    // # command-line-arguments
    // ./main.go:9:10: invalid map key type []string
    
    dict := map[[3]string]int{}
    // map[]
    
    e.g2:
    func main() {
        // 创建一个映射, 字母与对应的10进制ascii码
        dict := map[string]int{"a": 97, "b": 98, "c": 99}
        for key, value := range dict {
            fmt.Printf("key: %s Value: %s
    ", key, value)
        }
        removeDict(dict, "b")
        fmt.Println(dict)
    }
    func removeDict(OneDict map[string]int, key string) {
        delete(OneDict, key)
    }
    1.Print以ln结尾的是直接输出,类似Python的print,可以输出各种类型(包括自定义结构体)变量并换行,一行输出多个值使用逗号隔开;以f结尾的是结构化输出,类似c语言中的printf
    2.在函数间传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对 这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改。这个特性和切片类似,保证可以用很小的成本来复制映射
    

    3.GO语言的类型系统

    // user 在程序里定义一个用户类型
    // 属性的类型也可以是用户自定义类型,用法类似c语言结构体struct
    type user struct {
        name       string
        email      string
        ext        int
        privileged bool
    }
    // 声明 user 类型的变量
    var bill user
    // 声明变量并赋值
    lisa := user{
            name:       "Lisa",
            email:      "lisa@email.com",
            ext:        123,
            privileged: true,}
    // 顺序必须与声明一致
    lisa := user{"Lisa", "lisa@email.com", 123, true}
    
    // 声明一个新类型
    type Duration int64
    // int64 类型叫作 Duration 的基础类型。不过,虽然 int64 是基础 类型,Go 并不认为 Duration 和 int64 是同一种类型。这两个类型是完全不同的有区别的 类型。
    
    e.g1:
    var dur Duration
    dur = int64(1000)
    ./main.go:12:6: cannot use int64(1000) (type int64) as type Duration in assignment
    

    e.g2:

    // 这个示例程序展示如何声明,并使用方法
    package main
    import (
        "fmt"
    )
    // user 在程序里定义一个用户类型
    type user struct {
        name  string
        email string
    }
    // notify 使用值接收者实现了一个方法
    func (u user) notify() {
        fmt.Printf("Sending User Email To %s<%s>
    ",
            u.name,
            u.email)
    }
    // changeEmail 使用指针接收者实现了一个方法
    // 这个方法使用指针接收者声明。这个接收者的类型是指向 user 类型值的指针,而不是 user 类型的值。当调用使用指针接收者声明的方法时,这个方法会共享调用方法时接收者所指向的值
    func (u *user) changeEmail(email string) {
        u.email = email
    }
    // main 是应用程序的入口
    func main() {
        // user 类型的值可以用来调用
        // 使用值接收者声明的方法
        bill := user{"Bill", "bill@email.com"}
        bill.notify()
        // 指向 user 类型值的指针也可以用来调用
        // 使用值接收者声明的方法
        lisa := &user{"Lisa", "lisa@email.com"}
        lisa.notify()
        //指针被解引用为值, 这样就符合了值接收者的要求。notify 操作的是一个副本,只不过这次操作的是 从 lisa 指针指向的值的副本。
        // (*lisa).notify()
    
        // user 类型的值可以用来调用
        // 使用指针接收者声明的方法
        bill.changeEmail("bill@newdomain.com")
        bill.notify()
        //变量 bill,以及之后使用这个变量调用使用指针接收者声明的 changeEmail 方法。Go 语言再一次对值做了调整,使之符合函数的接收者,进行调用
        // (&bill).changeEmail ("bill@newdomain.com")
    
        // 指向 user 类型值的指针可以用来调用
        // 使用指针接收者声明的方法
        lisa.changeEmail("lisa@newdomain.com")
        lisa.notify()
    }
    // Go 语言既允许使用值,也允许使用指针来调用方法,不必严格符合接收者的类型。
    

      

  • 相关阅读:
    Hibernate学习笔记
    Servlet:从入门到实战学习(3)---Servlet实例【图文】
    Servlet:从入门到实战学习(2)---Servlet生命周期
    Servlet:从入门到实战学习(1)---全·环境配置
    java复习(9)---数据库JDBC
    java复习(8)---I/O
    java复习(7)---集合类、泛型
    java复习(6)---异常处理
    C#尝试读取或写入受保护的内存。这通常指示其他内存已损坏
    C# TTS 文本转语音中断实现方式
  • 原文地址:https://www.cnblogs.com/newguy/p/8615119.html
Copyright © 2011-2022 走看看