在Go语言中,常量表达式是在编译期求值的,因此在程序运行时是没有性能损耗的。常量的底层类型是前面提过的基本类型:布尔值,字符串,数值变量。

常量的声明方式和变量很相似,但是常量的值是不可变的,因此在运行期是不可以对常量进行修改的。例如,对于π这种数学常数,常量显然比变量更适合,因为我们不允许这个值发生任何变化:

const pi = 3.14159 // 近似值;实际应用请使用math.Pi,更精确

可以同时声明多个常量:

const (e  = 2.71828182845904523536028747135266249775724709369995957496696763pi = 3.14159265358979323846264338327950288419716939937510582097494459
)

常量的运算也是在编译期完成的,这样不仅可以做编译优化,还可以提升运行时的性能。如果一个表达式的操作数是常量,那么一些运行时的错误就可以提前在编译期发现,例如:整数除以零、字符串索引越界、浮点数计算导致的正负无穷等等。

常量作为操作数时,以下表达式的结果都是常量:算术、逻辑、比较运算,类型转换,len、cap、real、imag、comlex、unsafe.Sizeof。

因为常量是在编译器确定的,因此可以作为一些复杂类型的组成部分,比如数组类型的长度:

const IPv4Len = 4// parseIPv4函数对IPv4地址(d.d.d.d)进行解析.
func parseIPv4(s string) IP {var p [IPv4Len]byte// ...
}

常量声明时可以指定类型,也可以不指定类型,如果不指定,那么编译器会自己进行类型推断。下面代码中,time.Duration是一个具名类型,底层类型是int64,而time.Minute是一个time.Duration类型的常量。下面声明的两个常量的类型都是time.Duration,我们可以在fmt中使用%T参数打印变量的类型:

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"    

批量声明常量时,除了第一个常量,其它常量声明时的右边表达式都可以省略。如果某个常量的右边表达式缺失,则该常量的值和类型等于前面常量的值和类型,例如:

const (a = 1bc = 2d
)fmt.Println(a, b, c, d) // "1 1 2 2"

实际场景中,上面的代码并没有太多实用价值。但是我们可以利用它实现下面的iota语法。

3.6.1. iota

我们可以使用iota语法来声明一组按照同样规则初始化的常量,优点是不用每行声明都写一遍初始化语句。在一组const声明中,第一个常量的iota值被设置为0,然后接下来每一个行的常量值都会自动递增1。

下面这个例子来自time包,首先,它定义了Weekday这个具名类型,然后定义了一组常量(一周七天),其中周日的值为0,后面的值依次递增。在C语言中,这种被称为枚举类型(Enum):

type Weekday intconst (Sunday Weekday = iotaMondayTuesdayWednesdayThursdayFridaySaturday
)

周日到周一的值依次是0到6。

下面是一个更为复杂的例子,来自net包,每个Flag常量都是一个无符号整数:整数的指定bit被设置为1:

type Flags uintconst (FlagUp Flags = 1 << iota // is upFlagBroadcast            // supports broadcast access capabilityFlagLoopback             // is a loopback interfaceFlagPointToPoint         // belongs to a point-to-point linkFlagMulticast            // supports multicast access capability
)

随着iota的递增,每个常量特定的bit位都会设置为1(位左移),这里第一个常量的二进制为0000 0001,第二个常量的二进制为0000 0010,第三个0000 0100,依次类推。可以使用这些常量用于测试、设置或清除对应bit位的值,也可以用来判断某个值对应的bit是否设置为1(代表着相应的Flag是否设置)。

