在声明一个新类型之后,声明一个该类型的方法之前,需要先回答一个问题:这个类型的本质是什么。

如果给这个类型增加或删除某个值,是要创建一个新值,还是要更改当前的值?

如果是要创建一个新值,该类型的方法就使用值接收者。如果是要修改当前值,就使用指针接收者。

这个答案也会影响程序内部传递这个类型的值的方式:是按值做传递,还是按指针做传递。保持传递的一致性很重要。

这个背后的原则是,不要只关注某个方法是如何处理这个值的,而是要关注这个值的本质是什么?

1.内置类型

内置类型是由语言提供的一组类型。我们已经见过这些类型,分别是数值类型、字符串类型和布尔类型。

这些类型本质上是原始的类型。因此,当对这些值进行增加或删除的时候,会创建一个新值。

基于这个结论,当把这些类型的值传递给方法或者函数时,应该传递一个对应值的副本。

让我们看一下标准库里使用这些内置类型的值的函数。

func Trim(s string, cutset string) string {
if s == "" || cutset == "" {
return s
}
return TrimFunc(s, makeCutsetFunc(cutset))
}

可以看到标准库里strings包的Trim函数。Trim函数传入一个string类型的值做操作,再传入一个string类型的值用于查找。

之后函数会返回一个新的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
}

这个函数传入了int8类型的值,并返回一个bool类型的值。

注意,这里的参数没有使用指针来共享参数的值或者返回值。调用者传入一个uint8值得副本,并接受一个返回值true或false。

2.引用类型

Go语言里得引用类型有如下几个:切片、字典、通道、接口和函数类型。

当声明上述类型得变量时,创建的变量被称作标头(header)值。

从技术细节上说,字符串也是一种引用类型。

每个引用类型创建的标头值是包含一个指向底层数据结构的指针。

每个引用类型还包含一组独特的字段,用于管理底层数据结构。

因为标头值是为复制而设计的,所以永远不需要共享一个引用类型的值。

标头值里包含一个指针,因此通过复制来传递一个引用类型的值的副本,本质上就是在共享底层数据结构。

type IP []byte

上面代码展示了一个名为IP的类型,这个类型被声明为字节切片。

当要围绕相关的内置类型或者引用类型来声明用户定义的行为时,直接基于已有类型来声明用户定义的类型会很好用。

编译器只允许为命名的用户定义的类型声明方法。

func (ip IP) MarshalText() ([]byte, error) {
if len(ip) == 0 {
return []byte(""), nil
}
if len(ip) != IPv4len && len(ip) != IPv6len {
return nil, &AddrError{Err: "invalid IP address", Addr: hexString(ip)}
}
return []byte(ip.String()), nil
}

MarshalText方法是用IP类型的值接收者声明的。

一个值接收者,正像预期的那样通过复制来传递引用,从而不需要通过指针来共享引用类型的值。

这种传递方法也可以应用到函数或者方法的参数传递。

// ipEmptyString像ip.String一样
// 只不过在没有设置ip时会返回一个空字符串
func ipEmptyString(ip IP) string {
if len(ip) == 0 {
return ""
}
return ip.String()
}

上述代码中有一个ipEmptyString函数。这个函数需要传入一个IP类型的值。

再一次,你可以看到调用者传入的是这个引用类型的值,而不是通过引用共享给这个函数。

调用者将引用类型的值的副本传入这个函数。这种方法也适合函数的返回值。

引用类型的值在其它方面像原始的数据类型的值一样对待。

3.结构类型

结构类型可以用来描述一组数据值,这组值的本质既可以是原始的,也可以是非原始的。

如果决定在某些东西需要删除或添加某个结构类型的值时该结构类型的值不应该被更改,那么需要遵守之前提到的内置类型或引用类型的规范。

type Time struct {
// wall and ext encode the wall time seconds, wall time nanoseconds,
// and optional monotonic clock reading in nanoseconds.
//
// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
// The nanoseconds field is in the range [0, 999999999].
// If the hasMonotonic bit is 0, then the 33-bit field must be zero
// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
// unsigned wall seconds since Jan 1 year 1885, and ext holds a
// signed 64-bit monotonic clock reading, nanoseconds since process start.
wall uint64
ext int64 // loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location
}

  上面代码是源码中time包中一段代码。

当思考时间的值时,你应该意识到给定的一个时间点的时间是不能修改的。所以标准库里也是这样实现Time类型的。

让我们看一下Now函数是如何创建Time类型的值的。

func Now() Time {
sec, nsec, mono := now()
sec += unixToInternal - minWall
if uint64(sec)>>33 != 0 {
return Time{uint64(nsec), sec + minWall, Local}
}
return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}

这个函数创建了一个Time类型的值,并给调用者返回time值得副本。这个函数没有使用指针来共享Time值。

来看一下Time类型的方法。

func (t Time) Add(d Duration) Time {
dsec := int64(d / 1e9)
nsec := t.nsec() + int32(d%1e9)
if nsec >= 1e9 {
dsec++
nsec -= 1e9
} else if nsec < 0 {
dsec--
nsec += 1e9
}
t.wall = t.wall&^nsecMask | uint64(nsec) // update nsec
t.addSec(dsec)
if t.wall&hasMonotonic != 0 {
te := t.ext + int64(d)
if d < 0 && te > t.ext || d > 0 && te < t.ext {
// Monotonic clock reading now out of range; degrade to wall-only.
t.stripMono()
} else {
t.ext = te
}
}
return t
}

  这个方法使用值接收者,并返回了一个新的Time的值。

