常量
在go中,所有的字面值都是常量,被称为“无名常量”,false和true是预声明的两个具名常量。自定义具名常量使用关键字 const 定义,用于存储不会改变的数据。和C/C++中宏定义类似。
常量声明中的等号=表示“绑定”而非“赋值”。 每个常量描述将一个或多个字面量绑定到各自对应的具名常量上。 或者说,每个具名常量其实代表着一个字面常量。因为在编译阶段,所有的标识符将被它们各自绑定的字面量所替代。如果一个运算中的所有运算数都为常量,则此运算的结果也为常量。
存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型,其它的类型不允许以常量的形式存在。go语言定义具名常量的格式如下:
const 常量名[数据类型] = 值
其中,数据类型是可选的,你可以省略类型说明符,因为编译器可以根据常量的值来推断其类型。例如:
package main
const ( // 包级常量
No = !Yes
Yes = true
MaxDegrees = 360
Unit = "弧度"
)
func main() {
const a = 10 // 类型不确定常量,因为10可以是uint8,int8,int,int64,float64等
const b int64 = 12345 // 类型确定常量,声明了是int64类型
const c = float32(123.123) // 类型确定常量,声明了float32类型的常量
}
包级常量声明中的常量描述的顺序并不重要。比如在上面的例子中, 常量描述No和Yes的顺序可以掉换一下。
类型不确定常量的默认类型和它们各自代表的字面量的默认类型是一样的。
- 一个字符串字面量的默认类型是预声明的string类型。
- 一个布尔字面量的默认类型是预声明的bool类型。
- 一个整数型字面量的默认类型是预声明的int类型。
- 一个rune字面量的默认类型是预声明的rune(亦即int32)类型。
- 一个浮点数字面量的默认类型是预声明的float64类型。
- 如果一个字面量含有虚部字面量,则此字面量的默认类型是预声明的complex128类型。
类型推断
类型推断是指在某些场合下,程序员可以在代码中使用一些类型不确定值, 编译器会自动推断出这些类型不确定值在特定情景下应被视为某些特定类型的值。
- 在Go代码中,如果某处需要一个特定类型的值并且一个类型不确定值可以表示为此特定类型的值, 则此类型不确定值可以使用在此处。Go编译器将此类型不确定值视为此特定类型的类型确定值。 这种情形常常出现在运算符运算、函数调用和赋值语句中。
- 有些场景对某些类型不确定值并没有特定的类型要求。在这种情况下,Go编译器将这些类型不确定值视为它们各自的默认类型的类型确定值。
上述两条类型推断规则可以被视为隐式转换规则。
常量声明中的自动补全
const (
X float32 = 3.14
Y // 这里必须只有一个标识符
Z // 这里必须只有一个标识符
A, B = "Go", "language"
C, _
// 上一行中的空标识符是必需的(如果
// 上一行是一个不完整的常量描述的话)。
)
关于这个空白标识符,需要专门写一篇来讲一下。《go语言的空白标识符》
iota
iota是Go中预声明(内置)的一个特殊的具名常量。 iota被预声明为0,但是它的值在编译阶段并非恒定。 当此预声明的iota出现在一个常量声明中的时候,它的值在第n个常量描述中的值为n(从0开始)。实际编程中的一个例子如下所示:
const (
Mon = iota + 1
Tue
Wed
Thu
Fri
Sat
Sun
)
结合常量声明自动补全,上面的代码相当于。
const (
Mon = iota + 1 // iota == 0
Tue = iota + 1 // iota == 1
Wed = iota + 1 // iota == 2
Thu = iota + 1 // iota == 3
Fri = iota + 1 // iota == 4
Sat = iota + 1 // iota == 5
Sun = iota + 1 // iota == 6
)
在一个包含多个常量描述的常量声明中,除了第一个常量描述,其它后续的常量描述都可以只包含标识符列表部分。 Go编译器将通过照抄前面最紧挨的一个完整的常量描述来自动补全不完整的常量描述。因此,上面的代码等价于下面的代码:
const (
X float32 = 3.14
Y float32 = 3.14
Z float32 = 3.14
A, B = "Go", "language"
C, _ = "Go", "language"
)
如果一个运算中的所有运算数都为常量,则此运算的结果也为常量。或者说,此运算将在编译阶段就被估值。
常量类型转换
常量的类型转换T(v)结果一般仍然是一个常量。
- 给定一个常量值x和一个类型T,如果x可以表示成类型T的一个值,则x可以被显式地转换为类型T;否则无法转换,编译失败。
- 特别地,如果x是一个类型不确定值常量且可以表示为类型T的值,则它可以被隐式转换为类型T。例如:
const a uint16 = 12345 // 字面值12345类型是不确定值且可以表示为uint16,被隐式类型转换为uint16
const b float32 = 123.123 // 字面值123.123的类型是不确定值且可以表示为float32,被隐式类型转换为float32
const c float64 = float64(a) // 显式类型转换,a是uint16类型,和float64类型不一致,必须通过显式类型转
变量
变量的值可以被更改,所有的变量值都是类型确定值。当声明一个变量的时候,我们必须在代码中给编译器提供足够的信息来让编译器推断出此变量的确切类型。go语言使用关键字var定义变量,常用的定义变量格式如下所示:
var 变量名[数据类型] = 值
和常量定义一样,其中的数据类型是可选的,你可以省略类型说明符,因为编译器可以做类型推断。
为了更加方便程序员书写代码,下面两段代码是等价的,但是第二段少写了两个var。
// 第一段代码
var a int
var b bool
var str string
// 第二段代码
var (
a int
b bool
str string
)
在一个函数体内声明的变量称为局部变量。 在任何函数体外声明的变量称为包级或者全局变量。Go语言有两种变量声明形式。一种称为标准形式,另一种称为短声明形式。 短声明形式只能用来声明局部变量。例如:
package main
import "fmt"
var g = 100 // 全局变量
func main() {
fmt.Println(g)
g := 200 // 局部变量, 短声明形式。
g, f: = g, 100
fmt.Println(g)
}
- 短声明形式不包含var关键字,并且不能指定变量的类型。
- 短变量声明中的赋值符号必须为:=。
- 在一个短声明语句的左侧,已经声明过的变量和新声明的变量可以共存。 但在一个标准声明语句中,所有出现在左侧的变量必须都为新声明的变量。
需要说明的是,go是编译器在编译时进行数据类型的推断,而不是在运行时。python这样的语言是在运行时(实际上,python只拥有运行时)进行数据类型推断。
go虽然是编译型语言,但是go被设计为拥有垃圾回收等机制,因此go还需要一个运行时。这个也需要专门开一篇文章讲述。《go语言的运行时》
还有一点需要说明的是,go的变量声明形式将类型说明符放在了最后面,它能在一定程度上减少C/C++的那种错误。例如:
package main
import "fmt"
var g = 100 // 全局变量
func main() {
var a, b, c int = 1, 2, 3 //a,b,c都是int类型
fmt.Println(a, b, c)
var j, k, l *int = &a, &b, &c // j,k,l都是int类型的指针
fmt.Println(j, k, l)
var (
q int = 1
w float32 = 3.3
e string = "qwe"
)
_, _, _ = q, w, e
}
最后一行使用了空白标识符,这是在go语言中常见的手段之一。因为go语言中的常量在绑定之后,如果不使用,是不会产生编译报错的。但是go中的局部变量则至少需要被有效使用(有效使用是指除了被赋值之外的地方使用)一次,才不会导致编译器报错。包级变量无此限制。变量j, k, l都是int类型的指针,如果在C/C++中,进行如下的定义。
int a, *b
那么a是int类型,而b是int类型的指针。
个人的一些废话,go是静态语言,但是不知道为什么搞了这个类型推断,是为了让写JavaScript的人快速上手吗?但是实际开发的时候,你就几乎一定会遇到类型转换,最后还是会回到这个问题。因为你的短声明实际上一个类型确定值,他不像是动态语言中的变量。所以我觉得在使用go的时候,还是需要指明类型。
非常量数字值相关的显式类型转换规则
变量是非常量,听起来像是废话。但是变量确实是“非常量“。
-
非常量浮点数和整数值可以被显式转换为任何浮点数和整数类型。
-
非常量复数值可以被显式转换为任何复数类型。(主要是指complex64和complex128之间的转换)
注意事项:非常量复数值不能被转换为浮点数或整数类型。
非常量浮点数和整数值不能被转换为复数类型。
在非常量数值的转换过程中,溢出和舍入是允许的。当一个浮点数被转换为整数时,小数部分将被舍弃(向零靠拢)。
值类型和引用类型
像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,数组和结构这些复合类型也是值类型,当使用等号 = 将一个值类型的变量赋值给另一个变量时,实际上是在内存中进行了值拷贝。值类型的变量存储在栈空间中。
引用类型包括指针,slice,map和 channel。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。引用类型之间的赋值操作,只会拷贝引用的地址,例如:r1 = r2,当你改变r2的时候,r1也会被改变。
参考资料
- https://learnku.com/docs/the-way-to-go/variable/3585
- https://www.jianshu.com/p/df1eab8c0f9d