go入门(二)

本文以面向对象为基础,阐述一下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的数值变化。这个方法也叫值方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352