go语言圣经第四章(读书笔记)

第四章 复合数据类型

数组

  • 数组是由固定长度的特定元素组成的序列,一个数组可以由零个或多个元素组成
  • 索引下标的范围是从0开始到数组长度减1的位置
  • 默认情况下,数组的每个元素都被初始化为对应类型的零值
  • ...省略号表示数组的长度是根据初始化值的个数决定的
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"
  • 数组的长度是数组类型的一个组成部分,[3]int和[4]int不是相同的数组类型
  • 数组的长度必须是常量表达式
  • 数组间可以相互比较,按序列顺序,逐个元素比较,但需满足:类型相同,记住[3]int和[4]int不是相同类型
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int

Slice

  • Slice(切片)是变长序列,每个元素都是相同类型的,一般写作[]T
  • Slice提供访问数组子序列(或全部)元素的功能,它的底层引用了一个数组对象
  • Slice由三个部分构成
    1.指针:指向第一个slice元素对应的底层数组元素地址
    2.长度:长度不能超过容量,可用len函数求得
    3.容量:容量一般是从slice开始位置到底层数据的结尾位置的长度,可用cap函数求得
  • Slice可以由数组的切片操作得到,切片操作s[i:j]
    1.若省略i,会用0代替
    2.若省略j,会用len(s)代替
    3.切片操作得到的是一个“左闭右开的区间”
    4.如果超出cap(s)会导致panic
    5.如果超出len(s)而没超出cap(s),会扩展(这里指的扩展是在原来的slice下扩展,但是直接访问超出len(s)的元素将会产生panic)
func main() {
  a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
  s := a[:4]
  s2 := s[:7]
  fmt.Println(s, s2)//[1 2 3 4] [1 2 3 4 5 6 7]
  fmt.Println(cap(s)) //10
  fmt.Println(s[7]) //panic:index out of range
  fmt.Printf("%T\n", s)  //[]int
}
  • 字符串和[]byte的切片操作相似,都是x[m:n],都是返回一个原始字节序列的子序列,底层都是共享之前的底层数组
  • 需要注意的是字符串的切片操作还是字符串,实际上都是字符串而不是切片,也就是常量,可以使用len但是不能用cap
  s1 := "hello,world"
  s2 := s1[:5]
  fmt.Println(cap(s1))  //panic
  fmt.Println(len(s1))  //11
  fmt.Println(cap(s2))  //panic
  fmt.Println(len(s2))  //5
  fmt.Printf("%T\n", s2)  //string
  • 相比较字符串,数组的切片,可以修改数据,就像普通数组一样可以修改数据
  • 在使用切片修改数据时,必须注意会改变底层数组的数据,因为修改的是同一片内存空间(这其实在笔记Slice部分一开始就说明了,构成中的指针指向第一个slice元素对应的底层数组元素地址)
  • 当然,可以使用字面量来创建slcie。其底层数组就是字面量指出的匿名数组了
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
  • 和数组不同,slice之间不能直接相互比较
  • 对于[]byte的比较,有bytes.Equal函数来判断两个[]byte是否相等
  • 其它的类型,需要自己定义比较的方法
  • 为什么不直接支持slice的比较操作呢?
    1.slice是间接引用,slice可以包含自身
    2.一个固定的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改
  • slice唯一合法的比较是和nil比较,一个零值的slice等于nil,这代表slice没有底层数组,长度和容量都是0
  • 也有非nil值,但长度容量都为0的slice,例如[]int{}或make([]int, 3)[3:],这是允许的
  • 在实际应用中,必须搞清楚slice的空究竟代表什么:
    1.为nil?
    2.不为nil,但是len=0?
  • 以上两种情况,len都是为0,故判断slice为空的通用方式可以以采用len(s)==0
  • 可以使用内置的make函数来创建指定的slice
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

append函数

  • 内置的append函数用于向slice追加元素,或者追加slice
var x []int
x = append(x, 1)
x = append(x, 2, 3)
x = append(x, 4, 5, 6)
x = append(x, x...) // append the slice x
fmt.Println(x)
// "[1 2 3 4 5 6 1 2 3 4 5 6]"
  • 但是append不能保证内存是否有重新分配,也不能确定新的slice和原始的是否引用相同的底层数组空间
  • 书中给出一个slice的结构参考,便于理解
type IntSlice struct {
    ptr
    *int
    len, cap int
}

Slice内存技巧

  • 这一部分都是介绍例子,详细看原书
  • 一个有用的问题:通过函数传参的slice会如何呢?
  • 书中给出的例子证明了,对于[]string传参后,作为参数的[]string和原来的[]string是指向同一个底层string数组的,但如果使用了append处理后,就不一定了
  • 建议:每次处理完slice,最好覆盖掉原来的slice
data := []string{"hello", " ", ",", "world" }
data = nonempty(data)
  • 内置copy函数,可以把后面的slice向前依次移动一位:
copy(slice[i:], slice[i+1:])

Map

  • Map是一个哈希表的引用,可以写作map[K]V,也就是key,value:
    1.key都有相同的类型,value都有相同的类型,key和value可以是不同类型
    2.key的类型必须支持==操作,但不推荐用浮点数作为key,浮点数有可能出现NaN和任何浮点数都不相等
    3.可以使用len函数获取map的长度
  • 要创建一个map,推荐使用内置make,同样的,这里把slice也放进来作为对比
var a1 []int //只是声明这么一个类型,对应的零值为nil
a2 := make([]int, 3)  //这里创建了一个[]int数据,并且长度为3,每个元素对应零值
0

