Go语言入门(二)
Day 06
数组
数组的语法
声明和初始化数组
-
需要指明数组的大小和存储的数据类型。
1
var variable_name [SIZE] variable_type
-
示例代码:
1 2
var balance [10] float32 var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
-
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
-
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
1
var balance = []float32{1000.0, 2.0, 3.4, 7.0, 50.0}
-
数组的其他创建方式:
1 2 3 4 5 6 7 8 9 10 11 12
var a [4] float32 // 等价于:var arr2 = [4]float32{} fmt.Println(a) // [0 0 0 0] var b = [5] string{"ruby", "王二狗", "rose"} fmt.Println(b) // [ruby 王二狗 rose ] var c = [5] int{'A', 'B', 'C', 'D', 'E'} // byte fmt.Println(c) // [65 66 67 68 69] d := [...] int{1,2,3,4,5}// 根据元素的个数,设置数组的大小 fmt.Println(d)//[1 2 3 4 5] e := [5] int{4: 100} // [0 0 0 0 100] fmt.Println(e) f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1] fmt.Println(f)
访问数组元素
-
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package main import "fmt" func main() { var n [10]int /* n 是一个长度为 10 的数组 */ var i,j int /* 为数组 n 初始化元素 */ for i = 0; i < 10; i++ { n[i] = i + 100 /* 设置元素为 i + 100 */ } /* 输出每个数组元素的值 */ for j = 0; j < 10; j++ { fmt.Printf("Element[%d] = %d\n", j, n[j] ) } }
数组的长度
-
通过将数组作为参数传递给len函数,可以获得数组的长度。
-
示例代码:
1 2 3 4 5 6 7 8 9
package main import "fmt" func main() { a := [...]float64{67.7, 89.8, 21, 78} fmt.Println("length of a is",len(a)) }
遍历数组
|
|
使用range遍历数组
|
|
-
如果只需要值并希望忽略索引,那么可以通过使用_ blank标识符替换索引来实现这一点。
1 2
for _, v := range a { //ignores index }
数组赋值
-
数组和基本数据类型一样是值传递,非引用传递!!
-
可以用 == 比较是否相等(类型相同的情况下),但不能比较大小。
多维数据
-
Go 语言支持多维数组,以下为常用的多维数组声明语法方式:
1
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
1 2 3 4 5
a = [3][4]int{ {0, 1, 2, 3} , /* 第一行索引为 0 */ {4, 5, 6, 7} , /* 第二行索引为 1 */ {8, 9, 10, 11} /* 第三行索引为 2 */ }
Day 07
切片(Slice)
什么是切片
- Go 语言切片是对数组的抽象,灵活,功能强悍的内置类型切片(“动态数组”)。
切片的语法
定义切片
-
切片不需要说明长度。
-
有长度为数组,无长度就是切片。
1
var identifier []type
-
使用make()函数来创建切片:
1 2 3
var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len)
1
make([]T, length, capacity)
初始化
|
|
|
|
-
将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片(前闭后开),长度为endIndex-startIndex
1
s := arr[startIndex:endIndex]
-
缺省endIndex时将表示一直到arr的最后一个元素
1
s := arr[startIndex:]
-
缺省startIndex时将表示从arr的第一个元素开始
1
s := arr[:endIndex]
|
|
修改切片
- slice没有自己的任何数据。它只是底层数组的一个表示。对slice所做的任何修改都将反映在底层数组中。
- 当多个片共享相同的底层数组时,每个元素所做的更改将在数组中反映出来。(引用传递)
len() 和 cap() 函数
- 切片的长度是切片中元素的数量。切片的容量是从创建切片的索引开始的底层数组中元素的数量。
- 长度表示元素个数,只有有元素才能使用元素对应的索引。
空切片
- 一个切片在未初始化之前默认为 nil,长度为 0。
append() 和 copy() 函数
- append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice。
- copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数。(深拷贝)
注意事项
- 每一个切片引用了一个底层数组。
- 切片本身不存储任何数据都是这个底层数组存储,所以修改切片也就是修改这数组中的数据。
- 当向切片中添加数据时,如果没有超过容量,直接添加,如果超过容量,自动扩容(成倍增长)
- 切片一旦扩容,就是重新指向一个新的底层数组。
- 引用数据类型打印地址不需要加 &
Day 08
集合(Map)
什么是Map
- Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
- map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
- map的长度是不固定的,也就是和slice一样,也是一种引用类型
- 内置的len函数同样适用于map,返回map拥有的key的数量
- map的key可以是所有可比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型……也可以键。
Map的使用
定义Map
-
使用 map 关键字。
1 2
/* 声明变量,默认 map 是 nil */ var map_variable map[key_data_type]value_data_type
-
使用内建函数 make
1 2
/* 使用 make 函数 */ map_variable = make(map[key_data_type]value_data_type)
-
如果只声明,不初始化 map,那么就会创建一个 nil map。nil map 不能直接使用。
-
初始化Map
1
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
delete() 函数
- delete(map, key) 函数用于删除集合的元素, 参数为 map 和其对应的 key。删除函数不返回任何值。
ok-idiom
-
我们可以通过key获取map中对应的value值。语法为:
1
map[key]
-
当key如果不存在的时候,我们会得到该value值类型的默认值,比如string类型得到空字符串,int类型得到0。但是程序不会报错。
-
我们可以使用ok-idiom获取值,可知道key/value是否存在。
-
key存在,ok返回true。
1
value, ok := map[key]
map的长度
-
使用len函数可以确定map的长度。
1
len(map) // 可以得到map的长度
map是引用类型的
- 与切片相似,映射是引用类型。当将映射分配给一个新变量时,它们都指向相同的内部数据结构。因此,一个的变化会反映另一个。(引用传递)
注意事项
- map不能使用==操作符进行比较。==只能用来检查map是否为空。否则会报错:invalid operation: map1 == map2 (map can only be comparedto nil)
Day 09
字符串(string)
什么是string
- Go中的字符串是一个字节的切片。
- 字符串不允许修改。
string的使用
strings包
strconv包
- Go中加号不能用于拼接,加号两边类型要求一致。
- 使用strconv包完成字符串与基本类型之间的转换。
Day 10
函数
什么是函数
- 函数是执行特定任务的代码块。
函数的声明
- go语言至少有一个main函数。
|
|
- func:函数由 func 开始声明
- funcName:函数名称,函数名和参数列表一起构成了函数签名。
- parametername type:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- output1 type1, output2 type2:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型。
- 如果只有一个返回值且不声明返回值变量,那么你可以省略包括返回值的括号(即一个返回值可以不声明返回类型)
- 函数体:函数定义的代码集合。
函数的使用
- 调用。
函数的参数
参数的使用
-
形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。
-
实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。
-
函数调用:
A:函数名称必须匹配
B:实参与形参必须一一对应:顺序,个数,类型
可变参
-
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
1
func myfunc(arg ...int) {}
-
对于函数,可变参数相当于一个切片。
-
调用函数的时候,可以传入0个或多个参数。
-
如果一个函数的参数是可变参数,同时还有其他的参数,可变参数要放在参数列表的最后。
-
一个函数的参数列表中最多只能有一个可变参数。
参数传递
- go语言函数的参数也是存在值传递和引用传递
- 传指针使得多个函数能操作同一个对象。
- 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
- Go语言中slice,map, channel这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
函数的返回值
什么是函数的返回值
- 一个函数被调用后,返回给调用处的执行结果,叫做函数的返回值。
一个函数可以返回多个值
- 一个函数可以没有返回值,也可以有一个返回值,也可以有返回多个值。
- 多个返回值类型可以不同。
- 接受返回值的顺序与返回顺序一致。
- 定义了返回值名称时,return 后面可以省略。
空白标识符
- _是Go中的空白标识符。它可以代替任何类型的任何值。
- _专门用于舍弃数据。
函数的作用域
局部变量
全局变量:
- 不支持 := 简短写法。
defer函数
延迟是什么?
- 延迟(defer)语句,延迟语句被用于延迟一个函数执行(不延迟调用)。
延迟函数
- 可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。
- 如果有很多调用defer,那么defer是采用
后进先出
模式。 - 在离开所在的方法时,执行(报错的时候也会执行)。
延迟方法
- 延迟并不仅仅局限于函数。延迟一个方法调用也是完全合法的。
延迟参数
- 延迟函数的参数在执行延迟语句时被执行,而不是在执行实际的函数调用时执行。
堆栈的推迟
- 当一个函数有多个延迟调用时,它们被添加到一个堆栈中,并在Last In First Out(LIFO)后进先出的顺序中执行。
defer注意点
- 当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正的结束执行。
- 当执行外围函数中的return语句时,只有其中所有的延迟函数都执行完毕后,外围函数才会真正返回。
- 当外围函数中的代码引发运行恐慌时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数。
匿名函数
-
匿名函数:没有名字的函数。
-
定义一个匿名函数,直接进行调用。通常只能使用一次。
-
可以使用匿名函数赋值给某个函数变量,那么就可以调用多次了。
-
Go语言支持函数式编程:
- 将匿名函数作为另一个函数的参数。(回调函数)
- 将匿名函数作为另一个函数的返回值。(闭包结构)
|
|
函数的本质
-
函数作为一种复合数据类型,可以看做是一种特殊的变量。
-
函数名():将函数进行调用,函数中的代码会全部执行,然后将return的结果返回给调用处。
-
函数名:指向函数体的内存地址。
-
函数的定义:
- 开辟一块内存。
- 内存中保存函数体。
- 函数名指向内存地址。
-
函数名加括号,表示调用函数:
- 通过函数名访问函数内存地址。
- 把内存中的函数体,从上到下执行一遍。
-
函数名加括号与不加括号完全不同。
-
可定义函数类型的变量,加括号也可调用。
回调函数
-
高阶函数:接受一个函数作为参数的函数。
-
回调函数:作为另一个函数参数的函数,不会立即被调用执行,何时调用执行取决于高阶函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
func main(){ fmt.Println(operation(5, 3, subtract)) fmt.Println(operation(5, 3, add)) } func add(a, b int) int { return a + b } func subtract(a, b int) int { return int(math.Abs(float64(a) - float64(b))) } func operation(a, b int, operation func(x, y int) int) int { return operation(a, b) }
闭包函数
-
返回值为一个函数。
-
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数中直接定义的变量),并且该外层函数的返回值就是这个内层函数。
-
这个内层函数和外层函数的局部变量,统称为闭包结构。
-
局部变量的生命周期会发生改变,正常的局部变量随着函数调用而创建,随着函数的结束而销毁。但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用。
-
每调用一次外层函数,就会产生一个对应局部变量,内层函数对局部变量的操作互不影响。