Go笔记-常量与变量

浅浅的花香味﹌ 2022-01-16 12:11 485阅读 0赞

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

变量

变量声明

  1. var v1 int
  2. var v2 string
  3. var v3 [10]int // 数组
  4. var v4 []int // 数组切片
  5. var v5 struct {
  6. f int
  7. }
  8. var v6 *int
  9. var v7 map[string]int
  10. var v8 func(a int) int // 函数
  11. var i int8 = 0 等同于 i := (int8)(0)
  12. var i *int8 = nil 等同于 j := (*int8)(nil)
  13. var min, max uint = 1, 5
  14. var i, j, k int // 都是int,零值是0
  15. var a, b, c = true, 2.3, "foo" // 自动推导类型
  16. var f, err = os.Open(name) // 通过调用函数赋值

变量初始化

  1. var v1 int = 10
  2. var v2 = 10 // 编译器自动推导v2的类型
  3. v3 := 10 // 编译器自动推导v2的类型

出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误,比如下面这个 写法:

  1. var i int
  2. i := 2

会导致类似如下的编译错误:

  1. no new variables on left side of :=

:= 不能在函数外使用,函数外的语法块必须以关键字开始

  1. val := 4 // 错误
  2. var val int32 = 4 // 正确

变量赋值

多重赋值功能,比如下面这个交换i和j变量的语句:

  1. i, j = j, i

重新声明和重新赋值

  1. a, err := f1.Close()
  2. b, err := f2.Close()

err在两条语句中都出现了,第一次的err是声明,第二次的err只是重新赋值了,第二个语句中必须有至少一个变量是需要声明的,才能使用:=


重新赋值与定义新同名变量的区别

  1. s := "abs"
  2. fmt.Println(&s)
  3. s, y := "hello", 20 // 重新赋值,与前s在同一层次的代码块中,且有新的变量被定义
  4. fmt.Println(&s, y) // 通常函数多返回值 err 会被重复使用
  5. {
  6. s, z := 1000, 30 // 定义新同名变量,不在同一层次代码块
  7. fmt.Println(&s, z)
  8. }

输出:

  1. 0xc08200c340
  2. 0xc08200c340 20
  3. 0xc08200c380 30

变量被重新赋值时不会改变地址

  1. a := 100
  2. b := "a"
  3. fmt.Println(&a, &b)
  4. a = 200
  5. b = "b"
  6. fmt.Println(&a, &b)

输出:

  1. 0xc08200c3a0 0xc08200c3b0
  2. 0xc08200c3a0 0xc08200c3b0

平行赋值

  1. a[i], a[j] = a[j], a[i]

覆盖的问题: 在进行多个变量赋值时,编译器会先计算赋值语句右边所有的值,之后再进行赋值。

  1. a[i] =1 , a[j] = 2
  2. a[i], a[j] = a[j], a[i] <=> a[i] ,a[j] = 2, 1

在赋值之前, 赋值语句右边的所有表达式将会先进行求值, 然后再统一更新左边变量的值.


来自[雨痕笔记]

多变量赋值时,先计算所有相关值,然后再从左到右依次赋值:

  1. data, i := [3]int{0, 1, 2}, 0
  2. i, data[i] = 2, 100 // (i=0) -> (i=2), (data[0] = 100)
  3. fmt.Println(data) // [100 1 2]

i被赋值成2之前,data[i]已经被计算成data[0]了。

再看一个右边是否也计算的例子:

  1. data, i := [3]int{0, 2, 4}, 1
  2. i, data[i] = data[i], 100
  3. fmt.Println(i, data) // 2 [0 100 4]

也就是说在赋值之前,左右两边都计算过了。

匿名变量

假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName:

  1. func GetName() (firstName, lastName, nickName string) {
  2. return"May", "Chan", "Chibi Maruko"
  3. }

若只想获得nickName,则函数调用语句可以用如下方式编写:

  1. _, _, nickName := GetName()

new()和make()

new:并不初始化内存,只是将其置零,并返回地址。如new(T),其返回一个指向新分配的 类型为T值为零的 指针。

  1. t1 := new(T) // type *T
  2. var t2 T // type T

之后就可以直接使用t1,t2了。

但有时候需要一个初始化构造器:

  1. t := new(T)
  2. t.f1 = v1
  3. t.f2 = v2

也可以使用复合文字(composite literal):

  1. t := T{v1, v2} // type T
  2. t := &T{v1, v2} // type *T

