go——类型的本质
在声明一个新类型之后,声明一个该类型的方法之前,需要先回答一个问题:这个类型的本质是什么。
如果给这个类型增加或删除某个值,是要创建一个新值,还是要更改当前的值?
如果是要创建一个新值,该类型的方法就使用值接收者。如果是要修改当前值,就使用指针接收者。
这个答案也会影响程序内部传递这个类型的值的方式:是按值做传递,还是按指针做传递。保持传递的一致性很重要。
这个背后的原则是,不要只关注某个方法是如何处理这个值的,而是要关注这个值的本质是什么?
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——类型的本质的更多相关文章
- JS中数值类型的本质
一.JS中的数值类型 众所JS爱好友周知,JS中只有一个总的数值类型--number,它包含了整型.浮点型等数值类型.其中,浮点数的实现思想有点复杂,它把一个数拆成两部分来存储.第一部分是有效位数,也 ...
- Go语言类型的本质
如果给这个类型增加或者删除某个值,是要创建一个新值,还是要更改当前的值? 如果是要创建一个新值,该类型的方法就使用值接收者. 如果是要修改当前值,就使用指针接收者. 这个答案也会影响程序内部传递这个类 ...
- enum类型的本质(转)
原地址:http://www.cppblog.com/chemz/archive/2007/06/05/25578.html 至从C语言开始enum类型就被作为用户自定义分类有限集合常量的方法被引入到 ...
- CLR via C#深解笔记二 - 类型设计
类型基础 所有类型都从System.Object派生 CLR要求所有对象都用new 操作符来创建. Employee e = new Employee("Constructor Para ...
- 你好,C++(13)这道单选题的答案是A、B、C还是D?3.7 枚举类型
3.7 枚举类型 除了之前我们介绍的数值数据和文字数据之外,在现实世界中,常常还会遇到这样一类数据:一道单选题的答案只能是A.B.C.D四个选项中的某一个:红绿灯的颜色只能是红色,绿色和黄色中的某一 ...
- .Net 类型、对象、线程栈、托管堆运行时的相互关系
JIT(just in time)编译器 接下来的会讲到方法的调用,这里先讲下JIT编译器.以CLR书中的代码为例(手打...).以Main方法为例: static void Main(){ Cons ...
- [CLR via C#]4. 类型基础及类型、对象、栈和堆运行时的相互联系
原文:[CLR via C#]4. 类型基础及类型.对象.栈和堆运行时的相互联系 CLR要求所有类型最终都要从System.Object派生.也就是所,下面的两个定义是完全相同的, //隐式派生自Sy ...
- c++中的类型擦除
(原创)c++中的类型擦除 c++11 boost技术交流群:296561497,欢迎大家来交流技术. 关于类型擦除,可能很多人都不清楚,不知道类型擦除是干啥的,为什么需要类型擦除.有必要做个说明,类 ...
- 4.2Python数据类型(2)之布尔类型
返回总目录 目录: 1.布尔类型的概念和分类: 2.布尔类型的本质 3.布尔类型的应用 (一)布尔类型的概念和分类: (1)概念: 布尔类型(bool)就是用于判断真假的数据类型 (2)分类: Pyt ...
随机推荐
- 基于Redis实现延迟队列
背景 在后端服务中,经常有这样一种场景,写数据库操作在异步队列中执行,且这个异步队列是多进程运行的,这时如果对同一资源进行写库操作,很有可能产生数据被覆盖等问题,于是就需要业务层在更新数据库之前进行加 ...
- c# 中的UserControl是什么 用户控件和自定义控件有什么区别
用户控件是许多控件的集成 自定义控件是自己写一个控件类,或者继承已有的控件类 复合控件是封装在公共容器内的 Windows 窗体控件的集合.这种控件有时称为“用户控件”.包含的控件称为“构成控件”. ...
- flutter table 在showModalBottomSheet中
问题是,不知道为什么又可以了.原来是显示黑屏,没有输出. showModalBottomSheet<void>( context: context, builder: (BuildCont ...
- Xcode 调试方法总结
前言:编写代码过程中出现错误.异常是不可避免的.通常我们都需要进行大量的调试去寻找.解决问题.这时,熟练掌握调试技巧将很大程度上的提高工作效率.接下来就说说开发过程中Xcode的调试方法. 1. En ...
- 加速I/O的基本规则
作为这个讨论的开始,这里有几个如何加速I/O的基本规则: 1. 避免访问磁盘 2. 避免访问底层的操作系统 3. 避免方法调用 4. 避免个别的处理字节和字符 很明显这些规则不能在所有的问题上避免,因 ...
- jQuery DataTables添加自定义多个搜索条件
效果如下: 一.在前台页面定义输入搜索条件的文本框 <div class="ibox-tools"> <span>年度</span> @Html ...
- Torch-RNN运行过程中的坑 [2](Lua的string sub函数,读取中文失败,乱码?)
0.踩坑背景 仍然是torch-rnn/LanguageModel.lua文件中的一些问题,仍然是这个狗血的LM:encode_string函数: function LM:encode_string( ...
- websphere web.xml
解决WAS更新web.xml文件不生效的问题(web_merged.xml是罪魁祸首) 问题原因分析 近日碰到更新web.xml文件到WAS服务器(WebSphere Application Se ...
- [分享]JavaScript Quick Reference Card
pdf文件:https://files.cnblogs.com/files/MakeView660/JavaScript_Quick_Reference_Card.pdf
- ViewPager滑动引导页
ViewPager实现Animation动画引导页 http://blog.csdn.net/ye_scofield/article/details/44831357 SurfaceView实现动 ...