美文网首页
go入门(二)

go入门(二)

作者: 靈08_1024 | 来源:发表于2019-05-06 15:59 被阅读0次

    本文以面向对象为基础,阐述一下go中的某些功能的使用。

    高级数据类型

    高级数据的关键字是type。下面简单描述每种对象的声明:

    数组

    数据必须定义类型,所有元素都有默认值。

        //第一种  先定义,而后赋值  []中的长度不可以省略,运行会出错
        var arr [2]int
        arr01 := []int{}
        arr[0] = 2
        //第二种
        arr1 := [3]int{1, 2, 3}
        //第三种  此处的[]中可以写长度值,也可以不写,也可以写...
        //因为后面的{}里有值,go会自动计算出该数组的长度
        arr2 := []int{4, 5, 6}
        arr21 := [...]int{4, 5, 6}
        //第四种 不按顺序来编写数组的值,而是按照索引
        //下面数据的输出是[3 1]
        //索引1位置的是1,索引0位置的是3
        arr3 := []int{1: 1, 0: 3}
        fmt.Println(arr, arr01, arr1, arr2, arr21, arr3)
    

    基本方法:

    • len():求数组长度,如len(arr21)。
    • cap():求数组容量,如cap(arr21)。
    • append():给数组追加元素(不会覆盖默认值)。arr21=append(arr21,22,23),值为[5 6 22 23]

    Tips:关于len()和cap()的区别
    在数组中,这两个是一样的。
    在切片中,cap()是总容量,len()是可见元素的长度。所以cap()>=len()。

    切片

    切片,数组的一种,类似于java中的数组截断。但是java中的截断是生成一个新数组,这个是返回指定索引,还有可能通过切片扩容访问原数组。
    数组中的所有方法都可以给切片使用。

        var numbers4 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
        slice5 := numbers4[4:6:8]
        fmt.Println(len(slice5), cap(slice5))
        fmt.Println("slice5-1:", slice5)
        slice5 = slice5[:cap(slice5)]
        fmt.Println("slice5-2:", slice5)
        slice5 = append(slice5, 11, 12, 13)
        fmt.Println("slice5-3:", slice5)
    

    如下是输出内容:

    2 4
    slice5-1: [5 6]
    slice5-2: [5 6 7 8]
    slice5-3: [5 6 7 8 11 12 13]
    

    关于切片的使用,有一种比numbers4[4:6:8]简单的用法,numbers4[4:6]。不同的是前者扩容,会找原数组下标为8的元素,并将原数组下标48的元素返回,而后者一直都是原数组下标46的元素。

    字典

    类似于在java中的map。其关键字也是map。
    示例如下:

        //定义一个string类型的键,int类型值的字典(不初始化则{}内为空即可)
        enums := map[string]int{"Z": 100, "W": 44}
        //给enum添加键值对,键为“X”,值为99
        enums["X"] = 99
        //获取键为“Z”的值
        i := enums["Z"]
        fmt.Println(enums, i)
        //获取键为“X”的值,如果没有,返回该类型的默认值
        //如果没有,则此时ok为false
        p, ok := enums["X"]
        fmt.Println(enums, p, ok)
        //删除键值为“Z”的键值对
        delete(enums, "Z")
        fmt.Println(enums)
    

    输出结果:

    map[W:44 X:99 Z:100] 100
    map[W:44 X:99 Z:100] 99 true
    map[W:44 X:99]
    

    关于p, ok := enums["X"]这种写法,以后会很常见这种写法。因为对获取的元素是否存在的不确定性,所以ok变量是表示该元素是否存在的bool值。

    通道

    通道类型为chan,用符号<-来表示。其数据格式类似于队列。示例如下:

        //表示简历4个长度的通道
        ints := make(chan int, 4)
        ints <- 1
        ints <- 2
    
        i := <-ints            //此处是1
        ints <- 4
        ints <- 7
        ints <- 71
        <-ints                 //此处是2
        fmt.Println(i, <-ints) //此处是4
    

    关于上面的通道ints,直接输出ints位地址,而<-ints表示ints中存储的值。而<-ints也可以和别的变量进行运算。
    i:=<-ints这一行,会弹出一个长度的数据。当塞入的长度超出指定的长度时,会抛出错误。相当于队列满了,不能再塞入了。

    fatal error: all goroutines are asleep - deadlock!
    

    goroutines是go的函数模型,可以理解为一次性轻量级线程。在不使用协程时,主函数运行完了,此时没有其他函数去执行管道设值,程序就放弃等待了,然后出错了。

    针对于上面的情况,

        i1 := 1
        //定义int型通道c1,默认长度为0
        c1 := make(chan int)
    
        go func() {
            c1 <- 11
        }()
    
        fmt.Println("第一次:", i1)
       //c1中的值赋值给i1
        i1 = <-c1
        fmt.Println("第二次:", i1)
    

    输出的结果:
    第一次: 1
    第二次: 11

    因为默认的通道为非缓冲通道,其长度为0,所以此时需要借助协程来操作。而第一个例子的通道指定了长度,叫做缓冲通道。
    go fun(){}表示一个协程来运行其所包含的代码。

    协程

    理解协程,要和多线程进行比较。二者的共同点就是并发,但是协程是线程自己内部进行切换,由程序决定,而多线程是多个线程间切换,有切换上下文开销。相比较而言,协程开销小的多。

    函数

    在go中方法首字母大写等于public(公开),小写等于protect(同包)。没有其他访问范围限制。

    对于JS中函数熟悉的人,对go中的函数理解也会很快的。
    函数的基本形式为:

    //第一种形式
    func sum(a int, b int) int {
        return a+b
    }
    //第二种形式
    func sum1(a int,b int)(c int)  {
         c=a+b
         return
    }
    

    对于函数,入参为变量名 变量类型。输出类型在最后。void类型的不写。如果入参的类型部分相同,则可以sum(a, b int)这样写法。在go中,所有方法不管出入参数类型是否相同,方法名必须不同!

    在JS中,函数可以用匿名变量代替,针对于上面的sum函数:

       //第一种
        var s0 = sum
        s0(1, 2)
       //第二种
        var s1 = func(a, b int) int {
            return a + b
        }
        s1(2, 3)
    

    对于匿名函数,还有一种是在第二种后面直接加(),写上入参,但这种是一次性的,个人感觉有点类似于闭包。复用性不大,有兴趣的可以自己尝试。

    go中的函数支持返回多个值,如下示例:

    func sum2(a, b int) (int, bool) {
        return a + b, true
    }
    

    结构体

    go中的结构体类似于就java中的类。他可以封装属性、函数等。
    示例,演示go中的继承:

    type Animal struct {
        name string
        Age  int
    }
    
    type Dog struct {
        //继承部分
        Animal
        color string
        name string
    }
    
    func main() {
        animal := Animal{"hh", 33}
        
        var dog Dog
        dog.Animal.name="XX1"
        dog.name="XX2"
    
        fmt.Println(animal, dog)
    }
    

    输出如下:
    {hh 33} {{XX1 0} XX2}

    值得一提的是,在Dog类中,如果没有name属性,那么调用dog.name属性会直接指向dog.Animal.name属性。

    匿名结构体

        dog := struct {
            name  string
            age   int
            color string
        }{"小黄", 5, "#FF0000"}
    
        fmt.Println(dog)
    

    方法体
    方法体,即属于某一结构专属的方法调用。
    方法体是一种特殊的函数。所以上面函数部分的规则也同样适用于方法体。

    func (animal *Animal) GrowUp() {
        animal.Age++
    }
    

    接口

    一个接口代表着某一种类型的行为。
    接口的基本定义格式如下:

    type Animal interface {
        Grow()
        Move(string) string
    }
    

    可以发现,接口内方法的定义和java是有相似之处的——都只有方法声明,没有方法体。在go的接口中,不支持属性。

    接口的类型转换

    func main() {
        d := Dog{"Robert", 3, "Beijing"}
        v := interface{}(&d)
        animal := v.(Animal)
        fmt.Println(animal)
    }
    
    type Animal interface {
        Grow()
        Move(string) string
    }
    
    type Dog struct {
        name    string
        Age     int
        address string
    }
    
    func (dog *Dog) Grow() {
        dog.Age++
    }
    func (dog *Dog) Move(address string) string {
        oldAddr := dog.address
        dog.address = address
        return oldAddr
    }
    

    上面的&d表示取地址操作。在下面会描写这部分的。而interface{}(&d)返回的类型用 reflect.TypeOf 查看是*main.Dog。需要说一下的是interface{}是空接口,它相当于java中的Object。go中的任何类型都是它的实现类型。再紧随其后的v.(Animal)是强制Dog类型转换为Animal类。

    指针

    对于java小伙伴来说,指针、地址等都只是概念性了解,不像C++中,直接操作地址、指针等。而C/C++的性能之所以高,指针功不可没。但是在C/C++中的回收控制不太好。所以下面从使用方面来了解一下指针与地址。
    操作指针设计两个字符——&*。简单的来说,&是取地址的,*是取值的。
    基本类型

        house := "wahaha"
        // 对字符串取地址, ptr类型为*string
        ptr := &house
        // 打印ptr的类型,地址
        fmt.Println(reflect.TypeOf(ptr), ptr)
            //对ptr取值操作
        fmt.Println(*ptr)
    

    此时输出结果为

    *string 0xc00004c1c0
    wahaha

    如想再深入了解,可以参见我整理的C++中的指针

    对象类型
    还是以上面的Dog类的Grow方法。

    func (dog *Dog) Grow() {
        fmt.Println(&dog)
        dog.Age++
    }
    

    此时打印输出的结果是 0xc000006028。

    我们注意到了所有的方法中,前面的都是*Dog,那么此时*Dog和Dog有什么区别呢?
    此时的*Dog是Dog类的一个指针类型,简单点说,Dog类型的对象在程序中有很多,怎么确定是你这个d来调用方法呢?就要用到指针类型。上面的方法也叫指针方法。而如果是:

    func (dog Dog) Grow() {
        dog.Age++
    }
    

    此时就会将你的d复制一份,而复制的这一份与原来的d只有数值的相同而已,两者不共用地址,所以在func (dog Dog) Grow()方法中,计算完后,函数的入参dog就没有再用了,也就是此时复制的这一份没有再用了,就会进行销毁,而且这个复制对象的生存与否与d没有任何关系,也不会影响d的数值变化。这个方法也叫值方法

    相关文章

      网友评论

          本文标题:go入门(二)

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