编辑
2023-03-14
🧰语言-go
00
请注意,本文编写于 828 天前,最后修改于 225 天前,其中某些信息可能已经过时。

目录

1.常量和变量
类型不确定值和类型确定值
类型不确定常量的显式类型转换
类型推断介绍
常量声明(constant declaration)
类型确定具名常量
常量声明中的自动补全
在常量声明中使用iota
2.变量声明和赋值操作语句
标准变量声明形式
纯赋值语句
短变量声明形式
关于“赋值”这个术语
每个局部声明的变量至少要被有效使用一次
若干包级变量在声明时刻的依赖关系将影响它们的初始化顺序
非常量数字值相关的显式类型转换规则
变量和常量的作用域
更多关于常量声明
每个常量标识符将在编译的时候被其绑定的字面量所替代
3.运算操作符
常量表达式
算数运算符
关于溢出

1.常量和变量

类型不确定值和类型确定值

  • 一个字面(常)量的默认类型取决于它为何种字面量形式:
  • 个字符串字面量的默认类型是预声明的string类型。
  • 一个布尔字面量的默认类型是预声明的bool类型。
  • 一个整数型字面量的默认类型是预声明的int类型。
  • 一个rune字面量的默认类型是预声明的rune(亦即int32)类型。
  • 一个浮点数字面量的默认类型是预声明的float64类型。
  • 如果一个字面量含有虚部字面量,则此字面量的默认类型是预声明的complex128类型

类型不确定常量的显式类型转换

一些合法的转换例子:

go
// 结果为complex128类型的1.0+0.0i。虚部被舍入了。 complex128(1 + -1e-1000i) // 结果为float32类型的0.5。这里也舍入了。 float32(0.49999999) // 只要目标类型不是整数类型,舍入都是允许的。 float32(17000000000000000) float32(123) uint(1.0) int8(-123) int16(6+0i) complex128(789) string(65) // "A" string('A') // "A" string('\u68ee') // "森" string(-1) // "\uFFFD" string(0xFFFD) // "\uFFFD" string(0x2FFFFFFFF) // "\uFFFD"

下面是一些非法的转换:

go
int(1.23) // 1.23不能被表示为int类型值。 uint8(-1) // -1不能被表示为uint8类型值。 float64(1+2i) // 1+2i不能被表示为float64类型值。 // -1e+1000不能被表示为float64类型值。不允许溢出。 float64(-1e1000) // 0x10000000000000000做为int值将溢出。 int(0x10000000000000000) // 字面量65.0的默认类型是float64(不是一个整数类型)。 string(65.0) // 66+0i的默认类型是complex128(不是一个整数类型)。 string(66+0i)

类型推断介绍

Go支持类型推断(type deduction or type inference)。

类型推断是指在某些场合下,程序员可以在代码中使用一些类型不确定值, 编译器会自动推断出这些类型不确定值在特定情景下应被视为某些特定类型的值

在Go代码中,如果某处需要一个特定类型的值并且一个类型不确定值可以表示为此特定类型的值, 则此类型不确定值可以使用在此处。Go编译器将此类型不确定值视为此特定类型的类型确定值。 这种情形常常出现在运算符运算、函数调用和赋值语句中。

有些场景对某些类型不确定值并没有特定的类型要求。在这种情况下,Go编译器将这些类型不确定值视为它们各自的默认类型的类型确定值。

常量声明(constant declaration)

go
package main // 声明了两个单独的具名常量。(是的, // 非ASCII字符可以用做标识符。) const π = 3.1416 const Pi = π // 等价于:const Pi = 3.1416 // 声明了一组具名常量。 const ( No = !Yes Yes = true MaxDegrees = 360 Unit = "弧度" ) func main() { // 声明了三个局部具名常量。 const DoublePi, HalfPi, Unit2 = π * 2, π * 0.5, "度" }

GO白皮书把上面每行含有一个等号=的语句称为一个常量描述。

每个const关键字对应一个常量声明。

一个常量声明中可以有多干个常量描述。

上面的例子中含有4个常量声明。除了第3个,其它的常量声明中都各自只有一个常量描述。 第3个常量声明中有4个常量描述。

