Go中有四种数据类型:

  • 基本类型(basic types)
  • 集合/聚合类型(aggregate types)
  • 引用类型(reference types)
  • 接口类型(interface types)

1. 基本类型

1.1 整数

  • 有符号整数:int,int8,int16,int32,int64
  • 无符号整数:uint,uint8,uint16,uint32,uint64
  • rune(等效于int32),表示是Unicode
  • byte(等效于uint8)
  • uintptr

注意点:

  1. int和uint的宽度取决于平台,可能等效于int32/uint32,也可能等效于int64/uint64。虽然等效,但均是不同的类型,不可以直接赋值。不同类型赋值时需要显式的做类型转换。
  2. uintptr是一个无符号整数类型,宽度未指定,但它足够存放一个指针。一般只用于底层编程。
  3. 在Go中,求模时,结果的符号与被除数相同:-5%3=-2-5%-3=-2.除号(/)的处理与C语言相同。
  4. rune类型打印时用%c,如果想要结果被单引号引起来的话用%q.
  5. 移位操作中:对于左移,空余位补0.对于右移,无符号补0,有符号补符号位。

1.2 浮点数

  • float32(提供6位小数精度)
  • float64(提供15位小数精度)

注意点:

  1. 浮点数没有float类型。
  2. 浮点数打印使用%g
  3. 最大的float32为math.MaxFloat32;最大的float64位math.MaxFloat64
  4. 可以使用math.IsNaN()函数测试他的参数是不是数。math.NaN表示不是一个数,任何与NaN的比较都是false。

1.3 复数

  • complex64
  • complex128

复数的定义方法有很多种:

var x complex128 = complex(1, 2)   // 1+2i
var y complex64 = complex(3, 4)    // 3+4i
m := 1+2i    // 1+2i
n := 3+4i    // 3+4i

函数realimag可以分别取复数的实部和虚部。

1.4 布尔型

  • true
  • false(布尔型变量的默认值)

对于bool型也存在其他语言中的短路(short-circuit)效应。

1.5 字符串类型

A string is an immutable sequence of bytes.

