前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go常量 【Go语言圣经笔记】

Go常量 【Go语言圣经笔记】

作者头像
Steve Wang
发布2021-12-06 16:17:12
3190
发布2021-12-06 16:17:12
举报
文章被收录于专栏:从流域到海域

常量

常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:booleanstring数值型

一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在运行期被意外或恶意的修改。例如,常量比变量更适合用于表达像π之类的数学常数,因为它们的值不会发生变化:

代码语言:javascript
复制
const pi = 3.14159 // approximately; Math.Pi is a better approximation

和变量声明一样,可以批量声明多个常量;这样比较适合声明一组相关的常量:

代码语言:javascript
复制
const (
    e  = 2.71828182845904523536028747135266249775724709369995957496696763
    pi = 3.14159265358979323846264338327950288419716939937510582097494459 
)

所有常量的运算都可以在编译期完成,这样可以减少运行时的工作,也方便其他编译优化。当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。

常量间的所有算术运算逻辑运算比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:lencaprealimagcomplexunsafe.Sizeof

代码语言:javascript
复制
const IPv4Len = 4

// parseIPv4 parses an IPv4 address (d.d.d.d).
func parseIPv4(s string) IP {
    var p [IPv4Len]byte
    // ...
}

一个常量的声明也可以包含一个类型一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。

在下面的代码中,time.Duration是一个命名类型,底层类型是int64time.Minute是对应类型的常量。下面声明的两个常量都是time.Duration类型,可以通过%T参数打印类型信息:

代码语言:javascript
复制
const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay)  // time.Duration 0
fmt.Printf("%T %[1]v\n", timeout)  // time.Duration 5m0s
fmt.Printf("%T %[1]v\n", time.Minute) // time.Duration 1m0s

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的(笔者注:即用同样的值同样的类型声明)。例如:

代码语言:javascript
复制
const (
    a = 1
    b
    c = 2
    d
)

fmt.Println(a, b, c, d)  // 1 1 2 2

如果只是简单地复制右边的常量表达式,其实并没有太实用的价值。但是它可以带来其它的特性,那就是iota常量生成器语法。

itoa常量生成器

常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。

下面是来自time包的例子,它首先定义了一个Weekday命名类型,然后为一周的每天定义了一个常量,从周日0开始。在其它编程语言中,这种类型一般被称为枚举类型。

代码语言:javascript
复制
type Weekday int

const (
    Sunday Weekday = iota  // 0
    Monday                 // 1
    Tuesday                // 2
    Wednesday              // 3
    Thursday               // 4
    Friday                 // 5
    Saturday               // 6
)

也可以在复杂的表达式中使用iota,下面是来自net包的例子,用于给一个无符号整数的最低5bit的每个bit制定一个名字:

代码语言:javascript
复制
type Flag unit 
const (
    FlagUp Flags = 1 << iota // b0001 1 is up
    FlagBroadcast            // b0010 2 supports broadcast access capability
    FlagLoopback             // b0100 4 is a loopback interface
    FlagPointToPoint         // b1000 8 belongs to a point-to-point link
    FlagMulticast            // b10000 16 supports multicast access capability
)

随着iota的递增,每个常量对应表达式1 << iota,是连续的2的幂,分别对应一个bit位置。使用这些常量可以用于测试、设置或清除对应的bit位的值:

代码语言:javascript
复制
func IsUp(v flags) bool      {return v&FlagUp == FlagUp} // 与运算
func TurnDown(v *Flags)      {*v &^= Flags}              // 清零运算
func SetBroadcast(v *Flags)  {*v |= FlagBroadcast}       // 或运算
func IsCast(v Flags) bool.   {return v&(FlagBroadcast | FlagMulticast) != 0}  // 先或后与

func main() {
    var v Flags = FlagMulticast | FlagUp
    fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true"
    TurnDown(&v)
    fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false"
    SetBroadcast(&v)
    fmt.Printf("%b %t\n", v, IsUp(v))   // "10010 false"
    fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true"
}

下面是一个更复杂的例子,每个常量都是1024的幂:

代码语言:javascript
复制
const (
    _ = 1 << (10 * iota)
    KiB  // 1024
    MiB  // 1048576
    GiB  // 1073741824
    TiB  // 1099511627776             (exceeds 1 << 32)
    PiB  // 1125899906842624
    EiB  // 1152921504606846976
    ZiB  // 1180591620717411303424    (exceeds 1 << 64)
    YiB  // 1208925819614629174706176
)

