【Go入门】数据类型

基础数据类型

  • 整数
    • 有符号:int 32/64bit、int8 8bit、int16 16bit、int32 32bit、int64 64bit
    • 无符号:uint 32/64bit、uint8 8bit、uint16 16bit、uint32 32bit、uint64 64bit、uintptr 没有指定具体的bit大小但是足以容纳指针
      • byte(uint8 的别名)
      • rune(int32 的别名, 通常用来表示一个 Unicode 码点)
  • 浮点数:float32 float64
  • 字符串:string
  • 布尔型:bool
  • 复数:complex64 complex128
  • 常量
    • iota 常量生成器,用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。
    • 无类型常量,编译器为没有明确基础类型的数字常量提供比基础类型更高精度的算术运算,可以认为至少有256bit的运算精度。

标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包:

  • strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
  • bytes包提供了很多类似strings包功能的函数,但是针对和字符串有着相同结构的[]byte类型。
  • strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
  • unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,用于给字符分类。

特别注意:

  • go中默认字符编码格式为UTF-8,由于UTF-8编码对字节长度占用的不确定性,go中的字符串会根据需要占用1-4个字节。英文字符占用1个字节,中文占3个字节。
  • 算术运算中,%取模符号与被取模数符号一致,-5%3和-5%-3结果都是-2。/除法运算依赖于操作数是否全为整数,5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
  • 一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度。通常应该优先使用float64类型,因为float32的有效bit位只有23个,其它的bit位用于指数和符号,当整数大于23bit能表达的范围时,float32的表示将出现误差。

复合数据类型

数组

数组是一个由固定长度的特定类型元素组成的序列,因为长度固定在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩的动态序列,slice功能也更灵活。

默认情况下,数组的每个元素都被初始化为元素类型对应的零值。

数组定义时长度可以使用“…”省略号,表示数组的长度是根据初始化值的个数来计算。
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。
数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。

切片

slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型。slice的语法和数组很像,只是没有固定长度而已。

slice的底层引用一个数组对象,提供了访问数组子序列(或者全部)元素的功能。

slice由三个部分构成:指针、长度和容量。指针指向第一个silce元素对应的底层数据元素的地址,但slice的第一个元素不一定就是数组的第一个元素。容量一般是从开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。

slice的切片操作s[i:j],其中0 ≤ i≤ j≤cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。新的slice将只有j-i个元素。
如果i位置的索引被省略的话将使用0代替,如果j位置的索引被省略的话将使用len(s)代替。

e.g.: months[1:13]切片操作将引用全部有效的月份,和months[1:]操作等价;months[:]切片操作则是引用整个数组。

和数组不同的是,slice之间不能比较,不能使用==操作符来判断两个slice是否含有全部相等元素。唯一的例外是和nil进行比较。

可以使用内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。

1
2
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]

在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能引用底层匿名的数组变量。

内置的append函数用于向slice追加元素。

Map

map是哈希表的引用,写为map[K]V,其中K和V分别对应key和value。其中K对应的key必须是支持==比较运算符的数据类型。

使用内置的make函数可以创建一个map:

1
ages := make(map[string]int) // mapping from strings to ints

也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value:

1
2
3
4
ages := map[string]int{
"alice": 31,
"charlie": 34,
}

相当于

1
2
3
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

另一种创建空的map的表达式是map[string]int{}

map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:

1
_ = &ages["bob"] // compile error: cannot take address of map element

禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。

和slice一样,map之间也不能进行相等比较,唯一的例外是和nil进行比较。

Go语言中并没有提供一个set类型,但是map中的key也是不相同的,可以用map实现类似set的功能。

有时候需要一个map或set的key是slice类型,但是map的key必须是可比较的类型,但是slice并不满足这个条件。不过可以通过两个步骤绕过这个限制。

  • 第一步,定义一个辅助函数k,将slice转为map对应的string类型的key,确保只有x和y相等时k(x) == k(y)才成立。
  • 第二步,创建一个key为string类型的map,在每次对map操作时先用k辅助函数将slice转化为string类型。

结构体

结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。

声明实例:

1
2
3
4
5
6
type Address struct {
hostname string
port int
}

var addr Address

一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适用于数组。)但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。

如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小为0,也不包含任何信息。

结构体字面值

结构体值也可以用结构体字面值表示,结构体字面值可以指定每个成员的值。

1
2
type Point struct{ X, Y int }
p := Point{1, 2}

也可以以成员名字和相应的值来初始化,可以包含部分或全部的成员,e.g.:

1
p := Point{X:1, Y:2}

在这种形式的结构体字面值写法中,如果成员被忽略的话将默认用零值。因为提供了成员的名字,所以成员出现的顺序并不重要。

两种不同形式的写法不能混合使用。而且,不能企图在外部包中初始化结构体中未导出的成员。

结构体可以作为函数的参数和返回值,如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回。

如果要在函数内部修改结构体成员的话,用指针传入是必须的,因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。

结构体比较

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的。相等比较运算符==将比较两个结构体的每个成员。

结构体嵌入和匿名成员

Go语言提供结构体嵌入机制让一个命名的结构体包含另一个结构体类型的匿名成员,这样就可以通过简单的点运算符x.f来访问匿名成员链中嵌套的x.d.e.f成员。

匿名成员:只声明一个成员对应的数据类型而不指名成员的名字。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。