深入浅出Go常量

来源:互联网 发布:自由行 软件 编辑:程序博客网 时间:2024/06/03 12:43
  • 欢迎转载,请保留转载来源:http://blog.csdn.net/qq_24328101/article/details/78700847

      • 摘要
      • 问题背景
      • 问题原因扼要
      • 官方blog翻译
      • 介绍
      • 背景C
      • 术语定义
      • 字符串常量
        • 默认类型
        • 由语法决定的默认类型
        • 布尔值
        • 浮点数
        • 复数
        • 整型
        • 练习最大的unsigned int值
        • 数值
      • 一些补充
        • 编译器在实现常量的时候必须遵循如下规则
        • go的类型转换规则

  • 摘要:

    本文尝试对GO当中的常量的使用细节展开探讨,结合本人最近遇到的实际问题,通过翻译官方在2014/08/25发的关于Constants的blog以及结合stackoverflow上的一些讨论,GO的中文文档,希望能做到抛砖引玉的作用.

  • 问题背景:

    前两天我在写go的时候,遇到了这样的一个奇怪的报错:

    v:= []time.Duration{  time.Duration(0.5*float64(time.Minute)),//OK  time.Duration(1.1*float64(time.Minute)),//ERROR  time.Duration(1.7*float64(time.Minute)),//OK}

    当时我就非常懵逼,无论是Goland(IDE)还是实际编译都一直报错说常量丢失精度,不能进行类型转换,为何只有1.1不行,0.5,1.7就OK呢?难道触发了什么奇怪的BUG?而且,当我把这一行声明单独抽离出来如下操作的时候,缺又是OK的:

    time.Duration(1.1*float64(time.Minute)),//ERRORv:=1.1*float64(time.Minute)time.Duration(v)//OK

    带着这样的疑问,我开始去啃文档生肉.

  • 问题原因扼要

    考虑到后面的内容太长很多人不太愿意看,所以我就尽量简短地说出理由,不明所以的请看下面的探讨:D

    原因:由于GO严格的类型转换机制,不存在C-like语言的隐式类型转换,不符合转换规则的不可以被类型转换.time.Minute其实是个值为60000000000的constant,time.Duration 其实是int64(见声明type time.Duration int64 ), 而1.1*float64(time.Duration) 得到的是untyped的6600000000.1.因为time.Duration足够大从而在浮点运算中把误差给扩大了,从而导致上诉表达式实际上是:

    int64(3000000000)//okint64(6600000000.1)//errorint64(10200000000)//ok

    因为这个转换不符合类型转换规则之一的:数值x转换到类型T的时候,不可以发生精度丢失或者超出类型T的表达范围,因此表达式time.Duration(1.1*float64(time.Minute))不可通过编译.

    但是因为常量的类型转换检查是编译时行为,因此语句:

    v:=1.1*float64(time.Minute)time.Duration(v)//OK

    绕开了这个编译时的常量类型转换检测,到了运行时才进行类型转换,所以pass(关于这一点本人还有疑问)

  • 官方blog翻译:

    * 下面尝试对官方关于constant的博客https://blog.go-zh.org/constants进行翻译,本人水平有限,如有不确定的地方我也会贴上原文,翻译仅供参考 *

    介绍

    GO是一门静态类型的语言,它不允许对两个不同的数值类型进行运算操作.你不能float64数值加int数值 ,甚至你不能int数值加int32,尽管你写1e6*time.Second 或者 math.Exp(1) 甚至 1<<('\t'+2.0) 都是合法的.在GO里,常量和变量不一样,它表现得更像常规的数值(regulat numbers),这篇blog会解释为什么会这样以及它存在的意义

    背景:C

    在早期设计GO的时候,我们讨论了由于C风格的语言允许你对两个不同类型的数值同时进行操作引发的一系列问题(译注:从句真长不好翻).很多神秘的bug,crash,还有各种潜在问题都是由这种混合了不同位长,”有无符号”的整型运算引发的.尽管对于一个经验丰富的C程序猿来说,这样的计算结果:

    unsigned int u = 1e9;long signed int i = -1;... i + u ...

    对于他们来说是熟悉的,但结果这不是显而易见的(it isn`t a prioi obvious),结果是多大呢?值是多少呢?有符号还是无符号呢?

    恐怖的bug就潜藏在这种地方

    C有一系列叫做”常用运算转换”(the usual arithmetic conversions)的规则(译注:想必大家都被1+3/2 == 2折磨过),and it is an indicator of their subtlety that they have changed over the years (introducing yet more bugs, retroactively)(译注:抱歉我实在看不懂subtlety是什么,我猜它是说这体现了C的subtlety(隐式转换规则)多年以来的变化,这规则也催生)了很多BUG.

    当设计GO的时候,我们决定绕过这个坑,强制要求不能对不同类型的数值进行运算,如果你要做i+u的运算,你必须明确你想要什么样的结果,给定如下声明:

    var u uintvar i int

    你可以写成 uint(i)+u or i+int(u),两种写法都要明确它的意义和参与加法的具体类型,不像C那样,你不可以写i+u ,你甚至不能int+int32 ,尽管int是一个32bit的类型

    这规则消除了很多常见的bug和失败,这是GO的重要特性.但这也有代价:这要求程序员在进行混合类型计算的时候需要写很多类型转换的代码来明确表达他们的意图(译注:想一下你要做一个浮点数扩大整型数倍然后取整型, intvalue2=int( float(intvale)*floatvalue )和C的intvalue2=intvalue*float)

    那么对于常量呢?对于上诉u和i的声明,i = 0 or u = 0是怎么合法的呢?0的类型又是什么?在这样的短代码里i:=int(0)要求你对常量使用类型转换是让人难以接受的

    我们很快地意识到了解决办法在于设计和C-like语言不一样的GO常量.经过思考和实验,我们最终采取了不需要让程序员整天对常量进行类型转换的设计.

    总而言之,很多时候在GO里常量会如你所愿地工作.Let’s see how that happens.

    术语定义

    首先我们来定义一下:在GO里, const是一个用来声明数值变量(比如2,3.14,”hiro”等)的名称的关键字,那样的值,无论命名与否,在GO里都被叫做constants.constants也可以通过constants组成的表达式来获得,比如2+3 or 2+3i or math.Pi/2 or ("go"+"pher").

    有些语言没有常量,有一些会有更常见的的关于constant的定义或者对于const这个关键字的应用.举个例子,在C/C++中,const是一个类型修饰符,它能表达出很复杂的属性(t can codify more intricate properties of more intricate values.)(译注:你可知道C++指针和const组合的四种写法)

    但是在GO里,一个constant仅仅是一个简单的,不可修改的值.从这开始我们就仅讨论Go的constant(译注:下文开始我也恢复用常量表达constant这个词)

    字符串常量

    数值常量有很多种—integers, floats, runes(字符,符文), signed(有符号的), unsigned(无符号的), imaginary(虚数), complex(复数).我们就挑最简单的string开始说起.字符串常量非常容易理解,并且提供了一个比较小的空间来探索GO的constant(译注:就是没有太多复杂的条例相对简单的意思)

    一个字符串常量由两个双引号包裹起来(当然GO也有另外一种表达字符串的方式是用两个反单引(esc下面那个),为了讨论方便我们就统一用两个双引号的格式).这是一个字符串常量的例子:

    "Hello,世界"

    (想要了解更多关于string的解释和描述,可以看这篇blog:https://blog.go-zh.org/strings)

    这个字符串常量的类型是什么?很明显是string啦,然而并不是(译注:官方光速打脸)

    这是一个untyped string常量,这意味着它是一个文本值的常量,但是还没有明确的类型.是的,它是一个字符串,但还不是GO类型里的string.即使你给它赋予了变量名了,它也还是不是string:

    const hello = "Hello, 世界"

    经过这个声明,hello这个变量也是一个untyped string 常量,一个untyped string 常量仅是一个值,它还没有被定义为要接受严格类型限制具体的类型,当然它也就可以参与混合类型运算了(译注:既然GO自己要求不能混合类型计算,那我无类型就行啦,我不当有类型的变量啦JOJO)

    正是因为这种untyped常量的概念才让我们比较自由地使用常量.

    那么,什么是typed的字符串常量呢?这里有一个例子:

    const typedHello string = "hello,世界"

    注意这个声明在等号的前面有一个明确的string,这意味着变量typedHello有一个Go类型string,并且不能赋值给其他有不同的GO类型的变量.这意味着,这样的代码是work的:

    var s strings = typedHellofmt.Println(s)

    但这个就不work了:

    type MyString stringvar m MyStringm = typedHello // Type errorfmt.Println(m)

    变量m的类型是MyString,不能被别的类型的变量赋值,它只能被MyString类型的值赋值,比如:

    const myStringHello MyString = "Hello, 世界"m = myStringHello // OKfmt.Println(m)

    或者通过强制类型转换:

    m = MyString(typedHello)fmt.Println(m)

    回到我们的untyped string 类型,它有一个很有用的特性:因为它是无类型的,把它赋值给一个有类型的变量不会引起类型错误.因此,我们可以写:

    m = "Hello, 世界"

    or

    m = hello

    因为不像typed的常量typedHellomyStringHello,untyped常量"Hello, 世界" and hello没有具体类型,把他们赋值给任何兼容strings的类型都不会有问题

    当然,这些untyped的字符串常量是string,因此他们能被用在允许string类型的场合,但他们本身是没有string这个类型.

    默认类型

    作为一个GO程序猿,你肯定见到过很多这样的变量声明:

    str := "Hello, 世界"

    到了现在,你可能会有疑问:”如果常量是untyped,那么变量str怎么在这个变量声明当中获取具体类型呢?”答案是一个untyped的常量有相应的默认类型.当一个常量需要被转换成某个具体类型的值,而这个具体类型又没有提供的时候,会隐式地提供一个类型.对于untyped的字符串常量,它默认的类型显然是string了,因此

    str := "Hello, 世界"

    or

    var str = "Hello, 世界"

    同样等价于

    var str string = "Hello, 世界"

    一种理解untyped常量的思路是,把他们当作一堆居住在幻想乡的里的变量,幻想乡里有比GO的类型系统更少的限制.但是要对他们进行操作,就要把他们赋值到变量里.这个时候,变量(而不是常量自身)就需要一个具体的类型

    而常量就要告诉它,它应该是什么类型.在这个例子里,str变量就变成了string类型,因为untyped 字符串常量给了它自己的默认类型:string

    在上面的声明里,一个变量同时声明了类型以及它的初始值.(译注:因此我们可以清楚常量该给什么类型给变量)而有些时候,常量值要变成什么具体类型并不是那么明晰的.举个例子考虑一下下面的情况:

    fmt.Printf("%s", "Hello, 世界")

    函数fmt.Printf 的签名是:

    func Printf(format string, a ...interface{}) (n int, err error)

    which is to say its arguments (after the format string) are interface values. What happens when fmt.Printf is called with an untyped constant is that an interface value is created to pass as an argument, and the concrete type stored for that argument is the default type of the constant. This process is analogous to what we saw earlier when declaring an initialized value using an untyped string constant.

    它的第二个参数是interface类型值.当我们调用fmt.Printf并且提供untyped的常量作为参数的时候,会用该常量来构造一个interface变量来传递参数,然后这个interface的变量里保存的类型信息(1)里,正是常量的默认类型信息.这跟我们之前看到的,用untyped字符串常量值来直接声明一个变量的过程是类似的.(2)(3)

    (译注:1.这段话涉及到interface的内存模型,根据我的理解,一个interface里其实保存了变量的实际类型信息和变量指针,所以这里说的interface保存的类型是指这个,建议大家去阅读一下关于interface的内存模型的文章,毕竟interface也是个大坑,本人才疏学浅就不误人子弟了

    2.这段话让我深深体会到中英差别orz,于是我就贴了原文,大家酌情观看对比.我的从句翻译怎么样都很别扭,我就尝试着用我自己的表达复述一次:对于函数参数是interface{}类型的,go会用常量值来构造一个interface{}类型的变量然后传参进去,然后因为interface{}里面会保存实际类型信息,这个信息就会被这个常量的默认类型填充.

    为此我构造了一个例子,也印证了我对这段话的理解:

    type MyString stringfunc test(t interface{}){   o:=t.(MyString)   fmt.Printf("%T,%v",o,o)}func main ()  {    ms := "hiro"   test(ms)//Error}

    这会引发一个运行时错误panic: interface conversion: interface {} is string, not main.MyString

    3.它所说的类似,我猜就是指常量总会有办法提供一个默认的信息给对方,让别人有类型信息可以接下去干活)

    你可以看看下面样例的结果,我们用了%v来打印变量值,用了%T来打印变量类型:

    fmt.Printf("%T: %v\n", "Hello, 世界", "Hello, 世界")//string: Hello, 世界fmt.Printf("%T: %v\n", hello, hello)//string: Hello, 世界

    如果常量(指加了const关键字)是有类型的,那么它的类型信息也会被包含在interface里:

    const myStringHello MyString = "Hello, 世界"fmt.Printf("%T: %v\n", myStringHello, myStringHello)//main.MyString: Hello, 世界

    (如果你想了解更多关于interface的细节,可以看这篇blog的第一小节 this blog post.))

    总而言之,一个typed的常量遵循所有typed变量的所遵循的规则.另一方面,一个untyped的常量并不携带Go的具体类型,它能更自由地匹配到不同类型.它仅有一个默认的类型信息,除此之外的类型信息都没有了. It does, however, have a default type that is exposed when, and only when, no other type information is available.(译注:水平有限,翻译酌情观看)

    由语法决定的默认类型

    untyped常量的默认类型是由语法决定的,对于字符串常量,就只有string类型了,但是对于数值类常量,它的隐式类型种类会更多.整型常量是int,浮点常量是float64,字符常量是rune(int32的一个别名),虚数常量是complex128.下面我们来打印一下这些常用的常量的具体类型

    fmt.Printf("%T %v\n", 0, 0)   fmt.Printf("%T %v\n", 0.0, 0.0)fmt.Printf("%T %v\n", 'x', 'x')fmt.Printf("%T %v\n", 0i, 0i)//--- output is ----//int 0//float64 0//int32 120//complex128 (0+0i)

    (练习:解释一下’x’的运行结果)

    (译注:go里库文件有这样的一行:type rune = int32 并且它在上面注释说rune总是等价与int32,所以这里打印出来会是int32,即使我rune('x') ,然后120不用说了吧,ascii码)

    布尔值

    所有关于untyped字符串常量的说法都适用于untyped布尔值常量.值true and false是可以被赋值到任何布尔类型的untyped布尔值常量.但是一旦他们被给予具体类型,布尔变量就不能混合着用了:

      type MyBool bool  const True = true  const TypedTrue bool = true  var mb MyBool  mb = true      // OK  mb = True      // OK  mb = TypedTrue // Bad  fmt.Println(mb)//--- output is ---// cannot use TypedTrue (type bool) as type MyBool in assignment

    运行一下上面的代码然后看看输出什么(译注:已经帮你们跑了),然后注释掉//Bad那一行再跑一次(译注:输出true),这里遵循的规则和上面的字符串常量一样.

    浮点数

    Floating-point constants are just like boolean constants in most respects. Our standard example works as expected in translation:

    浮点数常量大部分情况下和布尔值常量差不多,我们的标准样例的经过转换后,运行的结果也会如我们所预期

      type MyFloat64 float64  const Zero = 0.0  const TypedZero float64 = 0.0  var mf MyFloat64  mf = 0.0       // OK  mf = Zero      // OK  mf = TypedZero // Bad  fmt.Println(mf)//---output is ---//cannot use TypedZero (type float64) as type MyFloat64 in assignment

    然而有一个小问题,在GO里有两种浮点数类型:float32 and float64,浮点数常量的默认值是float64,尽管把untyped浮点数常量赋值给float32没什么问题:

      var f32 float32  f32 = 0.0  f32 = Zero      // OK: Zero is untyped  f32 = TypedZero // Bad: TypedZero is float64 not float32.  fmt.Println(f32)

    浮点数值是用来介绍溢出和数值范围(the range of values)的好地方.

    数值常量居住在一个”支持任意精度”(arbitrary-precision)的幻想乡,他们只是常规的数据(regular numbers).(译注:这里的arbitrary-precision其实应该是指常量数据的机器位长异常的长:https://go-zh.org/ref/spec#Constants,下面我也会翻译这一小节的内容)但当他们要赋值的时候,他们要能够被目标类型所表达.我们可以定义一个非常大的常量:

      const Huge = 1e1000

    这仅是个数值,然而我们并不能赋值甚至打印它,下面的语句也会编译失败

      fmt.Println(Huge)

    这个错误: “constant 1.00000e+1000 overflows float64”, 是对的. 但 Huge 可能是游泳的:我们可以把它用在和其他常量组成的表达式中,然后使用这些表达式的值,如果这些值能被float64表达:

      fmt.Println(Huge / 1e999)

    结果是 10, 正如所我们想

    另一方面,浮点常量有一个非常高的精度,因此使用他们的运算会得到更好的准确性,在math包里定义的常量提供了比float64所能表达的范围长更多的数据,这里是math.Pi的定义:

    Pi    = 3.14159265358979323846264338327950288419716939937510582097494459

    当这个值被赋值到变量的时候,会丢失一些精度,这个赋值操作会产生一个最接近原来值的float64(或者float32):

    pi := math.Pifmt.Println(pi)

    这段代码会打印 3.141592653589793.

    Having so many digits available means that calculations like Pi/2 or other more intricate evaluations can carry more precision until the result is assigned, making calculations involving constants easier to write without losing precision. It also means that there is no occasion in which the floating-point corner cases like infinities, soft underflows, and NaNs arise in constant expressions. (Division by a constant zero is a compile-time error, and when everything is a number there’s no such thing as “not a number”.)

    常量能用这么长的机器位长意味着类似Pi/2的计算或者其他更复杂的计算可以用着很高的精度,直到这个常量被赋值为止,这样涉及到常量的运算可以保留着很高的精度.这也意味着对于浮点数常量运算来说不会有边界问题:无穷大,下溢,NaN.(除以常数零是一个编译时错误,并且当所有东西都是一个数的时候,就没有NaN这种说法了)

    (译注:这一段我觉得简要来说就是常量占有非常长的机器位长,可以做出对于具体类型而言几乎没有限制的数值运算操作而不引起经典的问题,但总归表达位长有限,只是可以忽略了(我猜),并且因为把常量视为单纯的01,纯机器码运算不带附加意义,所以也不存在NaN这种东西(讨论范围应该限于数值型常量))

    复数

    复数常量的表现和浮点数常量很像,这里有一份我们已经很熟悉的测试代码:

      type MyComplex128 complex128  const I = (0.0 + 1.0i)  const TypedI complex128 = (0.0 + 1.0i)  var mc MyComplex128  mc = (0.0 + 1.0i) // OK  mc = I            // OK  mc = TypedI       // Bad  fmt.Println(mc)//---output ----//cannot use TypedI (type complex128) as type MyComplex128 in assignment

    复数常量的默认类型是complex128 ,一个包含两个float64值的高精度版本

    为了看起来清晰,我们在样例了写了复数的完整表达式 (0.0+1.0i),但是这个可以缩短成 0.0+1.0i, 1.0i 甚至1i.

    我们来捣个蛋~,我们知道在GO里,一个数值型的常量仅仅是一个数,如果这个复数常量没有虚部,也就是说,它是一个实数,会怎么样呢?

      const Two = 2.0 + 0i

    这是一个untyped复数常量,尽管它没有虚部,这个表达式的语法定义了它的默认类型是complex128 ,因此如果我们用这种语法(这种复数表述法)来声明变量,它的默认类型就会是complex128 ,

      s := Two  fmt.Printf("%T: %v\n", s, s)

    这段代码会打印complex128: (2+0i). 但是从数值上来讲,常量Two可以存储在实数系的浮点数里,一个float64或者float32,并且不丢失信息.因此我们可以将 Two 复制给float64,不论在初始化还是在赋值都没有问题

      var f float64  var g float64 = Two  f = Two  fmt.Println(f, "and", g)//output//2 and 2

    输出是 2 and 2. 尽管Two是一个复数常量,它可以被复制到实数系的浮点数变量里,这种跨类型的能力对于常量来说非常有用

    整型

    最后我们来介绍整型.他们有很多种类:—不同size,有无符号等—但是他们遵循一样的准则,这次我们最后再拿这个例子出来:

      type MyInt int  const Three = 3  const TypedThree int = 3  var mi MyInt  mi = 3          // OK  mi = Three      // OK  mi = TypedThree // Bad  fmt.Println(mi)

    上面的例子可以换成其他整型类型:

    int int8 int16 int32 int64uint uint8 uint16 uint32 uint64uintptr

    (算上byte和rune这两个整型类型的别名).虽然有很多具体的整型类型,但是他们的工作模式都很相似,因此你可以预见他们的运算结果.

    正如上面提到的,整型有一堆具体的类型,每一种类型都有他们的默认类型,123 or 0xFF or -14这样简单常量就是int, 'a','世' or'\r' 这样的字符就是 rune .

    没有哪种整型常量的的默认类型是无符号的.然而我们可以用简单的常量来初始化无符号整型变量,只要我们清楚具体的类型.这跟我们用没有虚部的复数常量来初始化float64是类似的.这里有几种初始化uint的方法,他们都是等价的,但是都要表明我们的目标类型是无符号的

    var u uint = 17var u = uint(17)u := uint(17)

    Similarly to the range issue mentioned in the section on floating-point values, not all integer values can fit in all integer types. There are two problems that might arise: the value might be too large, or it might be a negative value being assigned to an unsigned integer type. For instance, int8 has range -128 through 127, so constants outside of that range can never be assigned to a variable of type int8:

    和浮点数那一章节提及到的范围问题相似,不是所有的整型常量值都能符合所有的整型类型.这可能会引发两个问题:值可能太大,或者对无符号数赋了一个负数值.例如,int8能表达的数值范围是-128~127,因此超过这个范围的数值不能被赋值到int8的变量.

      var i8 int8 = 128 // Error: too large.

    类似地,uint8,亦即byte,表达的范围是0~255,太大或者负数的常量都不能进行赋值

      var u8 uint8 = -1 // Error: negative value.

    类型检查可以捕抓到这个错误:

      type Char byte  var c Char = '世' // Error: '世' has value 0x4e16, too large.

    如果编译器对你使用常量的姿势发出警报,那你就真的是有bug了

    练习:最大的unsigned int值

    这里有一个能涨姿势的小练习:我们怎么表达出uint能承载的最大值?如果我们讨论的是uint32而不是uint,我们可以写:

    const MaxUint32 = 1<<32 - 1

    但是我们想要的是uint不是uint32,int and uint这两种类型有着相同但却没有指定的机器位长,32或者64.因为他们是由硬件架构决定,我们不能写出某一个固定的值(作为uint的最大值)

    作为二进制补码(GO整型的底层实现)真正的粉丝,都会知道-1其实是所有bits都为1,所以-1的二进制补码和无符号的整数的最大值在二进制上是一样的,因此我们可能会想这么写:(译注:C遗留下来的骚操作)

      const MaxUint uint = -1 // Error: negative value

    但这是非法的,因为-1不能用来表示一个无符号数,-1不在无符号值的范围内,加上类型转换也救不了你:

      const MaxUint uint = uint(-1) // Error: negative value

    即使在运行时,值-1能被转换成一个无符号整型,常量的转换规则 conversions在编译期禁止掉了这个强制类型转换操作,也就是说,这是work的

      var u uint  var v = -1  u = uint(v)

    但这仅适用于v是变量的情况,如果我们想要v是一个常量,甚至是untyped常量,我们又会被编译器禁止了:

      var u uint  const v = -1  u = uint(v) // Error: negative value

    让我们回到前一个方法,但这次不是用-1 而是用^0,对0进行非操作.但是这样也失败了,原因也很相似:在数值空间里,^0代表了(译注:无类型数值常量能表达的)无穷大,把它赋值给任何size的整型都会丢失信息

      const MaxUint uint = ^0 // Error: overflow

    那我们要怎么表达用常量来表达最大的unsigned 整型数呢?

    问题的关键是,把操作的数据的位长控制到uint一样并且避开那些不能被uint表达的值(比如-1),最简单的uint值是typed 常量 uint(0),如果uints 有32或者64位,那么uint(0) 也有相应的32或者64位,如果我们对每一位取反,我们可以得到正确的最大的unit 的值

    因此我们不对untyped常量0 取反,而是对uint(0) 取反,像这样:

      const MaxUint = ^uint(0)  fmt.Printf("%x\n", MaxUint)

    无论uint 在当前的运行环境具体是多少位,这样写都能表达出uint 能表达的最大值

    如果你能理解这段结果的分析,那你就明白了所有关于Go的常量的知识点了

    数值

    在Go里,untyped常量这个概念意味着所有的数值常量,无论是整型,浮点,复数,甚至字符值,都住在一个统一的幻想乡.当我们把他们从幻想乡里拉出来,拉到计算机的世界来进行赋值,计算操作我们才关心具体类型(译注:我理解是untyped只是一堆很长的机器码,只有在需要类型信息的时候才编译器自动处理).但是只要我们还留在数值常量的幻想乡里,我们就能随意地混合不同类型的数值常量进行计算,下面这些常量的数值都是1:

    11.0001e3-99.0*10-9'\x01''\u0001''b' - 'a'1.0+3i-3.0i

    因此,尽管他们有很多不同的隐式默认类型,写成untyped常量的形式他们就能赋值给任意类型的整型类型

      var f float32 = 1  var i int = 1.000  var u uint32 = 1e3 - 99.0*10.0 - 9  var c float64 = '\x01'  var p uintptr = '\u0001'  var r complex64 = 'b' - 'a'  var b byte = 1.0 + 3i - 3.0i  fmt.Println(f, i, u, c, p, r, b)

    输出分别是: 1 1 1 1 1 (1+0i) 1.

    你还可以这样骚操作:

      var f = 'a' * 1.5  fmt.Println(f)

    结果是145.5,当然这样并没有什么实际意义

    但是这个规则真正重要的是它的灵活性.这个灵活性意味着,即使在Go里我们不能混合不同类型进行运算(比如浮点和整型,甚至intint32),我们还可以这样:

    sqrt2 := math.Sqrt(2)

    or

    const millisecond = time.Second/1e3

    or

    bigBufferWithHeader := make([]byte, 512+1e6)

    并且有我们预期的结果

    Because in Go, numeric constants work as you expect: like numbers.

    因为在GO里,数值常量就像你预期的那样工作:像个数那样

    Rob Pike 编写

    (译注:博客翻译完毕)

  • 一些补充

    终于翻译完毕了,可喜可贺可喜可贺.

    但是我想补充两个重要的点,一个是go的类型转换,另一个是关于go的常量的语言规范,以下信息均摘录自https://go-zh.org/ref/spec,并且由我自己翻译

    1. 编译器在实现常量的时候,必须遵循如下规则:

      • 表达整型的常量必须至少有256bits
      • 表达浮点数的常量,包括复数常量里用到的那部分,必须满足:小数部分用至少256bits来表示,带符号指数部分必须要有32bits来表示(参考IEEE-754对浮点数格式的要求)
      • 当因为不能准确表达一个整数常量的时候应当抛出错误
      • 当因为溢出而不能表达一个浮点数或者复数的时候应当抛出错误
      • Round to the nearest representable constant if unable to represent a floating-point or complex constant due to limits on precision.
      • 当因为浮点数或者复数的精度限制的时候取最接近的能表达的常量
    2. go的类型转换规则

      • 当一个常量值 x能被转换成T 类型的时候应当满足如下条件之一:
      • x 是一个能被表达成类型 T的值.
      • x是一个浮点数常量, T 是一个浮点数类型, 并且x 是一个能够被 T 根据IEEE-754支持的精度范围表达的值. 常量T(x) 应当是范围内的数
      • x 是整型常量,T 是字符串类型,这时候和非常量值的转换规则一样
      • 当一个非常量值 x能被转换成T 类型的时候应当满足如下条件之一:
      • x 能被赋值到 T.
      • x的类型和 T 有完全一样的底层实现类型
      • x的类型和 T 都是unnamed pointer 类型,并且他们的指针的base types有完全相同的底层实现类型
      • x的类型和 T 都是整型或者浮点的指针类型
      • x的类型和 T 都是复数类型
      • x 是整型或者bytes的切片([]byte)或者rune的切片([]rune) 并且 T 是string类型
      • x 是string并且 T 是[]byte或者[]rune
      • 关于数值类型之间相互赋值的规则,我就不一一翻译了,见:https://go-zh.org/ref/spec#Conversions
  • 文章总结:

    • 总的来说,依我的理解,Go的常量(尤其是数值型)主要是被视作一堆机器码,在需要的时候才用具体的类型的眼光去看待它.并且通过翻译这篇blog,我也总结出关于go的常量使用姿势,我认为常量应当尽可能的推迟它被指定具体类型的时机,一方面是为了保留高精度,另一方面,运算方面也会有便捷.
    • 回到我们最初的问题,我想现在这篇将近8K word的文章读下来,各位应该都已经很清晰go的常量的表现了,原因应该也会像我上面总结出来的那样,如有疑问,欢迎留言探讨
  • 一些吐槽:

    • 这是我第一次翻译这么长的技术blog,起因是我遇到了开头的问题,然后看到了这篇官方的解说,想到国内中文资源不多,突然兴起想回馈一下社区,就试着翻译了一下.翻译过程其实整体都比较简单,但是也让我深刻体会到了中英表达的差异,尤其是从句的表达,基本都要修改一下表达语序.毕竟是休息时间的业余翻译产物,肯定中间有表达得不是很好甚至错误的地方,届时我还是推荐你看相应的原文部分,毕竟这种二手材料都不如一手的好嘛.GO这门语言我个人还是蛮喜欢的,喜欢接下来有更多机会深入探讨Go.

原创粉丝点击