在上面的例子中,符号*是一个乘法运算符, 符号!是一个布尔取否运算符。

常量声明中的等号=表示“绑定”而非“赋值”。 每个常量描述将一个或多个字面量绑定到各自对应的具名常量上。 或者说,每个具名常量其实代表着一个字面常量。

在上面的例子中,具名常量π和Pi都绑定到(或者说代表着)字面常量3.1416。 这两个具名常量可以在程序代码中被多次使用,从而有效避免了字面常量3.1416在代码中出现在多处。 如果字面常量3.1416在代码中出现在多处, 当我们以后欲将3.1416改为3.14的时候,所有出现在代码中的3.1416都得逐个修改。 有了具名常量的帮助,我们只需修改对应常量描述中的3.1416即可。 这是常量声明的主要作用。当然常量声明也可常常增加代码的可读性(代码即注释)。

注意,常量可以直接声明在包中,也可以声明在函数体中。 声明在函数体中的常量称为局部常量(local constant),直接声明在包中的常量称为包级常量(package-level constant)。 包级常量也常常被称为全局常量。

类型确定具名常量

我们可以在声明一些常量的时候指定这些常量的确切类型。 这样声明的常量称为类型确定具名常量。 在下面这个例子中,所有这4个声明的常量都是类型确定的。 X和Y的类型都是float32, A和B的类型都是int64。

go
const X float32 = 3.14 const ( A, B int64 = -3, 5 Y float32 = 2.718 )

我们也可以使用显式类型转换来声明类型确定常量。 下面的例子和上面的例子是完全等价的。

go
const X = float32(3.14) const ( A, B = int64(-3), int64(5) Y = float32(2.718) )

欲将一个字面常量绑定到一个类型确定具名常量上,此字面常量必须能够表示为此常量的确定类型的值。 否则,编译将报错。

go
const a uint8 = 256 // error: 256溢出uint8 const b = uint8(255) + uint8(1) // error: 256溢出uint8 const c = int8(-128) / int8(-1) // error: 128溢出int8 const MaxUint_a = uint(^0) // error: -1溢出uint const MaxUint_b uint = ^0 // error: -1溢出uint

下面这个类型确定常量声明在64位的操作系统上是合法的,但在32位的操作系统上是非法的。 因为一个uint值在32位操作系统上的尺寸是32位, (1 << 64) - 1将溢出uint。(这里,符号<<为左移位运算符。)

go
const MaxUint uint = (1 << 64) - 1

那么如何声明一个代表着最大uint值的常量呢? 我们可以用下面这个常量声明来替换上面这个。下面这个声明在64位和32位的操作系统上都是合法的。

go
const MaxUint = ^uint(0)

类似地,我们可以使用下面这个常量声明来声明一个具名常量来表示最大的int值。(这里,符号>>为右移位运算符。)

go
const MaxInt = int(^uint(0) >> 1)

使用类似的方法,我们可以声明一个常量来表示当前操作系统的位数,或者检查当前操作系统是32位的还是64位的。

go
const NativeWordBits = 32 << (^uint(0) >> 63) // 64 or 32 const Is64bitOS = ^uint(0) >> 63 != 0 const Is32bitOS = ^uint(0) >> 32 == 0

常量声明中的自动补全

在一个包含多个常量描述的常量声明中,除了第一个常量描述,其它后续的常量描述都可以只包含标识符列表部分。 Go编译器将通过照抄前面最紧挨的一个完整的常量描述来自动补全不完整的常量描述。 比如,在编译阶段,编译器会将下面的代码