不过iota常量生成规则也有其局限性。例如,它并不能用于产生1000的幂(KB、MB等),因为Go语言并没有计算幂的运算符

无类型常量

Go语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如intfloat64,或者是类似time.Duration这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些没有明确基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型无类型的整数无类型的字符无类型的浮点数无类型的复数无类型的字符串

通过延迟明确常量的具体类型无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。

ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围,但是它们依然是合法的常量,而且像下面的常量表达式依然有效(译注:YiB/ZiB是在编译期计算出来的,并且结果常量是1024,是Go语言int变量能有效表示的):

代码语言:javascript
复制
fmt.Println(YiB/ZiB)  // 1024

值得注意的是,math.Pi是无类型的浮点数常量,可以直接用于任意需要浮点数或者复数的地方:

代码语言:javascript
复制
var x float32 = math.PI
var y float64 = math.PI
var z complex128 = math.PI

如果math.Pi被定义为特定类型,比如float64,那么结果精度可能会不一样,同时对于需要float32或complex128类型值的地方则会强制需要一个明确的类型转换:

代码语言:javascript
复制
const Pi64 float64 = math.Pi

var x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)

对于常量面值,不同的写法可能会对应不同的类型。例如00.00i\u0000虽然有着相同的常量值,但是它们分别对应无类型的整数(0)、无类型的浮点数(0.0)、无类型的复数(0i)和无类型的字符串等不同的常量类型。同样, true和false也是无类型的布尔类型,字符串面值常量是无类型的字符串类型

除法运算符/会根据操作数的类型生成对应类型的结果。因此,不同写法的常量除法表达式可能对应不同的结果:

代码语言:javascript
复制
var f float64 = 212
fmt.Println((f-32) * 5 / 9)  // 100 float32 | (f - 32) * 5 is a float64
fmt.Println(5/9 * (f-32))    // 0 float32 | 5/9 is anuntyped integer 0
fmt.Println(5.0/9.0 * (f-32)) // 100 float32 | 5.0/9.0 is an untyped float

只有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的时候,就像下面的第一行语句,或者出现在有明确类型的变量声明的右边,如下面的其余三行语句,无类型的常量将会被隐式转换为对应的类型(如果转换合法的话)。

代码语言:javascript
复制
var f float64 = 3 + 0i // untyped complex -> float64
f = 2                  // untyped integer -> float64
f = 1e123              // untyped floating-point -> float64
f = 'a'                // untyped rune -> float64

相当于

代码语言:javascript
复制
var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')

无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值对于浮点数和复数,可能会有舍入处理

代码语言:javascript
复制
const (
    deadbeef = 0xdeadbeef // untyped int with value 3735928559
    a = uint32(deadbeef)  // uint32 with value 3735928559
    b = float32(deadbeef) // float32 with value 3735928576 (rounded up)
    c = float64(deadbeef) // float64 with value 3735928559 (exact)
    d = int32(deadbeef)   // compile error: constant overflows int32
    e = float64(1e309)    // compile error: constant overflows float64
    f = uint(-1)          // compile error: constant underflows uint
)

对于一个没有显式类型的变量声明(包括简短变量声明),常量的类型将隐式决定变量的默认类型,就像下面的例子:

代码语言:javascript
复制
i := 0      // untyped integer;        implicit int(0)
r := '\000' // untyped rune;           implicit rune('\000')
f := 0.0    // untyped floating-point; implicit float64(0.0)
c := 0i     // untyped complex;        implicit complex128(0i)

注意:无类型整数常量转换为int,它的内存大小是不确定的,但是无类型浮点数和复数常量则会转换为内存大小明确的float64和complex128。 如果不知道浮点数类型的内存大小是很难写出正确的数值算法的,因此Go语言不存在整型类似的不确定内存大小的浮点数和复数类型。

如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指定明确的类型,像下面例子这样:

代码语言:javascript
复制
var i = int8(0)
var i int8 = 0

当尝试将这些无类型的常量转为一个接口值时(见第7章),这些默认类型将显得尤为重要,因为要靠它们明确接口对应的动态类型。

代码语言:javascript
复制
fmt.Printf("%T\n", 0)      // "int"
fmt.Printf("%T\n", 0.0)    // "float64"
fmt.Printf("%T\n", 0i)     // "complex128"
fmt.Printf("%T\n", '\000') // "int32" (rune)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/07/06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常量
    • itoa常量生成器
      • 无类型常量
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档