go

go 数据类型使用总结

《Go语言学习笔记》第五章总结

Posted by 邹盛富 on September 19, 2018

字符串

字符串为不可变字节序列,其本身为复合结构:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

其中头部指针指向字节数组,没有NULL结尾,默认以UTF-8编码存储Unicode字符。

  • 字符串的默认值不是nil,而是””
  • 支持 !=、 ==、 <、 >、+、 += 操作符
  • 允许以索引访问字节数组,但是不能获取元素地址
    func main() {
      s := "abc"
      println(s[1])
      println(&s[1]) //错误:不能取地址
    }
    

遍历

func main() {
    s := "雨痕"
    //byte 遍历
    for i:=0; i <  len(s); i++ {
        fmt.Printf("%d : [%c]\n", i, s[i])
    }
    //rune 遍历
    for i, c := range s {
        fmt.Printf("%d : [%c]\n", i ,c)
    }
}

转换

如果要修改字符串,需要将其转化为可变类型([]byte或者[]rune),然后再转换回来,这需要重新分配内存。

func main() {
    s := "hello world"

    bs := []byte(s)
    s2 := string(bs)

    rs := []rune(s)
    s3 := string(rs)
}

在某些情况下,为了提高性能会对其进行优化,避免额外分配内存和赋值操作

  • 在类型为map[string]中查询时,默认将[]byte转换为string key
  • for循环迭代时,将string转换为[]byte,直接取字节赋值给局部变量

性能

构建字符串容易造成性能问题,用加法操作拼接字符串每次都需要重新分配内存。

通常的解决方式是通过预先分配足够的内存空间,然后使用strings.Join函数拼接成字符串

Unicode

类型rune专门用来存储Unicode码,它是int32的别名。

数组

定义数组类型的时候,长度必须是非负整型常量表达式,长度是类型的组成部分;也可以使用...来代替数组长度,此时编译器按照初始化值的数量确定数组长度,并且定义多维数组的时候仍然可以使用。

a := [4]int{2, 5}
b := [...]int{10, 3:100}
  • lencap都返回多维数组的第一维长度
  • 如果元素类型支持 != == 操作符,那么数组也支持
  • 可以获取任意的数组元素
  • 数组指针可以直接用来操作元素
  • 数组是值类型,赋值和传参操作都会复制整个数组的数据

切片

其复合结构如下:

type slice struct {
    array unsafe.Pointer
    len int
    cap int
}
  • 可以基于数组和数组指针创建切片
  • cap表示切片所引用数组片段的真实长度,len用于限定可读的写元素数量
  • 通过make指定caplen的指定切片的长度和容量
  • 初始化的区别:
    • 定义int[]类型变量,并未执行初始化操作
    • 完成定义以及初始化全部操作
      var a int[]
      b := []int{}
      
  • 不支持比较,及时元素类型支持
  • *int[]类型不支持索引操作,必须返回[]int类型对象
  • 可以在切片的基础上创建切片,但是不能超出去原来的cap,并且不受len的限制

append

超出切片的cap限制,新分配的数组长度是原来cap的2倍,而非原数组的二倍,对于较大的切片,会尝试扩容1/4

copy

允许两个切片指向同一个数组,允许目标区域重叠,最终所复制的长度以较短的长度为准

字典

  • 要求key必须支持相等运算
  • 访问不存在的key值,返回value的默认值
  • 对字典进行迭代,每次迭代的循序都不一样
  • 字典被设计成“not addressable”,不能直接修改value成员

      func main() {
          type user struct {
              name string
              age byte
          }
    
          m := map[int]user {
              1: {"Tom", 19},
          }
    
          m[1].age += 1   //错误,不可寻址
    
          u := m[1]
          u.age += 1
          m[1] = u        //正确
    
          m2 := map[int]*user {
              1: &user{"jack", 20},
          }
          m2[1].age++
      }
    
  • 不能对nil字典进行写操作,但是可以读
      func main() {
          var m map[string]int
          println(m["a"])
          m["a"] = 1
    
          var m2 map[string]int{}
      }
    
  • 在迭代期间,新增或者删除key是安全的
  • 字典运行时会进行并发操作检测,如果某个任务正在进行写操作,那个其他任务就不能对字典进行并发操作(读、写、删除)

性能

  • 字典对象本身就是指针的封装,传参时无需再次取地址
  • 创建时预先准备足够空间有助于提升性能,减少扩张时内存分配和重哈希操作

结构

  • 匿名结构类型
      u := struct {
          name string
          age byte
      } {
          name: "XXXX",
          age: 12,
      }
    
      type file struct {
          name string
          attr struct {
              owner int
              perm  int
          }
      }
    
      f := file{
          name: "YY",
          // attr: { 错误使用
          //    owner: 1,
          //    perm: 5,
          //},
      }
    
      f.attr.owner = 1
      f.attr.perm = 1
    
  • 只有结构内部所有的字段类型支持相等比较时,才可以进行相等比较

  • 可以使用指针直接操作字段,但是不能使用多级指针

空结构

struct {}

  • 长度为0
  • 可作为通道元素类型,用于事件通知

匿名字段

  • 可以直接引用匿名字段,但是初始化的时候必须显示独立初始化
  • 隐式字段名不包括包名
  • 除接口指针和多级指针之外任何命名类型都可以作为匿名字段
  • 不能将基础类型和其指针类型同时嵌入,因为两者隐式名字相同
  • 如果存在重名,从当前显示命名字段开始,逐步向内查找字段成员,如果匿名字段成员被外层同名字段遮蔽,必须使用显示字段命名

字段标签

type user struct {
    name string `昵称`
    sex byte `性别`
}

func main() {
    u := user{"Tom", 1}
    v := reflect.ValueOf(u)
    t : v.Type()

    for i, n := 0, t.NumField(); i < n; i++ {
        fmt.Println("%s: %v\n", t.Field(i),Tag(), v.Field(i))
    }
}

内存布局

  • 各字段在相邻地址空间内按照定义顺序排列
  • 字段需要进行对齐处理,通常以所有字段中最长的基础类型宽度为标准