Go语言学习十二 变量和常量

本文详细介绍了Go语言中的变量和常量。在变量部分,讲解了变量的声明、类型以及初始化。常量部分则涵盖了常量声明、无类型常量、常量表达式和求值顺序。此外,还提到了iota常量生成器的用法。通过本文,读者可以深入理解Go语言中变量和常量的使用规则。
摘要由CSDN通过智能技术生成

本文最初发表在我的个人博客,查看原文,获得更好的阅读体验


Go 使用var关键字声明变量;使用关键字const声明常量。变量可以像常量一样初始化。

一 变量

1.1 变量声明

语法:
var 变量名 变量类型 | = 值

var x int
var y string = "hello"
var z = 2
var c, python, java = true, false, "no!"
var r, s int

预声明的值nil不能用于初始化没有显式类型的变量。

var n = nil            // 非法

另外,还可以使用短变量声明运算符:=来声明变量,以下声明是等价的:

a := 2
var a = 2

短变量声明可能仅出现在函数内部。在某些上下文中,例如ifforswitch语句的初始化程序,它们可用于声明本地临时变量。

实现限制:如果在函数体内声明了变量却未使用,编译器可能认为是非法的。

初始值也可以是一个在运行时计算的一般表达式

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)

调用内置函数new或获取复合字面量的地址,会在运行时为变量分配存储空间。这种匿名变量通过指针重定向来引用(可能是隐式的)。

数组,切片和结构类型的结构化变量具有可单独寻址的元素和字段。每个这样的元素就像一个变量。

1.2 变量的类型

声明时给出的类型、调用new函数或在复合字面量中提供的类型,或结构化变量的元素类型,称为变量的静态类型(或仅仅类型)。
接口类型的变量甚至具有不同的动态类型,它是在运行时分配给变量的值的具体类型(除非该值是预先声明的标识符nil,它没有类型)。动态类型在执行期间可能会有所不同,但存储在接口变量中的值始终可分配给变量的静态类型。

var x interface{}  // x is nil and has static type interface{}
var v *T           // v has value nil, static type *T
x = 42             // x has value 42 and dynamic type int
x = v              // x has value (*T)(nil) and dynamic type *T

如果尚未为变量赋值,则其值为其类型的零值。

二 常量

Go中的常量有:布尔常量,符文常量,整数常量,浮点常数,复数常量和字符串常量。符文,整数,浮点和复数常量统称为数字常量。常量就是不变量。它们在编译期间创建。即使在函数中定义为局部常量,也是如此。

由于编译时限制,定义它们的表达式必须是可被编译器求值的常量表达式。例如,1<<3是常量表达式,而math.Sin(math.Pi/4)不是,因为函数调用math.Sin需要在运行时发生。

2.1 常量声明

常量的声明与变量类似,只不过是使用const关键字。常量声明将标识符列表(常量的名称)绑定到常量表达式列表的值。标识符的数量必须等于表达式的数量,左侧的第n个标识符绑定到右侧的第n个表达式的值。

如果存在类型,则所有常量都采用指定的类型,并且表达式必须可分配给该类型。如果省略该类型,则常量将采用相应表达式的各个类型。

const Pi float64 = 3.14159265358979323846
const zero = 0.0         // untyped floating-point constant
const (
	size int64 = 1024
	eof        = -1  // untyped integer constant
)
const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", untyped integer and string constants
const u, v float32 = 0, 3    // u = 0.0, v = 3.0

在带括号的const声明列表中,除了第一个ConstSpec之外,可以省略表达式列表。与iota常量生成器一起,此机制允许轻量级声明顺序值:

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Partyday
	numberOfDays  // this constant is not exported
)

常量不能用:=语法声明。预声明常量有:布尔常量true,false;预声明标识符:iota

可以使用枚举器iota创建枚举常量。由于iota可以是表达式的一部分,并且表达式可以隐式重复,因此很容易构建复杂的值集。

type ByteSize float64

const (
    _           = iota // 通过赋值给空白标识符来忽略第一个值(0)
    KB ByteSize = 1 << (10 * iota)// 序列自动顺延
    MB // 声明自动顺延
    GB
    TB
    PB
    EB
    ZB
    YB
)

