首页 > 编程笔记 > Go语言笔记 阅读:2,513

Go语言类型的本质

Go语言中根据类型的特点可以分成三类,分别是内置类型、引用类型和结构类型。下面就来分别为大家介绍一下这三种类型。

内置类型

内置类型是由语言提供的一组类型。分别是数值类型、字符串类型和布尔类型,我们将在后面的讲解中一一为大家介绍这些类型。内置类型本质上是原始的类型。因此,当对这些值进行增加或者删除的时候,会创建一个新值。

基于这个结论,当把这些类型的值传递给方法或者函数时,应该传递一个对应值的副本。让我们看一下标准库里使用这些内置类型的值的函数,如下面代码所示。
func Trim(s string, cutset string) string {
    if s == "" || cutset == "" {
        return s
    }
    return TrimFunc(s, makeCutsetFunc(cutset))
}
通过上面的代码可以看到标准库里 string 包的 Trim 函数。Trim 函数传入一个 string 类型的值 s 作操作,再传入一个 string 类型的值 cutset 用于查找。之后函数会返回一个新的 string 值作为操作结果。这个函数对调用者原始的 string 值的一个副本做操作,并返回一个新的 string 值的副本。

字符串(string)跟整数、浮点数和布尔值一样,本质上是一种很原始的数据值,所以在函数或方法内外传递时,要传递字符串的一份副本。

让我们看一下体现内置类型原始本质的第二个例子,如下面代码所示。
func isShellSpecialVar(c uint8) bool {
    switch c {
    case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
        return true
    }
    return false
}
上面的代码展示了 env 包里的 isShellSpecialVar 函数。这个函数传入了一个 uint8 类型的值,并返回一个 bool 类型的值。注意,这里的参数没有使用指针来共享参数的值或者返回值。调用者传入了一个 uint8 值的副本,并接受一个返回值 true 或者 false。

引用类型

Go语言里的引用类型有如下几个:切片、映射、通道、接口和函数类型。当声明上述类型的变量时,创建的变量被称作标头(header)值。字符串也是一种引用类型。

每个引用类型创建的标头值都包含一个指向底层数据结构的指针。每个引用类型还包含一组独特的字段,用于管理底层数据结构。标头值是为复制而设计的,其中包含一个指针,因此通过复制来传递一个引用类型值的副本,本质上就是在共享底层数据结构。

结构类型

结构类型可以用来描述一组数据值,如果决定在某些东西需要删除或者添加某个结构类型值的时候该结构类型的值不应该被更改,那么需要遵守之前提到的内置类型和引用类型的规范。让我们从标准库里的一个原始本质类型的结构实现开始,代码如下所示。
type Time struct {
    // sec 给出自公元 1 年1 月1 日00:00:00
    // 开始的秒数
    sec int64

    // nsec 指定了一秒内的纳秒偏移,
    // 这个值是非零值,
    // 必须在[0, 999999999]范围内
    nsec int32

    // loc 指定了一个Location,
    // 用于决定该时间对应的当地的分、小时、
    // 天和年的值
    // 只有Time 的零值,其loc 的值是nil
    // 这种情况下,认为处于UTC 时区
    loc *Location
}
上述代码中的 Time 结构选自 time 包。当获取时间值时,应该意识到给定的一个时间点的时间是不能修改的。所以标准库里也是这样实现 Time 类型的。让我们看一下 Now 函数是如何创建 Time 类型的值的,代码如下所示。
func Now() Time {
    sec, nsec := now()
    return Time{sec + unixToInternal, nsec, Local}
}
上述代码中的代码展示了 Now 函数的实现。这个函数创建了一个 Time 类型的值,并给调用者返回了 Time 值的副本。这个函数没有使用指针来共享 Time 值。之后,让我们来看一个 Time 类型的方法,代码如下所示。
func (t Time) Add(d Duration) Time {
    t.sec += int64(d / 1e9)
    nsec := int32(t.nsec) + int32(d%1e9)
    if nsec >= 1e9 {
        t.sec++
        nsec -= 1e9
    } else if nsec < 0 {
        t.sec--
        nsec += 1e9
    }
    t.nsec = nsec
    return t
}
上面代码中的 Add 方法使用按值传递,并返回了一个新的 Time 值。该方法操作的是调用者传入的 Time 值的副本,并且给调用者返回了一个方法内的 Time 值的副本。

至于是使用返回的值替换原来的 Time 值,还是创建一个新的 Time 变量来保存结果,是由调用者决定的事情。

所有教程

优秀文章