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

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

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

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

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

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. hdu6007 Mr. Panda and Crystal 最短路+完全背包

    /** 题目:hdu6007 Mr. Panda and Crystal 链接:http://acm.hdu.edu.cn/showproblem.php?pid=6007 题意:魔法师有m能量,有n ...

  2. java语言中public、private、protected三个关键字的用法,重写和重载的区别。

    java语言中public.private.protected三个关键字的用法,重写和重载的区别. 解答: 作用域 当前类 同包 子类 其它 public √ √ √ √ protected √ √ ...

  3. 消息队列ipc的一些设置

    Linux IPC 参数设定- 命令方式: echo 80 > /proc/sys/vm/overcommit_ratio, etc MSGMNB 每个消息队列的最大字节限制. MSGMNI 整 ...

  4. jQuery学习笔记1——操作属性

    一.获得和设置内容 三个简单实用的用于 DOM 操作的 jQuery 方法: text() - 设置或返回所选元素的文本内容, 得到匹配元素集合中每个元素的文本内容结合,包括他们的后代, 即由所有匹配 ...

  5. 【转】CStdioFile UNICODE编译 英文系统下读取中文汉字乱码解决

    转载出处:http://www.cnblogs.com/ct0421/p/3242418.html 函数原形为:char *setlocale( int category, const char *l ...

  6. poj 2531(dfs)

    题目链接:http://poj.org/problem?id=2531 思路:由于N才20,可以dfs爆搞,枚举所有的情况,复杂度为2^(n). #include<iostream> #i ...

  7. PANDAS 数据合并与重塑(join/merge篇)

    pandas中也常常用到的join 和merge方法 merge pandas的merge方法提供了一种类似于SQL的内存链接操作,官网文档提到它的性能会比其他开源语言的数据操作(例如R)要高效. 和 ...

  8. Http服务器实现文件上传与下载(三)

    一.引言 在前2章的内容基本上已经讲解了整个的大致流程.在设计Http服务器时,我设计为四层的结构,最底层是网络传输层,就是socket编程.接着一层是请求和响应层,叫做Request和Respons ...

  9. c++ wchar_t

    ·C语言相关 对应于char, C语言中也有宽字符内型wchar_t.wchar_t被定义为: typedef unsigned short wchar_t ;显然它是16位的.wchar_t类型的常 ...

  10. dns解决测试微信二级域名访问问题

    背景介绍: 1:解决本地不能通过域名访问问题: 2:解决微信设置二级域名且本地iis站点使用非80端口号问题: ps:网站中微信部分在global中设置了重定向,代码已经修改为必须通过“wechat. ...