复合文字的域按顺序排列,并且必须都存在。

通过field:value显式地为元素添加标号,则初始化可以按任何顺序出现,没有出现的则对应为零值

  1. t := &T{f2:v2, f1:v1}

new(T)和&T{}是等价的,字段的值都是零值。

make:内建函数make(T, args)与new(T)的用途不一样。它只用来创建slice,map和channel,并且返回一个初始化的(而不是置零),类型为T的值(而不是*T)。之所以有所不同,是因为这三个类型的背后是象征着,对使用前必须初始化的数据结构的引用。例如,slice是一个三项描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化之前,slice都是nil的。对于slice,map和channel,make初始化内部数据结构,并准备好可用的值. 如make([]int, 10, 100)代表分配一个有100个int的数组,然后创建一个长度为10,容量为100的slice结构,并指向数组前10个元素上。而new([]int)返回一个指向新分配的,被置零的slice结构体的指针,即指向nilslice值的指针。

这些例子阐释了new和make之间的差别。

  1. var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful(很少使用)
  2. var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints
  3. // Unnecessarily complex:(不必要的组合)
  4. var p *[]int = new([]int)
  5. *p = make([]int, 100, 100)
  6. // Idiomatic:(符合语言习惯的)
  7. v := make([]int, 100)

记住make只用于map,slice和channel,并且不返回指针。要获得一个显式的指针,使用new进行分配,或者显式地使用一个变量的地址。


常量

字面常量

  1. -12
  2. 3.14159265358979323846 // 浮点类型的常量
  3. 3.2+12i // 复数类型的常量
  4. true // 布尔类型的常量
  5. "foo" // 字符串常量

Go语言的字面常量是无类型的,只要这个常量在相应类型的值域范围内,就可以作为该类型的常量,比如12,它可以赋值给int、uint、int32、int64、float32、float64、complex64、complex128等类型的变量。

常量定义

存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型. 通过const关键字,你可以给字面常量指定一个友好的名字:

  1. const Pi float64= 3.14159265358979323846
  2. const zero = 0.0 // 无类型浮点常量
  3. const (
  4. size int64= 1024
  5. eof = -1 // 无类型整型常量
  6. )
  7. const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值
  8. const a, b, c = 3, 4, "foo"
  9. // a = 3, b = 4, c = "foo", 无类型整型和字符串常量

Go的常量定义可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它与字面常量一样,是无类型常量。 一个没有指定类型的常量被使用时,会根据其使用环境而推断出它所需要具备的类型。换句话说,未定义类型的常量会在必要时刻根据上下文来获得相关类型。

  1. var n int
  2. f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int

常量定义的右值也可以是一个在编译期运算的常量表达式,比如

  1. const mask = 1 << 3

由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达式,比如试图以如下方式定义常量就会导致编译错误:

  1. const Home = os.GetEnv("HOME")

原因很简单,os.GetEnv()只有在运行期才能知道返回结果,在编译期并不能确定,所以无法作为常量定义的右值。

  1. const (
  2. a = "abc"
  3. b = len(a)
  4. c = unsafe.Sizeof(b)
  5. )
  6. println(b, c) // 3 8

  1. const x = "xxx" // 未使用的局部变量不会引发编译错误

在常量组中,如不提供类型和初始化值,那么视作与上一常量相同:

  1. const (
  2. s = "abc"
  3. x
  4. )
  5. println(x) // abc

枚举的常量都为同一类型时, 可以使用简单序列格式

  1. const (
  2. a = iota // a int32 = 0
  3. b // b int32 = 1
  4. c // c int32 = 2
  5. )

枚举序列中的未指定类型的常量会跟随序列前面最后一次出现类型定义的类型

  1. const (
  2. a byte = iota // a uint8 = 0
  3. b // b uint8 = 1
  4. c // c uint8 = 2
  5. d rune = iota // d int32 = 3
  6. e // e int32 = 4
  7. f // f int32 = 5
  8. )

跳过一个iota

  1. const (
  2. a byte = iota
  3. b
  4. _ // 跳过
  5. c
  6. )
  7. fmt.Println(a, b, c) // 0 1 3

在同一常量组中,可以提供多个iota,他们各自增长:

  1. const (
  2. a, b = iota, iota << 10 // 0, 0<<10
  3. c, d // 1, 1<<10
  4. )
  5. println(a, b, c, d) // 0 0 1 1024

