指针
与c一样,go也支持指针,指针保存了值的内存地址。
// 定义一个T类型的指针,零值为 nil
var p *int
// & 操作符 会生成一个指向其操作数的指针
m := 1
p = &i //指针p指向i
// * 操作符 表示引用指针指向的内容(间接引用/重定向)
fmt.println(*p)
*p = 333 // 修改指针指向的值
var p *int
var p *int
结构体
- 定义
type s struct {
x int
y int
}
v = s{66,98}
v.x = 6 //给结构体赋值
- 结构体指针
可以通过结构体指针来访问结构体
v1 := s{1,2}
v2 := s{} // x:=0 y:=0
v3 := s{x:1} // y:=0
p := &v //定义指针指向结构体
p.x = 26 //更改其内容
数组
在 go 中一个数组变量表示整个数组,当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。
数组的指针并不是数组
- 表达式
var arr1 [2]string
arr[0] = "hello"
arr[1] = "world"
fmt.println(a)
//定义一个6元素的数组
n := [6]int{1,6,9,7,5,8}
数组不能改变大小,因此如果需要动态大小的数组需要用到 切片,因为切片长度不固定
切片
切片 由 上届 和 下届 来界定
a[low : high]
primes := [ ] int{4,9,7,3,2,5}
//定义一个 动态s切片 截取 下标 为2到5中的元素
var s []int = primes[2:5]
fmt.println(s)
切片不进行存储,只是描述底层数据的一段,更改切片的元素会更改底层数组中对应的元素,并且同步其他使用该底层数组的切片(相当于指针)
如果扩容之后,还没有触及原数组的容量,那么,切片中的指针指向的位置,就还是原数组,如果扩容之后,超过了原数组的容量,那么,Go就会开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。
切片小于原来数组。影响原数组,切片扩容后大于原数组。就复制了一份原数组。影响新的数组。
s := []bool{true,true,false,true,false}
q := []struct{
i int
b bool
c string
}{
//结构体的具体内容
{1,true,"molly"},
{2,false,"xixi"},
}
切片可以用默认行为来忽略上下界,切片下界默认是0,上界是切片的长度
var a [10]int{2,5,9,7,5,8,6,1}
a[0 : 10]
a[ : 10]
a[0 : ] //已经给数组长度
a[ : ] //同上
注意 切片 的赋值!!
func main()
{
s := []int{2,3,5,7,11}
s := s[1:4]
fmt.println(s) // 357
s:= s[:2]
fmt.println(s)// 35
s := s[1:]
fmt.println(s) //5
s最开始是[]int数组。
s :=s[ 1 : 4] s 的起点被重新赋值
现在s数组是
s{3,5,7} 对应 s[0] = 3 s[1] = 5 s[2] = 7
第二次
s := s[ : 2] s范围取到 s[0] = 3 , s[ 1] = 5
第三次
s := s[1 : ] s范围取到 s[0] = 5
切片的长度和容量
切片拥有 长度 和 容量。
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素 末尾的个数。
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
func main() {
s := []int{2, 3, 5, 7,}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前4个值
s = s[4:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=4 cap=4 [2 3 5 7]
len=0 cap=4 []
len=4 cap=4 [2 3 5 7]
len=0 cap=0 []
由结果可知,只改变下界不会改变容量,只有改变上界才会改变容量并且每次s切片的变化与上一次的变化有关
// 拓展其长度 切片长度已经变成了2个
s = s[:2]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
切片零值是 nil , 且 长度 容量 都是 0,没有底层的数组。
刚定义的数组会被初始化为 0
var s []int
fmt.println(len(s),cap(s))
if s == nil{
fmt.println("nil")
}
[] 0 0
nil!
使用make创建切片
创建动态数组可以使用内置函数make函数,
make会创建一个元素为0值的数组,并返回一个引用这个数组的切片。
func make([] T,len ,cap)[ ]T
//写法一:
var s []byte
s = make([]byte,5,5) // s == []byte{0,0,0,0,0}
//写法二:
a := make([]int,5,5)
fmt.Println(a, len(a), cap(a))
[0 0 0 0 0] 5 5
第一个参数:类型
第二个参数:数组长度
第三个参数:数组容量
为切片追加新的元素
Go内置函数 append()
func append(s []T , vs ...T)[]T
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
结果是 包含原切片的 所有元素 加上新添加元素的切片
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的切片。将原有的元素复制到新的切片,返回的切片会指向这个新分配的数组。
t := make([]int,len(s),(cap(s)+1)*2) // +1 cap(s) ==0
for i := range s{
t[ i ] = s[ i ]
}
s = t
循环复制部分 可以用copy内置函数替代。copy返回复制元素的数目
func copy(dst,src []T) int
t := make([]int,len(s),(cap(s)+1)*2)
copy(t, s)
s = t
将数据追加到切片尾部 AppendByte
AppendByte(slice []int, data..int) []byte{
m := len(slice)
n := m + len(data)
if n > cap(slice){
//扩容
newslice := make([]int, (n+1)*2)
copy(newslice,slice)
slice = newslice
}
//直接放入后面
slice := slice[0:n]
copy(slice[m : n], data)
return slice
//使用
p := []int{2,5,9}
p = AppendByte(p,5,9,4,6,55)
fmt.println(p)
最后输出结果为{2,5,9,5,9,4,6,55}
切片原理
一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。
前面使用 make([]byte, 5) 创建的切片变量 s 的结构如下:
长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。
继续进行切片 s = s[ 2:4 ]
创建一个新的切片,这个切片使用原来切片的底层数组。因此切片修改元素会影响原始切片对应的元素(因为底层数组发生改变)
创建一个切片长度为它的容量的
s = s[ : cap(s) ]
切片长度不能超过其容量,不能使用小于零的索引去访问切片之前的元素
注意
切片操作不会复制底层数组,而是将整个数组保存在内存当中,直到不会在被引用。
但是会有一个问题:
如果一个数组内容很多很大,但是我只需要用一点数据,但是却要把整个数组保存在内存中,如何进行优化呢?
//例如, FindDigits 函数加载整个文件到内存
//然后搜索第一个连续的数字,最后结果以切片方式返回。
//搜索第一个连续数字放到 digitRegexp 数组中
var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}
因为切片引用了原始的数组, 导致 不能释放数组的空间。只用到少数几个字节却导致整个文件的内容都一直保存在内存里。
解决办法:要修复整个问题,可以将感兴趣的数据复制到一个新的切片中:
func CopyDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = digitRegexp.Find(b)
c := make([]byte, len(b))
copy(c, b)
return c
}
注意:内存有个引用计数,第二个你已经开辟了新的切片,运行时b的引用计数为0,内存会自动释放
Range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。
第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
使用 _来忽略变量。
for i, _ := range pow //索引下标
for _, value := range pow //对应元素
映射map
make 函数会返回给定类型的映射,并将其初始化备用。
规则:
- 若顶级类型只是一个类型名,你可以在文法的元素中省略它。
- 映射的文法与结构体相似,不过必须有键名。
- 映射将键映射到值。
映射的零值为 nil 。nil 映射既没有键,也不能添加键。
type Vertex struct {
Lat, Long float64
}
//规则1 省略value类型 Vertex
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
//规则2 key:value
var m = map[string]Vertex{
"Bell Labs": Vertex{ 40.68433, -74.39967},
"Google": Vertex{ 37.42202, -122.08408},
}
//规则3
var m1 map[string]Vertex
m1 = make(map[string]Vertex)
m1["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m1["Bell Labs"])
修改映射
在映射 m 中插入或修改元素:m[key] = elem
获取元素:elem = m[key]
删除元素:delete(m, key)
通过双赋值检测某个键是否存在:elem, ok = m[key]
若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,那么 elem 是该映射元素类型的零值。
当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
注 :若 elem 或 ok 还未声明,你可以使用短变量声明:
elem, ok := m[key]
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
函数的闭包
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
方法
Go 没有类。不过可以为结构体类型定义方法。
方法就是一类带特殊的 接收者 参数的函数。
方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
记住:方法只是个带接收者参数的函数。