该方法操作的是调用者传入的Time值的副本,并且给调用者返回了一个方法内的Time值的副本。

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

大多数情况下,结构类型的本质并不是原始的,而是非原始的。

这种情况下,对这个类型的值做增加或者删除操作应该更改值本身。

当需要修改值本身时,在程序中其它地方,需要使用指针来共享这个值。

go——类型的本质的更多相关文章

  1. JS中数值类型的本质

    一.JS中的数值类型 众所JS爱好友周知,JS中只有一个总的数值类型--number,它包含了整型.浮点型等数值类型.其中,浮点数的实现思想有点复杂,它把一个数拆成两部分来存储.第一部分是有效位数,也 ...

  2. Go语言类型的本质

    如果给这个类型增加或者删除某个值,是要创建一个新值,还是要更改当前的值? 如果是要创建一个新值,该类型的方法就使用值接收者. 如果是要修改当前值,就使用指针接收者. 这个答案也会影响程序内部传递这个类 ...

  3. enum类型的本质(转)

    原地址:http://www.cppblog.com/chemz/archive/2007/06/05/25578.html 至从C语言开始enum类型就被作为用户自定义分类有限集合常量的方法被引入到 ...

  4. CLR via C#深解笔记二 - 类型设计

    类型基础 所有类型都从System.Object派生   CLR要求所有对象都用new 操作符来创建. Employee e = new Employee("Constructor Para ...

  5. 你好,C++(13)这道单选题的答案是A、B、C还是D?3.7 枚举类型

    3.7  枚举类型 除了之前我们介绍的数值数据和文字数据之外,在现实世界中,常常还会遇到这样一类数据:一道单选题的答案只能是A.B.C.D四个选项中的某一个:红绿灯的颜色只能是红色,绿色和黄色中的某一 ...

  6. .Net 类型、对象、线程栈、托管堆运行时的相互关系

    JIT(just in time)编译器 接下来的会讲到方法的调用,这里先讲下JIT编译器.以CLR书中的代码为例(手打...).以Main方法为例: static void Main(){ Cons ...

  7. [CLR via C#]4. 类型基础及类型、对象、栈和堆运行时的相互联系

    原文:[CLR via C#]4. 类型基础及类型.对象.栈和堆运行时的相互联系 CLR要求所有类型最终都要从System.Object派生.也就是所,下面的两个定义是完全相同的, //隐式派生自Sy ...

  8. c++中的类型擦除

    (原创)c++中的类型擦除 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 关于类型擦除,可能很多人都不清楚,不知道类型擦除是干啥的,为什么需要类型擦除.有必要做个说明,类 ...

  9. 4.2Python数据类型(2)之布尔类型

    返回总目录 目录: 1.布尔类型的概念和分类: 2.布尔类型的本质 3.布尔类型的应用 (一)布尔类型的概念和分类: (1)概念: 布尔类型(bool)就是用于判断真假的数据类型 (2)分类: Pyt ...

随机推荐

  1. Prerender Application Level Middleware - ASP.NET Core Middleware

    In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...

  2. pandas基础: Series和DataFrame的简单介绍

    一.pandas 是什么 pandas 是基于 NumPy 的一个 Python 数据分析包,主要目的是为了数据分析.它提供了大量高级的数据结构和对数据处理的方法. pandas 有两个主要的数据结构 ...

  3. linux DNS服务

    DNS服务器的安装和配置 首先在终端输入命令#vi  /etc/apt/sources.list 输入更新源 # kali repos installed by TARDIS deb http://h ...

  4. HDU 5892 Resident Evil

    题目链接:传送门 题目大意:有50种动物,给你n*n的矩阵,m次操作,P代表加入操作,在左上角 x1,y1 到右下角 x2,y2,的矩形范围内加入 种类为x,数量为y的动物. Q代表询问操作,在左上角 ...

  5. Jenkins publish over ssh 路劲配置问题 记录

    每次通过jenkins 实现  maven项目编辑后 自动通过 ssh发布到 服务器的功能时,对配置的路劲有疑问,特整理出来 前提:服务器路径   /home/ubuntu/aps 目标: 构建后的j ...

  6. GIT界的神探--bisect

    GIT界的神探--bisect 今天我们需要使用git命令中的bisect破获一起凶杀案,下面请看具体案件: 在一个git仓库中有a,b,c,d,e,f,g,h,i,j,k这几个提交,在k提交中发现软 ...

  7. Post And Get接口测试

    public class TestPost {   public static String do_post(String url, Map<String, String> params) ...

  8. node.js开发学习一HelloWorld

    前言:由于公司业务需求,最近启动了node.js的开发任务,想把自己的开发学习历程记录记录下来,可以增加记忆,也方便查找.虽然对javascript有一定的了解,但是刚接触node.js的时候,发现还 ...

  9. 巨蟒python全栈开发flask3

    首先,我们新建一个项目: 这个时候,我们调用ab函数,可以在所有的模板中使用. 上边是一个特殊装饰器, 1.flask特殊装饰器 下面说几个特殊的装饰器 再请求之前的装饰器 运行: 这个时候,服务端打 ...

  10. LCS/LIS/LCIS 模板总结

    /************************* LCS/LIS/LCIs模板总结: *************************/ /*************************** ...