将诸如String之类的方法附加到任何用户定义类型的能力使得任意值可以自动格式化以进行打印。虽然它最常用于结构,但这种技术对标量类型也很有用,比如像ByteSize这种浮点类型。

func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)

使用Sprintf来实现ByteSizeString方法是安全的(避免无限重复)不是因为转换而是因为它用%f调用Sprintf,这不是一个字符串格式:Sprintf只在需要字符串时调用String方法,%f需要浮点数。

2.2 无类型常量

无类型常量具有默认类型,该类型是在需要键入值的上下文中隐式转换常量的类型,例如,在一个简短的变量声明中,例如i := 0,其中没有显式类型。无类型常量的默认类型分别是bool,rune,int,float64,complex128或string,具体取决于它是布尔值,符文,整数,浮点数,复数还是字符串常量。

实现限制:虽然数字常量在语言中具有任意精度,但编译器可能使用精度有限的内部表示来实现它们。也就是说,每个实现必须:

  • 表示至少256位的整数常量。
  • 表示浮点常数,包括复数常量的部分,尾数至少为256位,有符号二进制指数至少为16位。
  • 如果无法精确表示整数常量,则给出错误。
  • 如果由于溢出而无法表示浮点或复数常量,则给出错误。
  • 如果由于精度限制而无法表示浮点或复数常量,则舍入到最接近的可表示常量

这些要求既适用于字面量常量,也适用于求值常量表达式的结果。

2.3 常量表达式

常量表达式只包含常量操作数,且在编译时求值。

无类型的布尔值,数字,字符串常量可以用作操作数,只要分别满足布尔,数字和字符串的操作数的要求。

常量的比较总是产生无类型的布尔常量。

如果常量移位表达式的左操作数是无类型常量,则结果为整数常量,否则它是一个与左操作数相同类型的常量,且必须是整数类型。

无类型常量的任何其他运算都会导致产生同样的无类型常量;也就是说,布尔,整数,浮点数,复数或字符串常量。如果二元运算(非移位操作)的无类型操作数具有不同的类型,则结果是该列表中后出现的操作数类型:整数,符文,浮点数,复数。例如,无类型整数常量除以无类型复数常量会产生无类型复数常量。

const a = 2 + 3.0          // a == 5.0   (无类型浮点常量)
const b = 15 / 4           // b == 3     (无类型整数常量)
const c = 15 / 4.0         // c == 3.75  (无类型浮点常量)
const Θ float64 = 3/2      // Θ == 1.0   (float64类型, 3/2 是整数除法)
const Π float64 = 3/2.     // Π == 1.5   (float64类型, 3/2. 是浮点除法)
const d = 1 << 3.0         // d == 8     (无类型整数常量)
const e = 1.0 << 3         // e == 8     (无类型整数常量)
const f = int32(1) << 33   // 非法    (常量 8589934592 在 int32下溢出)
const g = float64(2) >> 1  // 非法    (float64(2) 是浮点类型的常量)
const h = "foo" > "bar"    // h == true  (无类型布尔常量)
const j = true             // j == true  (无类型布尔常量)
const k = 'w' + 1          // k == 'x'   (无类型符文常量)
const l = "hi"             // l == "hi"  (无类型字符串常量)
const m = string(k)        // m == "x"   (字符串类型)
const Σ = 1 - 0.707i       //            (无类型复数常量)
const Δ = Σ + 2.0e-4       //            (无类型复数常量)
const Φ = iota*1i - 1/1i   //            (无类型复数常量)

常量表达式始终可以精确计算。中间值和常量本身可能要求精度明显大于语言中任何预声明的类型所支持的精度。以下是合法声明:

const Huge = 1 << 100         // Huge == 1267650600228229401496703205376  (无类型整数常量)
const Four int8 = Huge >> 98  // Four == 4                                (int8类型的整数常量)

常量除法或余数运算的除数不得为零:

x := 3.14
x / 0.0      // 合法
3.14 / 0.0   // 非法: 被零除

类型常量的值必须始终可以通过常量类型的值准确表示,以下常量表达式是非法的:

