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

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

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

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

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

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. ubuntun 下安装 node-v0.10.26

    sudo apt-get install g++ curl libssl-dev apache2-utils wget http://nodejs.org/dist/v0.10.26/node-v0. ...

  2. 图片触及翻转效果 css3

    实现图片由左向右飞入回到最初设定位置 ,鼠标浮上去旋转显示另一张图片效果: html: <!DOCTYPE HTML> <html> <head> <meta ...

  3. python笔记6:常用模块

    模块,模块就是封装了特殊功能的代码. 模块分为三种: 自定义模块 第三方模块 内置模块 1.自定义模块 自定义模块就是自己定义的模块,如何import自定义模块,如下: (1)主程序与模块程序在同一目 ...

  4. 实现Netty服务器与CocosCreate通信

    尽量采用无锁化Netty通信处理棋牌房间逻辑 一,棋牌类服务器的特点 1,棋牌类不分区不分服 一般来说,棋牌游戏都是不分区不分服的.所以棋牌类服务器要满足随着用户量的增加而扩展的需要,所以需要设计Ga ...

  5. 【BZOJ】3392: [Usaco2005 Feb]Part Acquisition 交易(spfa)

    http://www.lydsy.com/JudgeOnline/problem.php?id=3392 同1674 #include <cstdio> #include <cstr ...

  6. numpy和TensorFlow的函数

    pycharm  jupyter notebook 环境配置

  7. VS2008 调试出现错误 "Unable to start debugging."

    之前用的好好的调试功能,今天“F5”出现了 "Unable to start debugging." 的错误: 解决办法: 打开工程属性,选择“Debugging”,看看“Debu ...

  8. Eclipse UML插件

    Green UML http://green.sourceforge.net/ AmaterasUML http://amateras.sourceforge.jp/cgi-bin/fswiki_en ...

  9. Request的属性和防止图片被盗链

    Request.AppRelativeCurrentExecutionFilePath,获取当前执行请求相对于应用根目录的虚拟路径,以~开头,比如"~/default.ashx" ...

  10. 在asp.net页面上得到Castle容器的实例

    在项目中使用Castle IOC容器,Asp.net程序中如何得到Castle容器内. 可以如下实现: 1.Gloabal实现接口IContainerAccessor public class Glo ...