func IsUp(v Flags) bool     { return v&FlagUp == FlagUp }
func TurnDown(v *Flags)     { *v &^= FlagUp }
func SetBroadcast(v *Flags) { *v |= FlagBroadcast }
func IsCast(v Flags) bool   { return v&(FlagBroadcast|FlagMulticast) != 0 }unc main() {var v Flags = FlagMulticast | FlagUpfmt.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的幂:

const (_ = 1 << (10 * iota)KiB // 1024MiB // 1048576GiB // 1073741824TiB // 1099511627776             (超过了int32的范围)PiB // 1125899906842624EiB // 1152921504606846976ZiB // 1180591620717411303424    (超过了int64的范围)YiB // 1208925819614629174706176
)

不过iota常量也有其局限性。例如,1000的幂就无法用iota实现,因为Go语言没有幂运算符(只能通过标准库)。

练习 3.13: 利用尽可能简洁的方式声明KB至YB之间的常量

3.6.2. 无类型常量

Go语言中的常量有一点很特别:虽然一个常量可以指定某个特定的基本类型,例如int或float64或者类似time.Duration这样的具名基本类型,但是在实际应用中很多常量声明时都不指定类型。编译器表示无类型常量时用的精度比表示基本类型更高,同时无类型常量的算术运算也会更加精确,你可以假定这种精度至少是256bit。这里有六种无类型常量:无类型布尔值,无类型整数、无类型rune、无类型浮点数、无类型复数以及无类型字符串。
无类型常量不仅可以提供更高的精度,而且可以在表达式中避免显示类型转换。例如,上面例子中的ZiB、YiB的值已经超过Go语言中任何整数类型所能表达的范围,但是它们依然是合法的常量。也可以像下面这样使用:
fmt.Println(YiB/ZiB) // "1024"

再看一个例子,math.Pi是无类型浮点数常量,可直接用在任意需要浮点数或复数的地方:

var x float32 = math.Pi // 精度损耗,256bit -> 32bitvar y float64 = math.Pi // 精度损耗,256bit -> 64bitvar z complex128 = math.Pi

如果math.Pi不是无类型的而是float64类型的,那么最终结果的精度可能不同,同时从浮点数转为复数时需要显示的类型转换:

const Pi64 float64 = math.Pi // 精度损耗,256bit -> 64bitvar x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)

不同的常量值写法会对应不同的默认类型,虽然0、0.0、0i及'\u0000'有相同的常量值,但是它们分别是:无类型整数、无类型浮点数、无类型复数和无类型rune。同样的,true、false是无类型布尔常量,字符串值"hello"是无类型字符常量。

之前的章节提过:/ 运算符会根据操作数类型生成对应类型的结果(整形或浮点),常量的除法也有这样的特性:

var f float64 = 212
fmt.Println((f - 32) * 5 / 9)     // "100"; (f - 32) * 5 是float64类型
fmt.Println(5 / 9 * (f - 32))     // "0";   5/9 是无类型整数, 0
fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0是无类型浮点数

只有常量才能没有类型,当无类型常量被赋值给变量时,如果转换合法,那么会进行隐式类型转换:

var f float64 = 3 + 0i // 无类型复数 -> float64
f = 2                  // 无类型整数 -> float64
f = 1e123              // 无类型浮点数 -> float64
f = 'a'                // 无类型rune -> float64

上面的语句相当于:

var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')

无论隐式或者显式转换,将类型A转为类型B都需要B可以表示A代表的值。同时支持四舍五入:

const (deadbeef = 0xdeadbeef // 无类型整数, 3735928559a = uint32(deadbeef)  // uint32整数, 3735928559b = float32(deadbeef) // float32, 3735928576 (不精确)c = float64(deadbeef) // float64 ,3735928559 (精确)d = int32(deadbeef)   // compile error: constant overflows int32e = float64(1e309)    // compile error: constant overflows float64f = uint(-1)          // compile error: constant underflows uint
)

在无类型变量的声明中(包含短声明),无类型常量值会被隐式转为默认的类型,例如:

i := 0      // 无类型整数;        隐式转换 int(0)
r := '\000' // 无类型rune;       隐式转换  rune('\000')
f := 0.0    // 无类型浮点数;      隐式转换 float64(0.0)
c := 0i     // 无类型复数;      隐式转换 complex128(0i)

上面的隐式转换是有规则的:无类型整数默认转为int,无类型浮点数和复数默认转为float64和complex128。因此,如果要给变量指定一个和默认类型不同的类型,必须进行显式类型转换:

var i = int8(0)
var i int8 = 0

将无类型常量转为一个接口值时,这种默认类型就很重要,因为这样才能确定接口的动态类型(见第6章)。下面例子中,fmt第二个参数是接口类型inteface{},当把常量直接进行传参时,接口值的动态类型就是常量的默认类型。

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)

现在我们已经学习了Go语言中的所有基本类型。下面的章节将学习如果使用基本类型组合成复杂数据类型,然后解决实际编程问题。

文章所有权:Golang隐修会 联系人:孙飞,CTO@188.com!

