偷学golang(二)

今天还没事,继续偷学了,好爽。

今天主要总结一下golang的基础部分,大概就是面向对象之前的内容。

上一章其实内容和形式已经介绍不少了,这点东西其实大多数点到为止就行。

包和函数

包的导入用import关键字,包名以包名的最后一个单词为准,像是math/rand实际的包就是package rand,所有的方法都是以它为开头。引入一个包可能会有import _ "xxx"的写法,这样的写法主要是用了这个包的init()函数。

golang的包里没有什么public和private的概念,有的只有方法和属性的首字母是否大写形成的是否可以导出的概念。如果是大写,就相当于允许外部使用,反之就不行。所以调用其他包的方法会发现首字母全是大写的,不新鲜。说实话这样还挺方便的,比C++那三种关键字好理解多了。但是函数使用上有的点,像是重载和默认参数是不支持的。

形参和返回值的类型声明会有点不适应,因为是名称在前,类型在后的形式。但是按照开发者博客上的说法说这样写是有好处的,像是C语言中的int *p,有的人还可能理解错p的类型,但是在golang中p *int,基本上辨识度就很高,类似的还有数组写成arr [5]int的情况,总之理解上还是比较好的。

写法上语言格式要求大括号左半边必须出现在语句的末尾,看了一下说是因为不用写标点符号,所以如果不这样写,编译器在编译代码时候就会自动在句子结尾加分号,就会导致发生错误。所以在这方面语言格式要求很高,不过感觉问题也不大。

多值返回就很正常了,感觉用着很自然,写交换这类函数反向返回一下就行了,爽到。

函数的闭包

说是闭包就是函数加上引用函数,不太能理解这个闭包是什么意思。

看了个例子,大概是函数里面又定义了个内部函数,可以调用外层函数的变量,最后返回的结果是内层函数的流程……函数外的变量c引用了函数a内的函数b。函数a执行完成后因为垃圾回收的原因不会被回收自身的资源。因为函数a内的函数b需要函数a的数据资源……

闭包可以完成信息隐藏。

闭包的使用要注意引用环境,前一次和后一次引用的情况是不一样的。但是这个量不丢的话,应该就没问题。

总之就是因为引用的函数用了外部函数的数据,但是因为闭包的原因值还在使用,不会被垃圾清理释放掉,从而把这个值保留了下来,方便实现了一些功能。

注意外部引用的值指向的内部的东西是不一样的,所以返回的值不会一致。

函数的递归

简单来说,就是我调用我自己,最常见的例子就是斐波那契数列的写法。

流程控制语句

循环和选择

循环语句只有一个for关键字,具有for和while两个功能,总之把平常语言中的while换成for,再把条件的小括号和多余的标点去掉,用就是了。无限循环直接写for {}就行。🤔

选择语句if关键字也是一样的,不带小括号,语句块必须带大括号就行了,也没什么可说的。switch关键字也差不多,不像其它语言,golang的switch的每个case都自带了break,也就是每次匹配到的case运行完后就直接跳出了。要想继续运行需要用到fallthrough语句。其他的点就是case的条件不需要是常量,也不需要是整数,在使用上更方便了。甚至可以用不带条件的switch语句来实现一个多判断的if语句,显得更有条理。

defer关键字

这个defer关键字的作用是把正常流程中运行的语句结果塞到一个栈里,等到外层函数返回值的时候再返回值,因为是栈,所以会有后进先出的特性。这样的话你就可以用一个递增的循环输出一个递减的序列,只要在正常递增的输出语句前面加上defer关键字就好。

重点的数据结构

结构体不难,指针简单了太多。难的大概就是数组、切片和映射这三个东西。反正代码写多了都知道,无非是和数组还有字符串打交道,务必需要掌握相关的内容。

指针和结构体

指针的用法,其实和C的差不多,把指针运算去掉,好像指针也不是很难的东西了,golang的指针大概就一个取地址解地址的用法,在操作某些值的修改比较方便外,其他没什么好讲的。

结构体就是一组字段,注意结构的写法:

type Vertex struct {
    X int
    Y int
}

感觉和C的没什么区别。使用上用大括号赋值,大概类似于v := Vertex{1,2}这种形式。用v := Vertex{X:1}这种写法可以只列出部分值,其余值按默认的来取。结构体指针访问元素可以直接用p.X这种写法,不用解地址,编译器很智能。

数组和切片

数组就和正常的C语言的数组一样,大小固定不能扩展,所以切片相比数组更常用。

切片用[]int这样的类型声明这是一个元素类型是int的切片,切片单从字面理解上会认为是数组的一部分,事实上确实有这个功能,像是var slice []int = group[1:4]就是表示slice是group数组的下标1到3的数据。遵循左闭右开的情况。

切片像数组的引用,也就是说对切片元素的修改实际上就是对基于的数组元素的修改。

切片的直接定义,像是slice := []int {1,2,3,4,5},实际上是先创建了一个基底的数组,再在数组基础上申请的一个包含全部元素的切片。

切片的上下界不写的情况下默认下界为0,上界为切片的长度。

切片的长度和容量,说实话,看着很麻,什么长度,什么容量,不是一个东西吗?

仔细研究了一下,长度理解很简单,就是当前切片的元素数量;容量理解起来,就是切片的第一个元素,到基底的数组的最后一个元素的元素数量。

所以,有一种情况就比较有趣,切片切自己的话,最好是上界确定,这样下界默认就保持为0,以后扩展和缩小还有的救,但是下界确定的话,好像只能缩,想再扩大就难了,因为这样的操作直接把切片的容量给缩小了。🤔使用len(s)和cap(s)来获取切片的长度和容量。其实感觉还挺怪的,说是可扩展,结果还是得看基底的大小。

切片的0值是nil,长度和容量都是0,并且没有基底数组。

可以用make()来申请切片,包括切片的长度,容量都可以自己定义:

//长度0,容量5
slice := make([]int,0,5)

向切片追加元素,使用append()函数,第一个参数是一个切片,之后的参数类型要和切片类型一致,就可以传入,而且切片的容量不会影响插入,会自动扩容。(上面提的基底数组影响容量当我没说……🙃)

range

for循环中的range可以用来遍历切片或映射,每次返回两个值,一个键一个值,可以用_忽略部分数据:

for k,v := range group {
    fmt.Println(k,v)
}

映射

映射将键映射到值,空映射为nil,什么都干不了。用make()来创建一个映射make(map[string]string)。文法和结构体类似,但是键一定要有,用冒号链接。

函数当值

函数也是值,所以可以拿函数当参数和返回值。

函数的闭包

整不来,啥意思?

总结

基本内容串了一遍,下一部分该面向对象了,孩子还是看不懂函数闭包是个啥玩意,太难了。