美文网首页
Go 基础 (一)

Go 基础 (一)

作者: Edmond_33 | 来源:发表于2019-02-22 10:38 被阅读0次

    运行 hello world 报错

        package src
        
        import (
           "fmt"
        )
        
        func main() {
           fmt.Println("hello,world!Go!")
        }
    

    go run test.go

    go run: cannot run non-main package

    packge main go中的package 不同于Java中的

    Go Package Path

    go get -v github.com/gpmgo/gopm

    export GOPATH=/Users/liujifeng/go
    export PATH="$GOPATH/bin"


    go语言没有long 只有 int8 int16

    uintptr 指针

    rune类似java char 32位

    complex64 complex128 复数(实部和虚部)欧拉公式

    强制转换 float ->int int(float) java可以隐式转换

    常量 const

    const filename ="abc.text"
    const(
      filename = "a"
      a = 1
      b= "2"
    )   //小括号
    

    枚举类型

    const (
        cpp     = 1
        java    = 2
        android = 3
    ) 
    
    const (
        kotlin = iota
        swift
        oc
    )
    
    const (
        kotlin = 1<<(10*iota)
        swift
        oc
    )
    //结果 1 1024 1048576
    

    iota 表示自增

    switch 不需要break

    go java 备注
    rune char
    int64 long
    strconv.Itoa String()
    []int int[]
    interface{} Object

    指针

    值传递和引用传递
    Go只有值传递一种方式
    函数接受的参数都是copy一份传入函数中

    func swpa(a,b *int){
        *a ,*b = *b,*a
    }
    
    swpa(&a,&b)
    fmt.Println("a=%d, b=%d",a,b)
    

    数组

    数量写在数组前面。。

    var arr [5]int
    arr2:= [3]int{1,2,3}
    arr3:=[...]int{1,2,3,4,5}
    

    range 遍历

    for k,v :range arr3{
      fmt,println("k= %d,v=%d",k,v)
    }
    

    可以通过_省略参数

    调用func f(arr [10]int)会拷贝数组

    Slice(切片)

    FC541ED97069BDC66B6632C2112780FB.jpg

    Slice是数组的一个视图
    slice可以向后扩展,不可以向前扩展
    s[i]不可以超越len(s),向后扩展不能超过cap的长度

        var arr = [...]int{1,2,3,4,5,6,7}
        var slice1 = arr[2:5]
        fmt.Println(slice1)
        fmt.Println(len(slice1),cap(slice1))
    

    追加

    切片每一次追加后都会形成新的切片变量,如果底层数组没有扩容,那么追加前后的两个切片变量共享底层数组,如果底层数组扩容了,那么追加前后的底层数组是分离的不共享的。如果底层数组是共享的,一个切片的内容变化就会影响到另一个切片,这点需要特别注意

    func main() {
     var s1 = []int{1,2,3,4,5}
     fmt.Println(s1, len(s1), cap(s1))
    
     // 对满容的切片进行追加会分离底层数组
     var s2 = append(s1, 6)
     fmt.Println(s1, len(s1), cap(s1))
     fmt.Println(s2, len(s2), cap(s2))
    
     // 对非满容的切片进行追加会共享底层数组
     var s3 = append(s2, 7)
     fmt.Println(s2, len(s2), cap(s2))
     fmt.Println(s3, len(s3), cap(s3))
    }
    
    --------------------------
    [1 2 3 4 5] 5 5
    [1 2 3 4 5] 5 5
    [1 2 3 4 5 6] 6 10
    [1 2 3 4 5 6] 6 10
    [1 2 3 4 5 6 7] 7 10
    

    切片的扩容点

    当比较短的切片扩容时,系统会多分配 100% 的空间,也就是说分配的数组容量是切片长度的2倍。但切片长度超过1024时,扩容策略调整为多分配 25% 的空间,这是为了避免空间的过多浪费。试试解释下面的运行结果。

    s1 := make([]int, 6)
    s2 := make([]int, 1024)
    s1 = append(s1, 1)
    s2 = append(s2, 2)
    fmt.Println(len(s1), cap(s1))
    fmt.Println(len(s2), cap(s2))
    -------------------------------------------
    7 12
    1025 1344
    

    上面的结果是在 goplayground 里面运行的,如果在本地的环境运行,结果却不一样

    $ go run main.go
    7 12
    1033 1280
    

    扩容是一个比较复杂的操作,内部的细节必须通过分析源码才能知晓,不去理解扩容的细节并不会影响到平时的使用,所以关于切片的源码我们后续在高级内容里面再仔细分析。

    Map(字典)

    -- go java
    初始化 var m map[int]string = make(map[int]string)或者map[int]string{90:"优秀"} Map<Integer,String> map = new HasMap<>
    取值 m[90] map.get(90)
    删除 delete(m,90) map.remove(90)

    如果map key不存在怎么办

    delete函数没有返回值,如果key不存在,会做静默处理不会报错
    正常的获取方法,当key不存在的时候,也会做静默处理,返回零值(int = 0 ,string = “”,boolean = false),所以不能通过返回值判断key是否存在
    正确姿势如下

    var score,ok = m[90]
    if ok {
    
    } else {
    
    }
    

    字典里面存的是啥

    字典变量里存的只是一个地址指针,这个指针指向字典的头部对象。所以字典变量占用的空间是一个字,也就是一个指针的大小,64 位机器是 8 字节,32 位机器是 4 字节。


    F98D4F5128B19DE9CD875A9AA792D83E.jpg
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    func main() {
        var m = map[string]int{
            "apple":  2,
            "pear":   3,
            "banana": 5,
        }
        fmt.Println(unsafe.Sizeof(m))
    }
    
    ------
    8
    

    思考题

    package main
    
    import "fmt"
    
    func main() {
        var fruits = map[string]int {
            "apple": 2,
            "banana": 5,
            "orange": 8,
        }
    
        var names = make([]string, 0, len(fruits))
        var scores = make([]int, 0, len(fruits))
    
        for name, score := range fruits {
            names = append(names, name)
            scores = append(scores, score)
        }
    
        fmt.Println(names, scores)
    }
    ---------------------
    [apple banana orange] [2 5 8]
    

    如果替换如下代码

    var names = make([]string, len(fruits))
     var scores = make([]int,  len(fruits))
    

    答案
    [ apple banana orange] [0 0 0 2 5 8] 关系到切片 length 和cap的关系

    结构体

    结构体里面装的是基础类型、切片、字典、数组以及其它类型的结构体等等


    4E4C999EF498CE829AFE9F0EEF234AD8.jpg

    如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回
    如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。

    pp := &Point{1, 2}
    pp := new(Point)
    *pp = Point{1, 2}
    

    make 和 new的区别

    // The make built-in function allocates and initializes an object of type
    // slice, map, or chan (only). Like new, the first argument is a type, not a
    // value. Unlike new, make's return type is the same as the type of its
    // argument, not a pointer to it. The specification of the result depends on
    // the type:
    //  Slice: The size specifies the length. The capacity of the slice is
    //  equal to its length. A second integer argument may be provided to
    //  specify a different capacity; it must be no smaller than the
    //  length. For example, make([]int, 0, 10) allocates an underlying array
    //  of size 10 and returns a slice of length 0 and capacity 10 that is
    //  backed by this underlying array.
    //  Map: An empty map is allocated with enough space to hold the
    //  specified number of elements. The size may be omitted, in which case
    //  a small starting size is allocated.
    //  Channel: The channel's buffer is initialized with the specified
    //  buffer capacity. If zero, or the size is omitted, the channel is
    //  unbuffered.
    func make(t Type, size ...IntegerType) Type
    
    // The new built-in function allocates memory. The first argument is a type,
    // not a value, and the value returned is a pointer to a newly
    // allocated zero value of that type.
    func new(Type) *Type
    

    匿名成员

    Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针

    type Point struct {
        X, Y int
    }
    
    type Circle struct {
        Center Point
        Radius int
    }
    
    type Wheel struct {
        Circle Circle
        Spokes int
    }
    
    var w Wheel
    w.Circle.Center.X = 8
    w.Circle.Center.Y = 8
    w.Circle.Radius = 5
    w.Spokes = 20
    
    -----------------------------
    ------------------------------
    
    type Circle struct {
        Point
        Radius int
    }
    
    type Wheel struct {
        Circle
        Spokes int
    }
    var w Wheel
    w.X = 8            // equivalent to w.Circle.Point.X = 8
    w.Y = 8            // equivalent to w.Circle.Point.Y = 8
    w.Radius = 5       // equivalent to w.Circle.Radius = 5
    w.Spokes = 20
    

    函数

    EBC13F95B5483C27E9C5BECA307E691E.jpg

    1、闭包的定义

    函数可以嵌套定义(嵌套的函数一般为匿名函数),即在一个函数内部可以定义另一个函数。Go语言通过匿名函数支持闭包
    闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
    闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,函数代码在函数被定义后就确定,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

    2、闭包的本质

    闭包是包含自由变量的代码块,变量不在代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。由于自由变量包含在代码块中,所以只要闭包还被使用,那么自由变量以及引用的对象就不会被释放,要执行的代码为自由变量提供绑定的计算环境。
    闭包可以作为函数对象或者匿名函数。支持闭包的多数语言都将函数作为第一级对象,即函数可以存储到变量中作为参数传递给其它函数,能够被函数动态创建和返回。

    可以返回多个参数

    func div (a,b int)(int,int){
        return a/b,a%b
    }
    

    我们在调用的时候,如果只想使用其中几个参数,可以使用下划线屏蔽不需要的参数

    a,_:=div(12,5)
    

    函数可以接收函数作为参数

    func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
        if pre != nil {
            pre(n)
        }
        for c := n.FirstChild; c != nil; c = c.NextSibling {
            forEachNode(c, pre, post)
        }
        if post != nil {
            post(n)
        }
    }
    

    这个就会引入一个函数值
    函数值调用方式

      func square(n int) int { return n * n }
      f := square
      fmt.Println(f(3)) // "9"
    

    函数类型的零值是nil。调用值为nil的函数值会引起panic错误:

    匿名函数

    func squares() func() int {
        var x int
        return func() int {
            x++
            return x * x
        }
    }
    func main() {
        f := squares()
        fmt.Println(f()) 
        fmt.Println(f()) 
        fmt.Println(f()) 
        fmt.Println(f()) 
    }
    

    函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。

    可变参数

    func sum(vals...int) int {
        total := 0
        for _, val := range vals {
            total += val
        }
        return total
    }
    
    fmt.Println(sum(1, 2, 3, 4)) // "10"
    
    values := []int{1, 2, 3, 4}
    fmt.Println(sum(values...)) // "10"
    

    虽然在可变参数函数内部,...int 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。

    func f(...int) {}
    func g([]int) {}
    fmt.Printf("%T\n", f) // "func(...int)"
    fmt.Printf("%T\n", g) // "func([]int)"
    

    可变参数函数经常被用于格式化字符串。下面的errorf函数构造了一个以行号开头的,经过格式化的错误信息。函数名的后缀f是一种通用的命名规范,代表该可变参数函数可以接收Printf风格的格式化字符串。

    Deferred函数

    你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束

    func bigSlowOperation() {
        defer trace("bigSlowOperation")() // don't forget the
        extra parentheses
        // ...lots of work…
        time.Sleep(10 * time.Second) // simulate slow
        operation by sleeping
    }
    func trace(msg string) func() {
        start := time.Now()
        log.Printf("enter %s", msg)
        return func() { 
            log.Printf("exit %s (%s)", msg,time.Since(start)) 
        }
    }
    

    defer语句中的函数会在return语句更新返回值变量后再执行,又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量,所以,对匿名函数采用defer机制,可以使其观察函数的返回值。

    func double(x int) (result int) {
        defer func() { fmt.Printf("double(%d) = %d\n", x,result) }()
        return x + x
    }
    _=double(4)
    

    接口

    接口由使用者定义
    接口的实现是隐式的 只需要实现具体的方法。

    A,B 两个接口,A接口是B接口的子集,则B接口实例可以赋值给A接口

    type Poster interface{
    } 
    
    

    type assertion

    object.(Type)
    
    if newObject,ok: = object.(Type); ok{
    
    }else{
    }
    

    接口变量自带指针
    接口变量同样采用值传递,几乎不需要使用接口的指针
    指针接受者实现只能以指针方式使用;值接受者都可
    interface{} 类似Java中的Object

    接口组合

    type Smellable interface {
      smell()
    }
    
    type Eatable interface {
      eat()
    }
    
    type Fruitable interface {
      Smellable
      Eatable
    }
    

    接口变量的赋值

    变量赋值本质上是一次内存浅拷贝,切片的赋值是拷贝了切片头,字符串的赋值是拷贝了字符串的头部,而数组的赋值呢是直接拷贝整个数组。接口变量的赋值会不会不一样呢?接下来我们做一个实验

    package main
    
    import "fmt"
    
    type Rect struct {
        Width int
        Height int
    }
    
    func main() {
        var a interface {}
        var r = Rect{50, 50}
        a = r
    
        var rx = a.(Rect)
        r.Width = 100
        r.Height = 100
        fmt.Println(rx)
    }
    
    ------
    {50 50}
    

    从上面的输出结果中可以推断出结构体的内存发生了复制,这个复制可能是因为赋值(a = r)也可能是因为类型转换(rx = a.(Rect)),也可能是两者都进行了内存复制。那能不能判断出究竟在接口变量赋值时有没有发生内存复制呢?不好意思,就目前来说我们学到的知识点还办不到。到后面的高级阶段我们将会使用 unsafe 包来洞悉其中的更多细节。不过我可以提前告诉你们答案是什么,那就是两者都会发生数据内存的复制 —— 浅拷贝。

    相关文章

      网友评论

          本文标题:Go 基础 (一)

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