美文网首页
半天时间 Go 语言的基本实践

半天时间 Go 语言的基本实践

作者: 专职跑龙套 | 来源:发表于2019-03-04 05:15 被阅读71次

    上班两个月,写了 C++,写了 Pathon,最近又要开始写 Go,唯独没有用我最擅长的 Java。。。

    Go 开发环境的安装

    下载地址:https://golang.org/dl/,选择对应的操作系统。
    例如 Mac 系统选择 go1.12.darwin-amd64.pkg,安装完成后添加到环境变量:

    export PATH=$PATH:/usr/local/go/bin
    

    通过执行 go version 来确定安装是否完成:

    go version

    Go HelloWorld

    创建 go_hello_world.go

    package main
    
    import "fmt"
    
    func main() {
       /* Go HelloWorld */
       fmt.Println("Hello, World!")
    }
    

    通过执行 go run go_hello_world.go 来运行:

    go run go_hello_world.go

    分析上面的代码:

    • package main包声明,指明这个文件属于哪个包。package main 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
    • import "fmt"引入包声明。告诉 Go 编译器这个程序需要使用 fmt 包,fmt 包实现了格式化 IO(输入/输出)的函数。
    • func main() {函数main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
    • 可以使用 /.../ 或者 /*...*/ 来定义注释。
    • 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Println,那么使用这种形式的标识符的对象就可以被外部包的代码所使用,这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
    • 需要注意的是 { 不能单独放在一行。

    Go 基础语法

    Go 标记
    Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。

    行分隔符
    在 Go 程序中,一行代表一个语句结束。
    每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾。

    标识符
    标识符用来命名变量、类型、函数名、结构字段等程序实体。
    一个标识符实际上就是一个或是多个字母、数字、下划线组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

    关键字
    具体参见:https://golang.org/ref/spec#Keywords

    Go 数据类型

    • 布尔型
      • true false
    • 数字类型
      • uint8 uint16 uint32 uint64
      • int8 int16 int32 int64
      • float32 float64 complex64 complex128
      • byte rune uint int uintptr
    • 字符串类型
      • Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。。
    • 派生类型
      • 指针类型(Pointer)
      • 数组类型
      • 结构化类型(struct)
      • Channel 类型
      • 函数类型
      • 切片类型
      • 接口类型(interface)
      • Map 类型

    Go 变量

    声明变量的一般形式是使用 var 关键字。
    第一种,指定变量类型,声明后若不赋值,使用默认值。

    var i int
    i = 1
    

    第二种,根据值自行判定变量类型。

    var i = 1
    

    第三种,省略 var,使用 :=将由编译器自动推断类型,只能在函数体中出现。

    i := 1
    

    值类型和引用类型:

    • 所有像 intfloatboolstring 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值。例如:
    func main() {
        var i = 1
        var j = i
        fmt.Println(&i) // 取地址
        fmt.Println(&i, " != ", &j) // 地址不同
    }
    
    • 一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。例如:
    
    

    并行赋值:
    例如:

    func main() {
        var a, b int
        var c string
        a, b, c = 1, 2, "abc"
        // 或者 a, b, c := 1, 2, "abc"
        fmt.Println(a, b, c);
    }
    
    • 如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同
    • 并行赋值的一个作用:比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(...)
    • 空白标识符 _ 也被用于抛弃值,如值 1:_, i = 1, 2 中被抛弃。
      • _ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
      • 例如我们可能不需要使用函数范围的错误 err,那么可以使用 val, _ = Func1(...)

    Go 常量

    定义格式:
    const identifier [type] = value。可以省略类型 [type],因为编译器可以根据变量的值来推断其类型。

    func main() {
        const i int = 1
        const j = "abc"
        
        fmt.Println(i, j);
    }
    

    Go 运算符

    跟 Java 差不多,具体参见:

    func main() {
        var i int = 1
        var p *int
        
        p = &i
        
        fmt.Println(i, *p, p); // 1 1 0xc210000018
    }
    

    Go 条件语句

    跟 Java 差不多。

    func main() {
        var i int = 1
        
        if i > 0 { // 注意不要用括号 if (i > 0)
            fmt.Println("i > 0")
        } else { // 注意 else 不能换行
            fmt.Println("i <= 0")
        }
        
        var j int = 2
        switch j { // 注意不要用括号 switch (j)
            case 1:
                fmt.Println("one")
            case 2:
                fmt.Println("two")
            case 3:
                fmt.Println("three")
        }
    }
    

    Go 循环语句

    跟 Java 差不多。

    func main() {
        for n := 0; n <= 5; n++ {  // 注意不要用括号 for (n := 0; n <= 5; n++)
            if n%2 == 0 {
                continue
            }
            fmt.Println(n)
        }
    }
    

    Go 函数

    Go 语言最少有个 main() 函数。
    函数定义:

    func function_name( [parameter list] ) [return_types] {
       函数体
    }
    

    示例:

    // 一个返回值
    func f1(i int, j int) int {
        return i + j
    }
    
    // 多个返回值
    func f2(i int, j int) (int, int) {
        return i + j, i - j
    }
    
    func main() {
        var m = f1(1, 2)
        fmt.Println(m) // 3
        
        var p, q = f2(1, 2)
        fmt.Println(p, q) //3 -1
    }
    

    Go 变量作用域

    变量可以在三个地方声明:

    • 函数内定义的变量称为局部变量

      • 它们的作用域只在函数体内,参数和返回值变量也是局部变量。
    • 函数外定义的变量称为全局变量

      • 全局变量可以在任何函数中使用。
      • 全局变量可以在整个包甚至外部包(被导出后)使用。
      • Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
    var g = 1
    func main() {
        fmt.Println(g)
    }
    
    • 函数定义中的变量称为形式参数
      • 形式参数会作为函数的局部变量来使用。

    不同类型的局部和全局变量默认值为:

    • int - 0
    • float32 - 0
    • pointer - nil

    Go 数组

    数组定义:

    var variable_name [SIZE] variable_type
    

    示例:

    func main() {
        var a [5] int
        fmt.Println(a) // [0 0 0 0 0]
        
        a[4] = 100
        fmt.Println(a[4]) // 100
        fmt.Println(a) // [0 0 0 0 100]
        
        b := [5] int {1, 2, 3, 4, 5}
        fmt.Println(b) // [1 2 3 4 5]
        
        var twoD [2][3] int
        for i := 0; i < 2; i++ {
            for j := 0; j < 3; j++ {
                twoD[i][j] = i + j
            }
        }
        
        fmt.Println(twoD) // [[0 1 2] [1 2 3]]
    }
    

    Go 指针

    指针定义:

    var var_name *var-type
    

    示例:

    func zeroval(ival int) {
        ival = 0
    }
    
    func zeroptr(iptr *int) {
        *iptr = 0
    }
    
    func main() {
        i := 1
        fmt.Println("initial:", i) // 1
        
        zeroval(i)
        fmt.Println("zeroval:", i) // 1
        
        zeroptr(&i)
        fmt.Println("zeroptr:", i) // 0
        
        fmt.Println("pointer:", &i) // 0xc210000018
    }
    

    当一个指针被定义后没有分配到任何变量时,它的值为 nil
    nil 指针也称为空指针。

    Go 结构体

    定义结构体:

    type struct_variable_type struct {
       member definition;
       member definition;
       ...
       member definition;
    }
    
    • 使用结构体进行变量的声明和定义:
    variable_name := structure_variable_type {value1, value2...valuen}
    variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
    
    • 如果要访问结构体成员,需要使用点号 . 操作符
    • 可以定义指向结构体的指针

    示例:

    type person struct {
        name string
        age  int
    }
    func main() {
        fmt.Println(person{"Bob", 20}) // {Bob 20}
        
        fmt.Println(person{name: "Alice", age: 30}) // {Alice 30}
        
        fmt.Println(person{name: "Fred"}) // {Fred 0}
        
        fmt.Println(&person{name: "Ann", age: 40}) // &{Ann 40}
        
        s := person{name: "Sean", age: 50}
        fmt.Println(s.name) // Sean
        
        sp := &s
        fmt.Println(sp.age) // 50
        
        sp.age = 51
        fmt.Println(sp.age) // 51
    }
    

    Go 切片(Slice)

    Go 语言切片是对数组的抽象。
    Go 数组的长度不可改变,在特定场景中这样的集合就不太适用。
    Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

    可以声明一个未指定大小的数组来定义切片:

    var identifier [] type
    

    使用 make() 函数来创建指定 初始长度 len 的切片:

    var slice1 [] type = make([]type, len)
    
    slice2 := make([]type, len)
    
    var slice3 [] type = make([]type, len, capacity) // capacity 为容量,为可选参数
    

    切片初始化:

    func main() {
        // 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3。其 cap=len=3
        s1 :=[] int {1,2,3} 
        fmt.Println(s1) // [1 2 3]
        
        // 定义一个数组
        arr := [10] int {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
        // 初始化切片,是数组 arr 的引用
        s2 := arr[:]
        fmt.Println(s2) // [1 2 3 4 5 6 7 8 9 10]
        
        // 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片
        s3 := arr[1 : 5]
        fmt.Println(s3) // [2 3 4 5]
        
        // 缺省 endIndex 时将表示一直到 arr 的最后一个元素
        s4 := arr[1 : ]
        fmt.Println(s4) // [2 3 4 5 6 7 8 9 10]
        
        // 缺省 startIndex 时将表示从 arr 的第一个元素开始
        s5 := arr[ : 5]
        fmt.Println(s5) // [1 2 3 4 5]
    }
    
    • 切片是可索引的,并且可以由 len() 方法获取长度。
    • 切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
    • 一个切片在未初始化之前默认为 nil,长度为 0
    • 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来,使用 append()copy() 方法。示例:
    func main() {
        var numbers [] int
        fmt.Println(numbers) // []
        
        numbers = append(numbers, 1)
        fmt.Println(numbers) // [1]
        
        numbers = append(numbers, 2, 3, 4)
        fmt.Println(numbers) // [1 2 3 4]
        
        // 创建切片 numbers1 是之前切片的两倍容量
        numbers1 := make([]int, len(numbers), (cap(numbers))*2)
        copy(numbers1, numbers)
        fmt.Println(numbers1) // [1 2 3 4]
    }
    

    Go 范围(Range)

    Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。
    示例:

    func main() {
        // 定义一个数组
        nums := [] int{2, 3, 4}
        sum := 0
        
        // 使用 range 将传入 index 和值两个变量
        for i, num := range nums {
            fmt.Println("index = ", i, "value = ", num)
        }
        
        // 求和,使用空白标识符"_"来忽略索引
        for _, num := range nums {
            sum += num
        }
        fmt.Println("sum:", sum) // 9
        
        // 用在 map 的键值对上。
        kvs := map[string]string{"a": "apple", "b": "banana"}
        for k, v := range kvs {
            fmt.Printf("%s -> %s\n", k, v)
        }
        
        // 用来枚举 Unicode 字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
        for i, c := range "go" {
            fmt.Println(i, c)
        }
    }
    

    Go Map(集合)

    Map 的定义:

    // 声明变量,默认 map 是 nil
    var map_variable map[key_data_type]value_data_type
    
    // 使用 make 函数
    map_variable := make(map[key_data_type]value_data_type)
    

    示例:

    func main() {
        m := make(map[string]int)
        
        m["k1"] = 7
        m["k2"] = 13
        
        fmt.Println("map:", m) // map[k1:7 k2:13]
        
        v1 := m["k1"]
        fmt.Println("v1: ", v1) // v1:  7
        
        fmt.Println("len:", len(m)) // len: 2
        
        delete(m, "k2")
        fmt.Println("map:", m) // map: map[k1:7]
        
        _, prs := m["k2"]
        fmt.Println("prs:", prs) // prs: false
    }
    

    Go 类型转换

    类型转换:type_name(expression)
    示例:

    func main() {
        var sum int = 17
        var count int = 5
        var mean float32
        mean = float32(sum)/float32(count)
        fmt.Printf("mean 的值为: %f\n", mean) // 3.400000
    }
    

    Go 接口

    接口的定义:

    type interface_name interface {
       method_name1 [return_type]
       method_name2 [return_type]
       method_name3 [return_type]
       ...
       method_namen [return_type]
    }
    

    示例:

    import "fmt"
    import "math"
    
    type geometry interface {
        area() float64
        perim() float64
    }
    
    type rect struct {
        width, height float64
    }
    type circle struct {
        radius float64
    }
    
    func (r rect) area() float64 {
        return r.width * r.height
    }
    func (r rect) perim() float64 {
        return 2*r.width + 2*r.height
    }
    
    func (c circle) area() float64 {
        return math.Pi * c.radius * c.radius
    }
    func (c circle) perim() float64 {
        return 2 * math.Pi * c.radius
    }
    
    func measure(g geometry) {
        fmt.Println(g)
        fmt.Println(g.area())
        fmt.Println(g.perim())
    }
    
    func main() {
        r := rect{width: 3, height: 4}
        c := circle{radius: 5}
        
        measure(r)
        measure(c)
    }
    

    Go 错误处理

    通过内置的错误接口 error,定义:

    type error interface {
        Error() string
    }
    

    我们可以在编码中通过实现 error 接口类型来生成错误信息。
    函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息。
    示例:

    import "fmt"
    import "errors"
    
    func f1(arg int) (int, error) {
        if arg == 42 {
            return -1, errors.New("can't work with 42")
        }
        return arg + 3, nil
    }
    
    type argError struct {
        arg  int
        prob string
    }
    
    func (e *argError) Error() string {
        return fmt.Sprintf("%d - %s", e.arg, e.prob)
    }
    
    func f2(arg int) (int, error) {
        if arg == 42 {
            return -1, &argError{arg, "can't work with it"}
        }
        return arg + 3, nil
    }
    
    func main() {
        for _, i := range []int{7, 42} {
            r, e := f1(i)
            if e != nil {
                fmt.Println("f1 failed:", e)
            } else {
                fmt.Println("f1 worked:", r)
            }
        }
        
        for _, i := range []int{7, 42} {
            r, e := f2(i)
            if e != nil {
                fmt.Println("f2 failed:", e)
            } else {
                fmt.Println("f2 worked:", r)
            }
        }
    }
    

    Go 并发

    通过 go 关键字来开启 goroutine
    goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
    goroutine 定义:

    go 函数名( 参数列表 )
    

    示例:

    import "fmt"
    import "time"
    
    func say(s string) {
        for i := 0; i < 5; i++ {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(s)
        }
    }
    
    func main() {
        go say("world") // go 语句开启一个新的运行期线程, 即 goroutine
        say("hello")
    }
    

    输出:

    hello
    world
    hello
    world
    hello
    world
    hello
    world
    hello
    

    通道(channel)可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
    操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

    // 把 v 发送到通道 ch
    ch <- v
    
    // 从 ch 接收数据,并把值赋给 v
    v := <- ch
    

    示例:

    func f(messages chan string) {
        messages <- "ping"
    }
    func main() {
        // 使用 chan 关键字创建通道
        messages := make(chan string)
        
        // go 语句开启一个新的运行期线程, 即 goroutine
        go f(messages)
        
        // 将 messages 传到主线程
        msg := <- messages
        fmt.Println(msg) // ping
    }
    

    更多关于通道的示例:


    参考:
    Go 语言教程
    Go by Example

    相关文章

      网友评论

          本文标题:半天时间 Go 语言的基本实践

          本文链接:https://www.haomeiwen.com/subject/pxkmuqtx.html