Go语言strconv包实现字符串和数值类型的相互转换
数组
是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。
切片
(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内
。
切片是动态结构,只能与 nil 判定相等,不能互相判定相等。声明新的切片后,可以使用 append()
函数向切片中添加元素。
切片的底层结构,即 reflect.SliceHeader:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
由切片的结构定义可知,切片的结构由三个信息组成:
指针:指向底层数组中切片指定的开始位置;
长度:切片的长度;
容量:当前切片的容量;
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作,格式如下:
slice [开始位置 : 结束位置]
语法说明如下:
从数组生成切片,代码如下:
var a = [3]int{1, 2, 3}
var aa = a[1:2]
fmt.Println(a, aa)
其中 a 是一个拥有 3 个整型元素的数组,被初始化为数值 1 到 3,使用 a[1:2] 可以生成一个新的切片 aa,代码运行结果如下:
[1 2 3] [2]
其中 [2] 就是 aa 切片操作的结果。
从数组或切片生成新的切片拥有如下特性:
除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:
var name []Type
其中 name 表示切片的变量名,Type 表示切片对应的元素类型。
下面代码展示了切片声明的使用过程:
// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty)
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
代码输出结果:
[] [] []
0 0 0
true
true
false
※
1.3.3 使用 make() 函数构造切片如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:
make([]Type, Len, Cap)
其中 Type
是指切片的元素类型,Len
指的是为这个类型的切片长度,Cap
为该切片预分配的容量大小,这个值设定后不影响 Len
,只是能提前分配空间,降低多次分配空间造成的性能问题
。
示例如下:
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
代码输出如下:
[0 0] [0 0]
2 2
其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。
容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。
使用 make()函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。
Go语言的内建函数 append() 可以为切片动态添加元素,代码如下所示:
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充
,例如 1、2、4、8、16……。
除了在切片的尾部追加,我们还可以在切片的开头添加元素:
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多
。
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。
copy() 函数的使用格式如下:
copy( destSlice, srcSlice []T) int
其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。
下面的代码展示了使用 copy() 函数将一个切片复制到另一个切片的过程:
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题。
Go语言并没有对删除切片元素提供专用的方法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。
删除的原理是从已有的切片生成新的切片。切片中删除元素
切片是一个集合,可以使用 range
迭代其中的元素。
map 是引用类型,可以使用如下方式声明
:
var mapname map[keytype]valuetype
其中:
提示:[keytype] 和 valuetype 之间允许有空格。
在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 pair 的数目。
//方式1
var mapLit = map[string]string{}
//方式2
mapCreated := make(map[string]float32)
方式1和方式2等价。但不能使用 new() 来构造 map,如果错误的使用 new() 分配了一个引用对象,会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:
mapCreated := new(map[string]float)
接下来当我们调用mapCreated["key1"] = 4.5
的时候,编译器会报错:
invalid operation: mapCreated["key1"] (index of type *map[string]float).
既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?通过将 value 定义为 []int 类型或者其他类型的切片
,就可以优雅的解决这个问题,示例代码如下所示:
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:
delete(map, 键)
其中 map 为要删除的 map 实例,键为要删除的 map 中键值对的键。
Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。
Go语言sync.Map
Go语言list(列表)
在Go语言中,列表使用 container/list
包来实现,内部的实现原理是双向链表
,列表能够高效地进行任意位置的元素插入和删除操作。
list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。
container/list
包的New()
函数初始化 list
变量名 := list.New()
Go语言nil:空值/零值
推荐 :Go语言make和new关键字的区别及实现原理
函数的基本组成:
func
;在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。
在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中。
注意:
在声明函数类型的变量时,要根据实际调用的具体函数来声明。主要分为三种情况。
代码示例:
package main
import (
"fmt"
"strconv"
)
func main() {
//1、调用无参、无返回值的函数
var walkFun func()
walkFun = walk
walkFun()
//2、调用有参、无返回值的函数
var flyFun func(paramName string)
flyFun = fly
flyFun("鸟儿")
//3、调用有参、有返回值的函数
var runFun func(paramName string, paramDistance float64) (res string)
runFun = run
res := runFun("兔子", 500.00)
fmt.Println(res)
}
func walk() {
fmt.Println("walk")
}
func fly(name string) {
fmt.Printf("%s 在飞\n", name)
}
func run(name string, distance float64) (result string) {
result = name + ":跑了 " + strconv.FormatFloat(distance, 'f', 3, 64) + " 米"
return result
}
匿名函数:没有函数名字的函数。