go
const ( X float32 = 3.14 Y // 这里必须只有一个标识符 Z // 这里必须只有一个标识符 A, B = "Go", "language" C, _ // 上一行中的空标识符是必需的(如果 // 上一行是一个不完整的常量描述的话)。 )

自动补全为

go
const ( X float32 = 3.14 Y float32 = 3.14 Z float32 = 3.14 A, B = "Go", "language" C, _ = "Go", "language" )

在常量声明中使用iota

iota是Go中预声明(内置)的一个特殊的具名常量。 iota被预声明为0,但是它的值在编译阶段并非恒定。 当此预声明的iota出现在一个常量声明中的时候,它的值在第n个常量描述中的值为n(从0开始)。 所以iota只对含有多个常量描述的常量声明有意义。

iota和常量描述自动补全相结合有的时候能够给Go编程带来很大便利。 比如,下面是一个使用了这两个特性的例子。 请阅读代码注释以了解清楚各个常量被绑定的值。

go
package main func main() { const ( k = 3 // 在此处,iota == 0 m float32 = iota + .5 // m float32 = 1 + .5 n // n float32 = 2 + .5 p = 9 // 在此处,iota == 3 q = iota * 2 // q = 4 * 2 _ // _ = 5 * 2 r // r = 6 * 2 s, t = iota, iota // s, t = 7, 7 u, v // u, v = 8, 8 _, w // _, w = 9, 9 ) const x = iota // x = 0 (iota == 0) const ( y = iota // y = 0 (iota == 0) z // z = 1 ) println(m) // +1.500000e+000 println(n) // +2.500000e+000 println(q, r) // 8 12 println(s, t, u, v, w) // 7 7 8 8 9 println(x, y, z) // 0 0 1 }

上面的例子只是展示了一下如何使用iota。 在实际编程中,我们应该用有意义的方式使用之。比如:

go
const ( Failed = iota - 1 // == -1 Unknown // == 0 Succeeded // == 1 ) const ( Readable = 1 << iota // == 1 Writable // == 2 Executable // == 4 )

2.变量声明和赋值操作语句

标准变量声明形式

每条标准变量声明形式语句起始于一个var关键字。 每个var关键字跟随着一个变量名。 每个变量名必须为一个标识符。

下面是几条完整形式的标准变量声明语句。 这些声明确地指定了被声明的变量的类型和初始值。

go
var lang, website string = "Go", "https://golang.org" var compiled, dynamic bool = true, false var announceYear int = 2009

完整形式的标准变量声明使用起来有些罗嗦,因此很少在日常Go编程中使用。 在日常Go编程中,另外两种变种形式用得更广泛一些。 一种变种形式省略了变量类型(但仍指定了变量的初始值),这时编译器将根据初始值的字面量形式来推断出变量的类型。 另一种变种形式省略了初始值(但仍指定了变量类型),这时编译器将使用变量类型的零值做为变量的初始值。

下面是一些第一种变种形式的用例。在这些用例中,如果一个初始值是一个类型确定值,则对应声明的变量的类型将被推断为此初始值的类型; 如果一个初始值是一个类型不确定值,则对应声明的变量的类型将被推断为此初始值的默认类型。 注意在这种变种中,同时声明的多个变量的类型可以不一样。

go
// 变量lang和dynamic的类型将被推断为内置类型string和bool。 var lang, dynamic = "Go", false // 变量compiled和announceYear的类型将被推断 // 为内置类型bool和int。 var compiled, announceYear = true, 2009 // 变量website的类型将被推断为内置类型string。 var website = "https://golang.org"

上例中的类型推断可以被视为隐式类型转换。

下例展示了几个省略了初始值的标准变量声明。每个声明的变量的初始值为它们各自的类型的零值

go
var lang, website string // 两者都被初始化为空字符串。 var interpreted, dynamic bool // 两者都被初始化为false。 var n int // 被初始化为0。

和常量声明一样,多个变量可以用一对小括号组团在一起被声明。

go
var ( lang, bornYear, compiled = "Go", 2007, true announceAt, releaseAt int = 2009, 2012 createdBy, website string )

上面这个变量声明语句已经被go fmt命令格式化过了。 这个变量声明语句包含三个变量描述(variable specification)。

一般来说,将多个相关的变量声明在一起将增强代码的可读性。

纯赋值语句

在上面展示的变量声明的例子中,等号=表示赋值。 一旦一个变量被声明之后,它的值可以被通过纯赋值语句来修改。 多个变量可以同时在一条赋值语句中被修改。

一个赋值语句等号左边的表达式必须是一个可寻址的值、一个映射元素或者一个空标识符。 内存地址(以及指针)和映射将在以后的文章中介绍。

常量是不可改变的(不可寻址的),所以常量不能做为目标值出现在纯赋值语句的左边,而只能出现在右边用做源值。 变量既可以出现在纯赋值语句的左边用做目标值,也可以出现在右边用做源值。

空标识符也可以出现在纯赋值语句的左边,表示不关心对应的目标值。 空标识符不可被用做源值。

一个包含了很多(合法或者不合法的)纯赋值语句的例子:

go
const N = 123 var x int var y, z float32 N = 789 // error: N是一个不可变量 y = N // ok: N被隐式转换为类型float32 x = y // error: 类型不匹配 x = N // ok: N被隐式转换为类型int y = x // error: 类型不匹配 z = y // ok _ = y // ok z, y = y, z // ok _, y = y, z // ok z, _ = y, z // ok _, _ = y, z // ok x, y = 69, 1.23 // ok x, y = y, x // error: 类型不匹配 x, y = int(y), float32(x) // ok

上例中的最后一行使用了显式类型转换,否则此赋值(见倒数第二行)将不合法。 数字非常量值的类型转换规则将在后边的章节介绍。

Go不支持某些其它语言中的连等语法。下面的赋值语句在Go中是不合法的。

go
var a, b int a = b = 123 // 语法错误

短变量声明形式

我们也可以用短变量声明形式来声明一些局部变量。比如下例:

go
package main func main() { // 变量lang和year都为新声明的变量。 lang, year := "Go language", 2007 // 这里,只有变量createdBy是新声明的变量。 // 变量year已经在上面声明过了,所以这里仅仅 // 改变了它的值,或者说它被重新声明了。 year, createdBy := 2009, "Google Research" // 这是一个纯赋值语句。 lang, year = "Go", 2012 print(lang, "由", createdBy, "发明") print("并发布于", year, "年。") println() }

每个短声明语句中必须至少有一个新声明的变量。

从上面的例子中,我们可以看到短变量声明形式和标准变量声明形式有几个显著的区别:

  1. 短声明形式不包含var关键字,并且不能指定变量的类型。
  2. 短变量声明中的赋值符号必须为:=。
  3. 在一个短声明语句的左侧,已经声明过的变量和新声明的变量可以共存。 但在一个标准声明语句中,所有出现在左侧的变量必须都为新声明的变量。 注意,相对于纯赋值语句,目前短声明语句有一个限制:出现在一个短声明左侧的项必须都为纯标识符。 以后我们将学习到在纯赋值语句的左边可以出现结构体值的字段,指针的解引用和容器类型值的元素索引项等。 但是这些项不能出现在一个变量短声明语句的左边。

关于“赋值”这个术语

以后,当“赋值”这个术语被提到的时候,它可以指一个纯赋值、一个短变量声明或者一个初始值未省略的标准变量声明。 事实上,一个更通用的定义包括后续文章将要介绍的函数传参。

当y = x是一条合法的赋值语句时,我们可以说x可以被赋给y。 假设y的类型为Ty,有时为了叙述方便,我们也可以说x可以被赋给类型Ty。

一般来说,如果x可以被赋给y,则y应该是可修改的,并且x和y的类型相同或者x可以被隐式转换到y的类型。 当然,y也可以是空标识符_。

每个局部声明的变量至少要被有效使用一次

注意,当使用目前的主流Go编译器编译Go代码时,一个局部变量被声明之后至少要被有效使用一次,否则编译器将报错。 包级变量无此限制。 如果一个变量总是被当作赋值语句中的目标值,那么我们认为这个变量没有被有效使用过。

go
package main var x, y, z = 123, true, "foo" // 包级变量 func main() { var q, r = 789, false r, s := true, "bar" r = y // r没有被有效使用。 x = q // q被有效使用了。 }

当编译上面这个程序的时候,编译器将报错(这个程序代码存在一个名为example-unused.go的文件中):

go
./example-unused.go:6:6: r declared and not used ./example-unused.go:7:16: s declared and not used

避免编译器报错的方法很简单,要么删除相关的变量声明,要么像下面这样,将未曾有效使用过的变量(这里是r和s)赋给空标识符。

go
package main var x, y, z = 123, true, "foo" func main() { var q, r = 789, false r, s := true, "bar" r = y x = q _, _ = r, s // 将r和s做为源值使用一次。 }

若干包级变量在声明时刻的依赖关系将影响它们的初始化顺序

下面这个例子中的声明的变量的初始化顺序为y = 5、c = y、b = c+1、a = b+1、x = a+1。

go
var x, y = a+1, 5 // 8 5 var a, b, c = b+1, c+1, y // 7 6 5

包级变量在初始化的时候不能相互依赖。比如,下面这个变量声明语句编译不通过。

go
var x, y = y, x

非常量数字值相关的显式类型转换规则

在Go中,两个类型不一样的基本类型值是不能相互赋值的。 我们必须使用显式类型转换将一个值转换为另一个值的类型之后才能进行赋值。

具体规则如下:

  • 当从一个比特位数多的整数类型的非常量整数值向一个比特位数少的整数类型转换的时候,高位的比特将被舍弃,低位的比特将被保留。我们称这种处理方式为截断(truncated)。
  • 当从一个非常量的浮点数向一个整数类型转换的时候,浮点数的小数部分将被舍弃(向零靠拢)。
  • 当从一个非常量整数或者浮点数向一个浮点数类型转换的时候,精度丢失是可以发生的。
  • 当从一个非常量复数向另一个复数类型转换的时候,精度丢失也是可以发生的。
  • 当一个显式转换涉及到非常量浮点数或者复数数字值时,如果源值溢出了目标类型的表示范围,则转换结果取决于具体编译器实现(即行为未定义)。

在下面的例子中,第7行和第15行的隐式转换是不允许的,第5行和第14行的显式转换也是不允许的。

go
const a = -1.23 // 变量b的类型被推断为内置类型float64。 var b = a // error: 常量1.23不能被截断舍入到一个整数。 var x = int32(a) // error: float64类型值不能被隐式转换到int32。 var y int32 = b // ok: z == -1,变量z的类型被推断为int32。 // z的小数部分将被舍弃。 var z = int32(b) const k int16 = 255 var n = k // 变量n的类型将被推断为int16。 var f = uint8(k + 1) // error: 常量256溢出了uint8。 var g uint8 = n + 1 // error: int16值不能隐式转换为uint8。 var h = uint8(n + 1) // ok: h == 0,变量h的类型为uint8。 // (n+1)溢出uint8,所以只有低8位 // bits(都为0)被保留。

第3行的隐式转换中,a被转换为它的默认类型(float64);因此b的类型被推断为float64。

变量和常量的作用域

在Go中,我们可以使用一对大括号来显式形成一个(局部)代码块。一个代码块可以内嵌另一个代码块。 最外层的代码块称为包级代码块。 一个声明在一个内层代码块中的常量或者变量将遮挡另一个外层代码块中声明的同名变量或者常量。 比如,下面的代码中声明了3个名为x的变量。 内层的x将遮挡外层的x, 从而外层的x在内层的x声明之后在内层中将不可见。

go
package main const y = 70 var x int = 123 // 包级变量 func main() { // 此x变量遮挡了包级变量x。 var x = true // 一个内嵌代码块。 { x, y := x, y-10 // 这里,左边的x和y均为新声明 // 的变量。右边的x为外层声明的 // bool变量。右边的y为包级变量。 // 在此内层代码块中,从此开始, // 刚声明的x和y将遮挡外层声明x和y。 x, z := !x, y/10 // z是一个新声明的变量。 // x和y是上一句中声明的变量。 println(x, y, z) // false 60 6 } println(x) // true println(y) // 70 (包级变量y从未修改) /* println(z) // error: z未定义。 // z的作用域仅限于上面的最内层代码块。 */ }

刚提到的作用域是指一个标识符的可见范围。 一个包级变量或者常量的作用域为其所处于的整个代码包。 一个局部变量或者常量的作用域开始于此变量或者常量的声明的下一行,结束于最内层包含此变量或者常量的声明语句的代码块的结尾。 这解释了为什么上例中的println(z)将编译不通过。

更多关于常量声明

一个类型不确定常量所表示的值可以溢出其默认类型 比如,下例中的三个类型不确定常量均溢出了它们各自的默认类型,但是此程序编译和运行都没问题。

go
package main // 三个类型不确定常量。 const n = 1 << 64 // 默认类型为int const r = 'a' + 0x7FFFFFFF // 默认类型为rune const x = 2e+308 // 默认类型为float64 func main() { _ = n >> 2 _ = r - 0x7FFFFFFF _ = x / 2 }

但是下面这个程序编译不通过,因为三个声明的常量为类型确定常量。

go
package main // 三个类型确定常量。 const n int = 1 << 64 // error: 溢出int const r rune = 'a' + 0x7FFFFFFF // error: 溢出rune const x float64 = 2e+308 // error: 溢出float64 func main() {}

每个常量标识符将在编译的时候被其绑定的字面量所替代

常量声明可以看作是增强型的C语言中的#define宏。 在编译阶段,所有的标识符将被它们各自绑定的字面量所替代。

如果一个运算中的所有运算数都为常量,则此运算的结果也为常量。或者说,此运算将在编译阶段就被估值。 下一篇文章将介绍Go中的常用运算符。

go
package main const X = 3 const Y = X + X var a = X func main() { b := Y println(a, b, X, Y) }
go
package main var a = 3 func main() { b := 6 println(a, b, 3, 6) }

3.运算操作符

本文只介绍算术运算符、位运算符、比较运算符、布尔运算符和字符串衔接运算符。 这些运算符要么是二元的(需要两个操作数),要么是一元的(需要一个操作数)。 所有这些运算符运算都只返回一个结果。操作数常常也称为操作值。

本文中的解释不追求描述的完全准确性。 比如,当我们说一个二元运算符运算需要其涉及的两个操作数类型必须一样的时,这指:

  • 如果这两个操作数都是类型确定值,则它们的类型必须相同,或者其中一个操作数可以被隐式转换到另一个操作数的类型。
  • 如果其中只有一个操作数是类型确定的,则要么另外一个类型不确定操作数可以表示为此类型确定操作数的类型的值,要么此类型不确定操作数的默认类型的任何值可以被隐式转换到此类型确定操作数的类型。
  • 如果这两个操作数都是类型不确定的,则它们必须同时都为两个布尔值,同时都为两个字符串值,或者同时都为两个基本数字值。
  • 类似的,当我们说一个运算符(一元或者二元)运算要求其涉及的某个操作数的类型必须为某个特定类型时,这指:
  • 如果这个操作数是类型确定的,则它的类型必须为所要求的特定类型,或者此操作数可以被隐式转换为所要求的特定类型。
  • 如果一个操作数是类型不确定的,则要么此操作数可以表示为所要求的特定类型值,要么此操作数的默认类型的任何值可以被隐式转换为所要求的特定类型。

常量表达式

在继续下面的章节之前,我们需要知道什么叫常量表达式和关于常量表达式估值的一个常识。 表达式的概念将在表达式和语句一文中得到解释。 目前我们只需知道本文中所提及的大多数运算都属于表达式。 当一个表达式中涉及到的所有操作数都是常量时,此表达式称为一个常量表达式。 一个常量表达式的估值是在编译阶段进行的。一个常量表达式的估值结果依然是一个常量。 如果一个表达式中涉及到的操作数中至少有一个不为常量,则此表达式称为非常量表达式。

算数运算符

GO支持5个基本二元算术运算符:

字面形式名称对两个运算数的要求
+加法两个运算符数的类型必须相同并且为基本数值类型
-减法同上
*乘法同上
/除法同上
%余数两个运算数的类型必须相同并且为基本整数数值类型

go支持六种位运算符(也属于算术运算)

字面形式名称对两个运算数的要求及机制解释
&位与两个操作数的类型必须相同并且为基本整数数值类型。机制解释(下标2表明一个字面量为二进制):11002 & 10102 得到 10002 11002
位或
^(位)异或同上
&^清位同上
字面形式名称对两个运算数的要求及机制解释
<<左移位左操作数必须为一个整数,右操作数也必须为一个整数(如果它是一个常数,则它必须非负),但它们的类型可以不同。 (注意:在Go 1.13之前,右操作数必须为一个无符号整数类型的类型确定值或者一个可以表示成uint值的类型不确定常数值。)一个负右操作数(非常数)将在运行时刻造成一个恐慌。机制解释:11002 << 3 得到 11000002(低位补零)11002 >> 3 得到 12(低位被舍弃) 注意,在右移运算中,左边空出来的位(即高位)全部用左操作数的最高位(即正负号位)填充。 比如如果左操作数-128的类型为int8(二进制补码表示为100000002), 则100000002 >> 2的二进制补码结果为111000002(即-32)。
>>右移位同上

Go也支持三个一元算术运算符

字面形式名称对两个运算数的要求及机制解释
+取正数+n等价于0 + n.
-取负数-n等价于0 - n.
^位反(或位补)^n等价于m ^ n,其中m和n同类型并且它的二进制表示中所有比特位均为1。 比如如果n的类型为int8,则m的值为-1;如果n的类型为uint8,则m的值为255。

注意:

  • 在很多其它流行语言中,位反运算符是用~表示的。
  • 和一些其它流行语言一样,加号运算符+也可用做字符串衔接运算符(见下)。
  • 和C及C++语言一样,*除了可以当作乘号运算符,它也可以用做指针解引用运算符; &除了可以当作位与运算符,它也可以用做取地址运算符。 后面的指针一文将详解内存地址和指针类型。
  • 和Java不一样,Go支持无符号数,所以Go不需要无符号右移运算符>>>。
  • Go不支持幂运算符, 我们必须使用math标准库包中的Pow函数来进行幂运算。 下一篇文章将详解包和包引入。
  • 清位运算符&^是Go中特有的一个运算符。 m &^ n等价于m & (^n)。

一些运算符的使用示例:

go
func main() { var ( a, b float32 = 12.0, 3.14 c, d int16 = 15, -6 e uint8 = 7 ) // 这些行编译没问题。 _ = 12 + 'A' // 两个类型不确定操作数(都为数值类型) _ = 12 - a // 12将被当做a的类型(float32)使用。 _ = a * b // 两个同类型的类型确定操作数。 _ = c % d _, _ = c + int16(e), uint8(c) + e _, _, _, _ = a / b, c / d, -100 / -9, 1.23 / 1.2 _, _, _, _ = c | d, c & d, c ^ d, c &^ d _, _, _, _ = d << e, 123 >> e, e >> 3, 0xF << 0 _, _, _, _ = -b, +c, ^e, ^-1 // 这些行编译将失败。 _ = a % b // error: a和b都不是整数 _ = a | b // error: a和b都不是整数 _ = c + e // error: c和e的类型不匹配 _ = b >> 5 // error: b不是一个整数 _ = c >> -5 // error: -5不是一个无符号整数 _ = e << uint(c) // 编译没问题 _ = e << c // 从Go 1.13开始,此行才能编译过 _ = e << -c // 从Go 1.13开始,此行才能编译过。 // 将在运行时刻造成恐慌。 _ = e << -1 // error: 右操作数不能为负(常数) }

关于溢出

上一篇文章提到了

  • 一个类型确定数字型常量所表示的值是不能溢出它的类型的表示范围的。
  • 一个类型不确定数字型常量所表示的值是可以溢出它的默认类型的表示范围的。 当一个类型不确定数字常量值溢出它的默认类型的表示范围时,此数值不会被截断(亦即回绕)。
  • 将一个非常量数字值转换为其它数字类型时,此非常量数字值可以溢出转化结果的类型。 在此转换中,当溢出发生时,转化结果为此非常量数字值的截断(亦即回绕)表示。
go
// 结果为非常量 var a, b uint8 = 255, 1 var c = a + b // c==0。a+b是一个非常量表达式, // 结果中溢出的高位比特将被截断舍弃。 var d = a << b // d == 254。同样,结果中溢出的 // 高位比特将被截断舍弃。 // 结果为类型不确定常量,允许溢出其默认类型。 const X = 0x1FFFFFFFF * 0x1FFFFFFFF // 没问题,尽管X溢出 const R = 'a' + 0x7FFFFFFF // 没问题,尽管R溢出 // 运算结果或者转换结果为类型确定常量 var e = X // error: X溢出int。 var h = R // error: R溢出rune。 const Y = 128 - int8(1) // error: 128溢出int8。 const Z = uint8(255) + 1 // error: 256溢出uint8。

本文作者:Eric

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!