Go语言核心之美 2.6-常量相关推荐

  1. Go语言核心之美-必读

    Go语言核心之美开篇了!,无论你是新手还是一代高人,在这个系列文章中,总能找到你想要的! 博主是计算机领域资深专家并且是英语专8水平,翻译标准只有三个:精确.专业.不晦涩,为此每篇文章可能都要耗费数个 ...

  2. Go语言核心之美 3.4-Struct结构体

    struct(结构体)也是一种聚合的数据类型,struct可以包含多个任意类型的值,这些值被称为struct的字段.用来演示struct的一个经典案例就是雇员信息,每条雇员信息包含:员工编号,姓名,住 ...

  3. Go语言核心之美 1.5-作用域

    变量的作用域是指程序代码中可以有效使用这个变量的范围.不要将作用域和生命期混在一起.作用域是代码中的一块区域,是一个编译期的属性:生命期是程序运行期间变量存活的时间段,在此时间段内,变量可以被程序的其 ...

  4. Go语言核心之美 1.4-包和文件

    一.Package Go语言中的包(Package)就像其它语言的库(Library)或模块(Module)一样,支持模块化,封装性,可重用性,单独编译等特点.包的源码是由数个.go文件组成,这些文件 ...

  5. Go语言核心之美 3.2-slice切片

    Slice(切片)是长度可变的元素序列(与之对应,上一节中的数组是不可变的),每个元素都有相同的类型.slice类型写作[]T,T是元素类型.slice和数组写法很像,区别在于slice没有指定长度. ...

  6. Go语言核心之美 3.1-数组

    上一章我们深入学习了基本数据类型,它们是构建复杂数据类型的基础,是组成Go语言世界的原子.本章,我们将学习复合数据类型:通过不同的方式将基本类型组合起来.主要有四种复合类型--数组,切片(slice) ...

  7. Go语言核心之美 3.3-Map

    哈希表是一种非常好用.适用面很广的数据结构,是key-value对的无序集合.它的key是唯一的,通过key可以在常数复杂度时间内进行查询.更新或删除,无论哈希表有多大. Go语言的map类型就是对哈 ...

  8. Go语言核心之美 1.2-变量及声明篇

    变量 1.声明变量 使用var关键字可以创建一个指定类型的变量: var i int = 0 var i = 0 var i int 以上三个表达式均是合法的,第三个表达式会将i初始化为int类型的零 ...

  9. Go语言核心之美 2.1-整数

    第二章 序 在计算机底层,一切都是比特位.然而计算机一般操作的都是固定大小的值,称之为字(word).字会被解释为整数.浮点数.比特位数组.内存地址等,这些字又可以进一步聚合成数据包(packet). ...

最新文章

  1. yum安装出现Error: Package: glibc-headers-2.17-157.el7.x86_64 (centos7.3)类似报错解决方案
  2. mysql中如何将一个表中的部分记录合并,MySQL数据库将多条记录的单个字段合并成一条记录_MySQL...
  3. buildroot--ubootkernelrootfs全编译工具
  4. 读懂这一篇,集群节点不下线
  5. 设计模式入门(策略模式)
  6. Do you have an English name? 你有英文名吗?
  7. tcpdump的用法
  8. Dubbo 高危漏洞!原来都是反序列化惹得祸
  9. PyTorch | torch.linspace()创建均分数列张量 | torch.linspace()如何使用?| torch.linspace()使用方法 | torch.linspace例子
  10. [kuangbin带你飞]专题十二 基础DP1 C - Monkey and Banana HDU - 1069
  11. 批量 材质 调整_寒霜引擎的PBR实践3.0(一)材质篇
  12. 基于gitosis的Git云端服务器配置
  13. 思考,思考,不停思考——《技术领导之路》序言
  14. pkill mysql_MYSQL之mysqlcheck命令
  15. div+css总结—FF下div不设置…
  16. logo计算机语言,LOGO语言
  17. 计算机怎么使用远程桌面工具,win7一键开启远程桌面工具
  18. Jqury 初识 -jqury选择元素 、 $()下的常用方法
  19. javascript instaceof
  20. java基础-类-抽象类-接口(自学笔记)

热门文章

  1. 切比雪夫不等式例题讲解_14.初中数学:怎么求k的值?解一元一次不等式,基础常考题型...
  2. Linux学习-man和Info
  3. 基于Spring Boot 2 和 Vue.js 2 的 食品科学与工程学院网站的设计与实现
  4. 强化学习代码实操和讲解(一)
  5. matlab 贪吃的蛇,贪吃的蛇教案
  6. e.keycode 代码含义
  7. Excel VBA 编程的常用代码
  8. 基于I2C/SPI总线的温湿度采集与OLED显示
  9. UE4 GamePlay架构学习篇
  10. win10重装系统后连不上公司服务器,Win10重装系统后网络连接不了,重装win10系统后不能上网解决方法...