如果iota自增被打断,须显示恢复

  1. const (
  2. a = iota // 0
  3. b // 1
  4. c = "c" // c
  5. d // c,与上一行相同
  6. e = iota // 4, 显示恢复,注意计数包含了 c, d 两行
  7. f // 5
  8. )

注意e的值


自定义函数不能为常量赋值

  1. const (
  2. a = iota
  3. //b = getNumber() 错误
  4. c = len([3]int{})
  5. )
  6. func getNumber() int {
  7. return 1
  8. }

因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len() 。


数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出

  1. const Ln2= 0.693147180559945309417232121458\
  2. 176568075500134360255254120680009
  3. const Log2E= 1/Ln2 // this is a precise reciprocal
  4. const Billion = 1e9 // float constant
  5. const hardEight = (1 << 100) >> 97

反斜杠 \ 可以在常量表达式中作为多行的连接符使用. 不过需要注意的是,当常量赋值给一个精度过小的数字型变量时,可能会因为无法正确表达常量所代表的数值而导致溢出,这会在编译期间就引发错误.


可以通过自定义类型来实现枚举类型限制:

  1. type Color int
  2. const (
  3. Black Color = iota
  4. Red
  5. Blue
  6. )
  7. func test(c Color) {}
  8. func main() {
  9. c := Black
  10. test(c)
  11. // x := 1
  12. // test(x) // Error: cannot use x (type int) as type Color in argument to test
  13. test(1) // 常量会被编译器自动转换
  14. }

预定义常量

Go语言预定义了这些常量:true、false和iota iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。 从以下的例子可以基本理解iota的用法:

  1. const( // iota被重设为0
  2. c0 = iota // c0 == 0
  3. c1 = iota // c1 == 1
  4. c2 = iota // c2 == 2
  5. )
  6. const(
  7. a = 1 << iota // a == 1 (iota在每个const开头被重设为0)
  8. b = 1 << iota // b == 2
  9. c = 1 << iota // c == 4
  10. )
  11. const(
  12. u = iota * 42 // u == 0
  13. v float64 = iota * 42 // v == 42.0
  14. w = iota * 42 // w == 84
  15. )
  16. constx = iota // x == 0 (因为iota又被重设为0了)
  17. consty = iota // y == 0 (同上)
  18. const (
  19. e, f, g = iota, iota, iota //e=0,f=0,g=0 iota在同一行值相同
  20. )

如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式。因此,上面的前两个const语句可简写为:

  1. const( // iota被重设为0
  2. c0 = iota // c0 == 0
  3. c1 // c1 == 1
  4. c2 // c2 == 2
  5. )
  6. const(
  7. a = 1 << iota // a == 1 (iota在每个const开头被重设为0)
  8. b // b == 2
  9. c // c == 4
  10. )

枚举

Go语言并不支持众多其他语言明确支持的enum关键字。 下面是一个常规的枚举表示法,其中定义了一系列整型常量:

  1. const(
  2. Sunday = iota
  3. Monday
  4. Tuesday
  5. Wednesday
  6. Thursday
  7. Friday
  8. Saturday
  9. numberOfDays // 这个常量没有导出
  10. )

同Go语言的其他符号(symbol)一样,以大写字母开头的常量在包外可见。 以上例子中numberOfDays为包内私有,其他符号则可被其他包访问

转载于:https://my.oschina.net/u/2004526/blog/849243

发表评论

表情:
评论列表 (有 0 条评论,485人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Go——变量常量

    1、变量 变量:使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址里面存放的内容可以改变。 1.1 显示的完整声明

    相关 Go语言基础】变量常量

    用Go语言编写的程序都是从基本组件构成,而构成这些基本组件的是标识符,本章中将介绍到Go语言中使用到变量,常量以及基本数据类型。 1. 标识符 标识符是函数、变量、常量

    相关 go第四讲:变量常量

    和其他语言一样,go也有自己的变量和常量,那么是真么定义的呢 变量用关键字 var,常量用关键字const 一:声明方法 1)指定类型,不赋值(默认是初始值,比如int

    相关 Go语言笔记--常量运算符

    1.常量的定义 1)常量的值再编译时就已经确定 2)常量的定义格式与变量基本相同 3)等号右侧必须是常量或者常量表达式 4)常量表达式中的函数必须是内置函数 例如