var b1 map[string]int  //只是声明,对应零值为nil,不能添加值
b2 := make(map[string]int)  //这里创建了map[string]int数据,并且可以添加值了
  • 从上面例子看出引用类型的声明行为往往不如人意,需要特别注意
  • map添加元素很简单:
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34
  • 删除元素使用内置的delete函数
delete(ages, "alice") // remove element ages["alice"]
  • 查询(访问)元素
age1 := ages["bob"] //如果存在,返回值;否则,返回相应的零值
age2, ok := ages["bob"] //如果存在,返回value, true;否则,返回零值,false
  • 对map中的value的取址是不允许的
  • 原因在于,map的动态的,元素增长可能导致重新分配空间,从而可能导致之前的地址无效
_ = &ages["bob"] // compile error: cannot take address of map element
  • 使用range循环遍历map,遍历顺序不确定,每次都是随机的: k, v := range map
func equal(x, y map[string]int) bool {
    if len(x) != len(y) {
        return false
    }
    for k, xv := range x {
        if yv, ok := y[k]; !ok || yv != xv {
            return false
        }
    }
    return true
}
  • 如果需要排序,需要显式使用sorts包对key的值进行排序,相当于创建一个和key相同类型的slice
import "sort"
var names []string
for name := range ages {
    names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
    fmt.Printf("%s\t%d\n", name, ages[name])
}

Set类型(Go并不直接提供)

  • 通常使用map[T]bool来作为类型T的集合,当然,value的类型bool可以是其它有意义的值

一些比较灵活的用法

var graph = make(map[string]map[string]bool)
func addEdge(from, to string) {
    edges := graph[from]
    if edges == nil {
        edges = make(map[string]bool)
        graph[from] = edges
    }
    edges[to] = true
}
func hasEdge(from, to string) bool {
    return graph[from][to]
}

结构体

  • 是由零个或多个任意类型的值聚合成的实体
  • 结构体的成员,不像map的成员不能取址(这个还要看作用域,包一级,导出性)
  • 结构体的零值是相应成员的都是零值
  • 对于空结构体struct{},它的大小为0,应该尽量避免

    结构体面值

  • 不指定成员,将按声明顺序赋值或者初始化
type Point struct{ X, Y int }
p := Point{1, 2}
  • 指定成员赋值或者初始化
p := Point{X: 1, Y: 2}
p2 := Point{
    X; 1,
    Y: 2,
}
  • 注意到p2,{必须和Point同行,Y:2结尾需要逗号
  • 还需注意,外部包如果要调用某个包的结构体,需要导出才行(大写开头)
package p
type T struct{ a, b int } // a and b are not exported
package q
import "p"
var _ = p.T{a: 1, b: 2} // compile error: can't reference a, b
var _ = p.T{1, 2}// compile error: can't reference a, b

结构体比较

  • 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的

结构体嵌入和匿名成员

  • 下面给出两个例子,注意嵌入的方式,还有访问形式
type Point struct {
    X, Y int
}
type Circle struct {
    Center Point
    Radius int
}
type Wheel struct {
    Circle Circle
    Spokes int
}

var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8
w.Circle.Radius = 5
w.Spokes = 20

以上代码的等价形式(注释部分写法依然有效)如下:

type Point struct {
    X, Y int
}
type Circle struct {
    Point
    Radius int
}
type Wheel struct {
    Circle
    Spokes int
}

var w Wheel
w.X = 8 // equivalent to w.Circle.Point.X = 8
w.Y = 8 // equivalent to w.Circle.Point.Y = 8
w.Radius = 5 // equivalent to w.Circle.Radius = 5
w.Spokes = 20
  • 但是,有嵌入成员的结构体,在初始化时,必须显式写明:
w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
Circle: Circle{
    Point:
        Point{X: 8, Y: 8},
        Radius: 5,
    },
    Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
}
  • 嵌入的匿名成员也有一个隐式名字,因此不能包含两个类型相同的匿名成员
  • 实际上,嵌入成员不一定是结构体,任何命名的类型都可以作为结构体的匿名成员(在第六章会专门讨论)

JSON

  • JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议(由包encoding/json支持),类似的协议还有:
    1.XML (encoding/xml)
    2.ASN.1 (encoding/asn1)
    3.Google的Protocol Buffers (github.com/golang/protobuf)
  • JSON是对javascript中各种类型的值进行Unicode文本编码
  • JSON数组用方括号表示,对应Go的数组和切片
  • JSON对象是一个字符串到值的映射,也就是一系列name:value对,用花括号包含并以逗号分隔,可以对应Go的map类型
  • 例子
boolean true
number -273.15
string "She said \"Hello, BF\""
array ["gold", "silver", "bronze"]
object {"year": 1980,
        "event": "archery",
        "medals": ["gold", "silver", "bronze"]}
  • encoding/json的基本操作:
    1.Marshal
    2.Unmarshal
    3.MarshalIndent
    4.Decoder
    5.Encoder

  • 详细的在反射章节补充

文件和HTML模板

  • 这一部分不作记录,原因在于
    1.后端一般这是数据的处理,怎么呈现数据,这个交给前端反而更方便,而且减少了后端的数据处理压力(但真的需要后端处理时,再看也不迟)
    2.这里面的实现,都是靠go语言的基础来做的,接口,反射这些还没懂之前,看这个意义不大

猜你喜欢

转载自www.cnblogs.com/laiyuanjing/p/11227776.html