容器
数组与切片,切片构建在数组之上,提供更加强大的能力和便捷。
数组的声明与初始化
var identifier [len]type
var arr1 [5]int
arr1
在编译时,值初始化为默认值0。索引从0开始。
Go语言中的数组是值类型,和C中的指向首元素的指针不一样。对比例子
func main() {
// 这一步会给arr1分配一个指针
var arr1 = new([5]int)
// 这一步会将arr1的指针的值赋予arr2
// 这时候 arr2和 arr1指向相同地址
// arr2 := arr1
// 这一不会将arr1产生一个拷贝赋值给arr2
// 此时arr2 和 arr1的地址就不同了
arr2 := *arr1
arr2[2] = 100
arr1[2] = 200
fmt.Printf("%d, %d", arr2[2], arr1[2])
}
切片
切片是一个引用类型,更加类似于C/C++的数组类型、Python的list类型。多个切片如果表示同一个数据的片段,他们可以共享数据,而不同数组代表不同存储。切片是引用,所以他们不需要使用额外的内存并且比使用数组更加高效。
声明格式
var identifier []type // 不需要声明长度
初始化格式
var slice1 []type = arr[star: end]
// 如果完全赋值
var slice1 []type = arr[:]
// or
var slice1 []type = &arr
表示slice1
切片由arr[star]
到arr[end-1]
的子集构成。
切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。
切片能往后移,s = s[1:]
这表示s往后移一位,但是末尾没移动。切片只能往后移,不能往前移动,即s = s[-1:]
是不能通过编译的。
用make()创建切片
var slices []type = make([]type, len)
slices := make([]type, len)
slices := make([]type, len, cap)
len
是数组长度也是切片初始长度,其中cap
为可选参数,当你想创建一个不占用整个数组的切片的时候可以使用。
new()和make()的区别
new(T)
返回的是一个指向新类型T的、值为0的地址指针。适用于值类型如数组和结构体。
make(T)
返回一个类型为T的初始值,只能用于切片、map、channel
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUT4ERY1-1609396243116)(C:\Users\kong\AppData\Roaming\Typora\typora-user-images\image-20201230101551694.png)]
for-range结构
for pos, val := range slice1{
...// do something
}
这种方法val返回的是slice1对应pos下标的值的拷贝,不能修改slice1的值。
切片重组
func main() {
var ar = [10]int{0,1,2,3,4,5,6,7,8,9}
var a = ar[5:7] // {5,6} cap 5
a = a[0:4] // {5, 6, 7, 8} cap 5
}
切片的复制与追加
copy(dst, res)
与append()
-
将切片 b 的元素追加到切片 a 之后:
a = append(a, b...)
-
复制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a)) copy(b, a)
-
删除位于索引 i 的元素:
a = append(a[:i], a[i+1:]...)
-
切除切片 a 中从索引 i 至 j 位置的元素:
a = append(a[:i], a[j:]...)
-
为切片 a 扩展 j 个元素长度:
a = append(a, make([]T, j)...)
-
在索引 i 的位置插入元素 x:
a = append(a[:i], append([]T{x}, a[i:]...)...)
-
在索引 i 的位置插入长度为 j 的新切片:
a = append(a[:i], append(make([]T, j), a[i:]...)...)
-
在索引 i 的位置插入切片 b 的所有元素:
a = append(a[:i], append(b, a[i:]...)...)
-
取出位于切片 a 最末尾的元素 x:
x, a = a[len(a)-1], a[:len(a)-1]
-
将元素 x 追加到切片 a:
a = append(a, x)
切片与垃圾回收
切片的底层指向一个数组,该数组的实际容量可能要大于切片所定义的容量。只有在没有任何切片指向的时候,底层的数组内存才会被释放,这种特性有时会导致程序占用多余的内存。
想要避免这个问题,可以通过拷贝需要的部分到一个新切片中。
Map
键值对,在Python也被称为字典。无序集,hashtable。
概念
声明
var map1 map[keytype]valuetype
var map1 map[string]int
初始化
var map1 = make(map[keytype]valuetype)
map1 := make(map[keytype]valuetype)
永远用make来构造map
键值对是否存在以及删除元素
val, isPresent = map[key]
来判断元素是否存在。isPresent是一个bool,不存在返回false。
delete(map, key)
删除元素,如果key不存在也不会产生错误。
map类型的切片
假设我们想获取一个 map 类型的切片,我们必须使用两次 make()
函数,第一次分配切片,第二次分配 切片中每个 map 元素(参见下面的例子 8.4)。
package main
import "fmt"
func main() {
// Version A:
items := make([]map[int]int, 5)
for i:= range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)
// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}
Version A: Value of items: [map[1:2] map[1:2] map[1:2] map[1:2] map[1:2]]
Version B: Value of items: [map[] map[] map[] map[] map[]]
package(包)
像fmt
`os`等常用内置包在Go语言中有150个以上,它们被称为标准库
锁和sync包
sync.Mutex
是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。
import "sync"
type Info struct {
mu sync.Mutex
// ...
}
如果一个函数想要改变这个变量可以这样写:
func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
自定义包
和其他语言大同小异
import _ "./xxpkg"
只导入其副作用,即只执行它的init()和初始化其中的全局变量。
为自定义包使用godoc
使用 go install 安装自定义包
结构(struct) 方法(method)
组成结构体的数据称为字段(fields)
Go语言中没有类的概念,因此结构体在go中更为重要。
定义
type identifier struct{
field1 type1
field2 type2
...
}
new
var t *T = new(T) // 声明的同时也分配内存
var t *T
t = new(T) // 有些时候分开做会更好
此时t
称为T
的实例(instance)或对象(object)
无论变量是一个 结构体类型 或者是 结构体类型的指针 的操作通过选择器符.
来引用结构体的字段。
结构体的内存布局
Go语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的。如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jr5SLOTp-1609396243123)(C:\Users\kong\AppData\Roaming\Typora\typora-user-images\image-20201230171821765.png)]
初始化
ms := &struct1{xx, xx}
// or
var ms struct1
ms = struct{xx, xx}
使用工厂方法创建结构体
通常方法明明用new
或New
命名,如下:
type File struct{
fd int // 文件描述符
name string // 文件名
}
// File的工厂方法
func NewFile(fd int, name string) *File{
if fd < 0 {
return nil
}
return &File{fd, name} // 返回结构体的地址
}
Go语言常常像这样在工厂方法中简便地实现构造函数
匿名字段和内嵌结构体
type innerS struct {
in1 int
in2 int
}
type outerS struct {
b int
c float32
int // anonymous field
innerS //anonymous field
}
匿名字段只有类型,此时类型就是字段名。
在Go中可以用内嵌结构体来模拟继承。
// 接上
func main() {
outer := new(outerS)
outer.b = 6
outer.c = 7.5
outer.int = 60
outer.in1 = 5 // 继承了inner的in1
outer.in2 = 10 // 继承Inner的in2
fmt.Printf("outer.b is: %d\n", outer.b)
fmt.Printf("outer.c is: %f\n", outer.c)
fmt.Printf("outer.int is: %d\n", outer.int)
fmt.Printf("outer.in1 is: %d\n", outer.in1)
fmt.Printf("outer.in2 is: %d\n", outer.in2)
// 使用结构体字面量
outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
fmt.Println("outer2 is:", outer2)
}
命名冲突
type A struct {a int}
type B struct {a, b int}
type C struct {A; B}
var c C
c.a // 不明确引用
c.A.a // 明确引用
type D struct {B; b float32}
D.b // float32
D.B.b // int
方法
定义:Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
类型和作用在它上面定义的方法必须在同一个包中,因此不能在int、float或是类似的类型上定义方法,这会引发编译错误。
不过可以间接地定义方法,先取一个别名,然后在别名上定义方法。这个方法只对别名生效。
方法和函数的区别这里不赘述。
当接收者为指针的时候,方法可以改变接收者的值。
在方法中,接收者recv
必须有一个显式的名字,而且这个名字必须在方法中用到,也可以用_
不使用。
在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。
指针方法和值方法都可以在指针或非指针上被调用
type Point struct {
x, y float64
}
func (p *Point) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
type NamedPoint struct {
Point
name string
}
func main() {
n := &NamedPoint{Point{3, 4}, "Pythagoras"}
fmt.Println(n.Abs()) // 打印5
}
内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法。也可以覆写。
在示例 10.18 method4.go 中添加:
func (n *NamedPoint) Abs() float64 {
return n.Point.Abs() * 100.
} // output : 500
多重继承
在 Go 语言中,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承。
来集会收和SetFinalizer
Go 开发者不需要写代码来释放程序中不再使用的变量和结构占用的内存,在 Go 运行时中有一个独立的进程,即垃圾收集器(GC),会处理这些事情,它搜索不再使用的变量然后释放它们的内存。可以通过 runtime
包访问 GC 进程。
通过调用 runtime.GC()
函数可以显式的触发 GC,但这只在某些罕见的场景下才有用,比如当内存资源不足时调用 runtime.GC()
,它会在此函数执行的点上立即释放一大片内存,此时程序可能会有短时的性能下降(因为 GC
进程在执行)。