文章目录
1、数组
-
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
-
Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩动态序列,slice功能也更灵活。
-
内置的len函数将返回数组中元素的个数
-
默认情况下,数组的每个元素都被初始化为元素类型 对应的零值
-
如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算,,比如:
q := [...]int{1, 2, 3} //[3]int
-
可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。
-
数组是值类型,所以当一个数组赋值给另一个数组的时候,数组2的改变不会影响数组1,可以理解为复制的操作
s1 = [...]{1,2,3} s2 = s1 s1[0] = 100 //s1 :[1,2,3] s2:[100,2,3]
关于函数入参:
当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是 一个复制的副本,并不是原始调用的变量。因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。
当然,我们可以显式地传入一个数组指针,那样的话函数通过指针对数组的任何修改都可以直接反馈到调用者。
关于函数入参是值传递还是引用传递可参考:飞雪无情
2、Slice
1、概念
- Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。
- 一个slice类型一般写作[]T,其中T代表slice中元素的类型。
- slice的语法和数组很像,只是没有固定长度而已。切片是一个引用类型,指向底层的具体数组(数组改变,切片也会改变)
var i1 = [5]int{1,2,3,4,5} s1 := i1[0:3] s2 := i1[:] s1[1] = 200 //底层数组 i1 = {1,200,3,4,5} 切片s1: {1,200,3} 切片s2:{1,200,3,4,5} s2[2] = 300 //底层数组 i1 = {1,200,300,4,5} 切片s1: {1,200,300} 切片s2:{1,200,300,4,5}
- slice之间不能使用==操作符来判断两个slice是否含有全部相等元素,原因如下:
- 要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断
2、函数
-
内置的len和cap函数分别返回slice的长度和容量,关于长度和容量的定义可参考:李文周-切片
-
slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。
-
从数组中获取切片,这边遵循的原则是左闭右开 ,s[i:] 表示 第i个元素到最后一个元素,s[:j] 表示第0个元素到第j-1个元素。s[:] 切片操作则是引用整个数组。
-
因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素,换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名
-
在底层,make函数 创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量
make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len]
-
append函数 用于向slice追加元素,当slice的容量不足的时候,如果容量不够直接赋值会报错,会通过扩容的方式生成一个新的slice ,所以不能确认在原先的slice上的操作是否会影响到新的slice。因此,通常是将append返回的结果 直接 赋值 给 输入的slice变量
-
copy函数复制为值复制,也就是说赋值一个新的底层数组,而被复制的切片对应的就是新的底层数组,所以修改对于被复制的数组和切片没有影响,可参考:copy复制和等号复制的区别
3、Slice内存技巧
- 直接覆盖原有数组的方式: out = append(out, s)
- 用Slice模拟一个stack
1、初始化stack
2、stack的顶部位置对应slice的最后一个元素:stack = append(stack, v) // push v
3、通过收缩stack可以弹出栈顶的元素top := stack[len(stack)-1] // top of stack
4、要删除slice中间的某个元素并保存原有的元素顺序,可以通过内置的copy函数将后面的子slice向前依次移动一位完成:stack = stack[:len(stack)-1] // pop
如果删除元素后不用保持原来顺序的话,我们可以简单的用最后一个元素覆盖被删除的元素func remove(slice []int, i int) []int { copy(slice[i:], slice[i+1:]) return slice[:len(slice)-1] }
3、Map
在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。
1、map相关操作
创建map
内置的make函数可以创建一个map:
ages := make(map[string]int) // mapping from strings to ints
用map字面值的语法创建map:
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
访问map的值
Map中的元素通过key对应的下标语法访问:
ages["alice"] = 32
fmt.Println(ages["alice"]) // "32"
判断key是否存在的取法:
age, ok := ages["bob"] //age表示key对应的value,ok表示key是否存在也就是value是否能取到
if ok{}
删除map的值
delete(ages, "alice") // remove element ages["alice"]
2、Map注意事项
- 在向map存数据前必须先创建map。
- 所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值。
- map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作,禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效:
_ = &ages["bob"] // compile error: cannot take address of map element 不能取址报错
- Map的迭代顺序是不确定的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,我们必须显式地对key进行排序。
4、结构体
1、基本概念
- 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。
- 结构体变量的成员可以通过点操作符访问。
- 如果结构体成员名字是以大写字母开头的,那么该成员就是导出的(外部可使用);这是Go语言导出规则决定的。一个结构体可能同时包含导出和未导出的成员.
- 一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。
- 如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回。
- 如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。(重点)
- 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较。
2、结构体嵌入和匿名成员
Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。
声明和访问:
type Point struct {
X, Y int
}
type Circle struct {
Point
Radius int
}
//访问 Circle的X,Y
var c Circle
fmt.Println(c.X,c.Y)
初始化结构体
var c Circle
c = Circle: Circle{
Point: Point{
X: 8,
Y: 8
},
Radius: 5
}
5、Json、文本和HTML模板
参考原文