注意点:

  1. 字符串是一串不可改变的(immutable)字节序列,所以字符串不可以改变。
  2. 字符串的第i个字节不一定就是第i个字符,因为Go使用的是UTF-8编码。UTF-8中一个字符可能用1~4个字节编码。
  3. 字符串常量有两种表示方法:(1)使用双引号引起来。这种表示方法可以使用反斜线转义。(2)使用反引号(`)引起来,被引起来的内容保持原样,不做任何转义。这种方式常用语正则表达式等具有某种格式的字符串,可以避免输入很多转义符。
  4. 使用单引号引起来的不是字符串,而是rune类型(int32的别名)。

1.6 常量

Constants are expressions whose value is known to the compiler and whose evaluation is guaranteed to occur at compile time,not at run time. The underlying type of every constants is a basic type: boolean, strings, or number.

注意点:

  1. 常量使用const关键字定义。
  2. 当一次定义一组常量时,除了第一个以外,其它的都可以省略。此时,省略掉的常量沿用它之前常量的类型和值。说着好拗口,直接看例子:

    const (
        a = 1
        b        // b = a = 1
        c = 2
        d        // d = c = 2
    )
  3. 对于有的常量,我们可能找不到某种实际存在的类型与之对应(比如常量的值过大,没有那种整数类型可以容纳。但是它却提供了更高的精度,而且往往可以参加一些复杂的表达式计算),这种常量称之为(Untyped Constants)。对于未指定类型的常量,往往采用下列规则:

    i := 0        // untyped integer;    implicit int(0)
    r := '\000'   // untyped rune;       implicit rune('\000')
    f := 0.0      // untyped float;      implicit float64(0.0)
    c := 0i       // untyped complex;    implicit complex128(0i)

2 复合类型

复合类型(Composite Types)由基本类型构成,包括arrarys、slices、maps、structs。其中,arrarys和struct类型都是集合类型,它们的值在内存中都是连续的。而且都是固定大小的。不同之处是arrarys的所有元素类型相同,而struct的元素类型可以不同。slices和maps都是动态的数据类型,他们的元素都是动态增加的。

2.1 Arrarys和Slices

Go中的数组与C语言中的数组类似,但是有一些差异。数组定义方法:

var 变量名 [size]type

数组使用的一些注意点:

  1. 因为数组是固定长度的,所以定义的时候必须指定数组长度。比如,var a [3]int。当然,也可以使用字符串常量定义,这个时候我们可以将数组大小使用...代替:q := [...]int{1, 2, 3}。可以使用len函数获取数组元素个数。
  2. 数组的长度也是数组类型的一部分。所以var a[3]intvar b[4]int是不同的类型。
  3. 如果数组的元素是可比较的(comparable),那数组也是可比较的。
  4. Go中的数组是值类型的。当我们把数组名作为参数传递给函数时,函数获得的是数组的一个拷贝,而不是引用或指针。其他编程语言中的数组一般都是引用类型的。

因为数组是固定长度的,所以在Go中使用的比较少,更多的是使用Slice(底层类型是数组),它与数组的最大区别就是:Slice是变长的。Slice定义方法(和数组相比,少了一个大小):

var 变量名 []type

Slice使用注意点:

  1. Slice包含三个元素:指针、长度(length)、容量(capacity)。指针指向Slice的第一个元素,长度是目前Slice里面的元素个数,容量是Slice最多能容纳元素个数。capacity大于等于length。可以使用函数lencap获取一个Slice的长度和容量。使用下标访问时不能超过其容量,否则会panic。
  2. 与数组不同,Slice是不可比较的(一个例外是Slice可以和nil比较)。
  3. 如果一个Slice等于nil,那么它的length和capacity都是0,但是反过来不成立。也就是说当我们判断一个Slice是不是空(empty)的时候,要判断len(s) == 0而不是s == nil。但是在使用时,我们可以直接对为nil的Slice操作,和对空Slice操作是一样的(对于很多其他语言,一般都要先判空)。

    var s []int        // len(s)==0, s==nil
    s = nil            // len(s)==0, s==nil
    s = []int(nil)     // len(s)==0, s==nil
    s = []int{}        // len(s)==0, s!=nil
  4. 可以使用append函数往Slice里面添加元素。如果超过了其容量,append会自动扩展其容量。
  5. 可以使用make来创建指定长度和容量的Slice:

    make([]T, len, cap)
    make([]T, len)    // 如果省略cap,则cap==len 

2.2 Maps

定义方式:

var map[key]value

例子:

var test map[string]int
ages := make(map[string]int)

使用注意点:

  1. key的类型必须是可以使用==比较的类型,value类型无限制。
  2. 对于所有map的操作,如果元素在map中不存在,也是可以的。比如,对于查询类的,如果key不存在,就返回value的零值。

    当这样就会有个问题,我们如何区分是元素不存在还是value得值为零?Go提供了一个方法:当我们访问map的时候回返回两个值——第一个就是key所对应的值(key不存在是返回value类型的零值);第二个值是一个bool型的值,用来表示元素是否存在,如果存在就是true,反之false。比如:age, ok := ages["bob"]。我们通过判断变量ok的值就可以知道元素是否存在了。
  3. map之间不可比较(一个例外是map可以和nil比较)。
  4. map不是变量,不能对其取地址。
  5. 许多对于map的操作,比如查询(lookup)、删除(delete)、求长度(len)、range遍历等都对一个为nil的map操作都是安全的,效果和一个空(empty)map是一样的。但是往里面存储元素会panic。这个特性在Go中对于很多符合类型都是通用的。

2.3 Struct

相比于C中的结构体,Go中的结构体有了更多的特性。定义方式:

type 结构体名 struct {
     ...    // 成员,同类型的成员可放在一行写
}

比如:

type Employee struct {
    ID              int
    Name Address    string
    DoB             time.Time
    Position        string
    Salary          int
    ManagerID       int
}

使用注意点:

  1. 访问结构体的成员使用点操作符(.)。Go中没有->操作符,不论是结构体变量还是结构体变量指针都是用点操作符访问结构体变量。
  2. 结构体成全的对外权限也是根据成员变量首字母大小写区分的,大写表示导出,非大写表示未导出。
  3. 结构体S的成员中不能再包含S类型的变量,但可以包含*S(指向S的指针)类型的变量。
  4. 空结构体为:struct{}.
  5. 结构体字面值(struct literal)有两种方式:(1)不使用成员变量名,默认按照成员顺序初始化。(2)指定成员名,这样可以只初始化一部分成员,且没有顺序要求。

    type Point struct{ X, Y int }
    p := Point{1, 2}
    q := Point{Y:2, X:1}    
  6. 如果结构体的每个成员都是可比较的,那么结构体就是可比较的。
  7. 结构体嵌套特性和匿名成员(Anonymous Field)特性。先看下面的例子:

    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

    上面的例子应该是我们在C中常见的用法,但这样访问成员很麻烦。所以Go提供了结构体的匿名成员特性——我们可以使用一个类型来声明结构体的一个成员。这个成员就叫匿名成员:

    type Circle struct {
        Point
        Radius    int
    }
    
    type Wheel struct {
        Circle
        Spokes    int
    }
    
    var w Wheel
    w.X = 8        // equivalent to w.Circle.Center.X
    w.Y = 8        // equivalent to w.Circle.Center.Y
    w.Radius = 5   // equivalent to w.Circle.Radius
    w.Spokes = 20

    对于匿名成员,我们访问时可以简化,而不用给出每个嵌套结构的名字。但对于嵌套结构体的字面值赋值要特别注意:

    // 以下两行是错误的初始化
    w = Wheel{8, 8, 5, 20}    // compile error: unknown field
    w = Wheel{X:8, Y:8, Radius:5, Spokes:20}    // compile error: unknown field
    
    // 以下是正确的赋值
    w = Wheel{Circle{Point{8, 8}, 5}, 20}
    
    w = Wheel{
        Circle: Circle{
            Point: Point{X:8, Y:8},
            Radius: 5,
        },
        Spokes:20,    
    }

    对于第二种正确的赋值,结尾的逗号不能少。其实嵌套结构体不光继承了其他结构体的特性,也继承了它的成员函数,这个后面再介绍。

至此,除接口外的数据类型都介绍完了。我们可以看到,Go从本身设计上面已经帮我们避免了很多可能出问题的地方,比如每种变量一经声明就会有默认值,即使对nil操作一般也不会有问题等。