uint(-1)     // -1 不能表示为 uint
int(3.14)    // 3.14 不能表示为 int
int64(Huge)  // 1267650600228229401496703205376 不能表示为 int64
Four * 300   // 操作数 300 不能表示为 int8 (Four的类型)
Four * 100   // 积 400 不能表示为 int8 (Four的类型)

一元按位补码运算符^使用的掩码满足非常量的规则:对于无符号常量,掩码全为1,对于有符号和无类型常量,掩码均为-1

^1         // 无类型整数常量, 等于 -2
uint8(^1)  // 非法: 同 uint8(-2), -2 不能表示为 uint8
^uint8(1)  // uint8 类型的常量, 同 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // 同 int8(-2)
^int8(1)   // 同 -1 ^ int8(1) = -2

实现限制:编译器可能在计算无类型浮点数或复数常量表达式时使用舍入;请参阅常量一节中的实现限制。这种舍入可能导致浮点常量表达式在整数上下文中无效,即使在使用无限精度计算时它将是积分的,反之亦然。

2.4 求值顺序

在包级别,初始化依赖决定变量声明中各个初始化表达式的求值顺序。除此之外,在计算表达式的操作数、赋值或返回语句时,所有函数调用,方法调用和通信操作都按词法从左到右进行求值。

例如,在(函数-局部)赋值中:

y[f()], ok = g(h(), i()+x[j()], <-c), k()

函数调用和通信按f(), h(), i(), j(), <-c, g(), 和 k()的顺序发生。但是,没有指定与x的求值和索引以及y的求值相比较的那些事件的顺序。

a := 1
f := func() int { a++; return a }
x := []int{a, f()}            // x 可能为 [1, 2] 或 [2, 2]: a 和 f() 之间的求值顺序未指定
m := map[int]int{a: 1, a: 2}  // m 可能为 {2: 1} 或 {2: 2}: 两个映射赋值的求值顺序未指定
n := map[int]int{a: f()}      // n 可能Wie {2: 3} 或 {3: 3}: key 和 value之间的求值顺序未指定

在包级别,初始化依赖会覆盖单个初始化表达式的从左到右的规则,但不会覆盖每个表达式中的操作数的顺序规则:

var a, b, c = f() + v(), g(), sqr(u()) + v()

func f() int        { return c }
func g() int        { return a }
func sqr(x int) int { return x*x }

// 函数 u 和 v 独立于所有其他变量和函数

函数调用按顺序u(), sqr(), v(), f(), v(), 和 g()发生。

单个表达式中的浮点运算根据运算符的关联性求值。显式括号通过覆盖默认关联性来影响求值顺序。在表达式x + (y + z)中,在加x前先执行加法(y + z)

2.5 iota常量生成器

在常量声明中,预先声明的标识符iota表示连续的无类型整数常量。它的值是该常量声明中相应ConstSpec的索引,从零开始。它可以用于构造一组相关的常量:

const (
	c0 = iota  // c0 == 0
	c1 = iota  // c1 == 1
	c2 = iota  // c2 == 2
)

const (
	a = 1 << iota  // a == 1  (iota == 0)
	b = 1 << iota  // b == 2  (iota == 1)
	c = 3          // c == 3  (iota == 2, unused)
	d = 1 << iota  // d == 8  (iota == 3)
)

const (
	u         = iota * 42  // u == 0     (untyped integer constant)
	v float64 = iota * 42  // v == 42.0  (float64 constant)
	w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0
const y = iota  // y == 0

根据定义,同一ConstSpec中iota的多次使用都具有相同的值:

const (
	bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0  (iota == 0)
	bit1, mask1                           // bit1 == 2, mask1 == 1  (iota == 1)
	_, _                                  //                        (iota == 2, unused)
	bit3, mask3                           // bit3 == 8, mask3 == 7  (iota == 3)
)

最后一个示例利用最后一个非空表达式列表的隐式重复。

参考:
https://golang.org/doc/effective_go.html#variables
https://golang.org/ref/spec#Variables
https://golang.org/ref/spec#Constant_expressions
https://golang.org/ref/spec#Order_of_evaluation

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值