常量
介绍
- Go语言中的常量使用关键字 const 定义,用于存储不会改变的数据,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。
- 声明时可以省略类型,因为编译器可以根据值来推断其类型。
- 常量是在编译时被创建的,即使定义在函数内部也是如此。编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。
- 常量的运算在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。
- 常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof。
声明
单个声明
```go const name [type] = value
const e float64 = 2.7182818 const pi = 3.14159 // 相当于 math.Pi 的近似值
<a name="fUCOf"></a>
##### 批量声明
1. 如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略。
1. 如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。
```go
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
无类型常量
介绍
- Go语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如 int 或 float64,或者是类似 time.Duration 这样的基础类型,但是许多常量并没有一个明确的基础类型。
- 编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算,可以认为至少有 256bit 的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
- 通过延迟明确常量的具体类型,不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。
对于常量面值,不同的写法可能会对应不同的类型。例如 0、0.0、0i 和 \u0000 虽然有着相同的常量值,但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样,true 和 false 也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。
例子
math.Pi 无类型的浮点数常量,可以直接用于任意需要浮点数或复数的地方:
var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi
如果 math.Pi 被确定为特定类型,比如 float64,那么结果精度可能会不一样,同时对于需要 float32 或 complex128 类型值的地方则需要一个明确的强制类型转换
const Pi64 float64 = math.Pi
var x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)
iota 生产器
介绍
iota 生产器类此c#中的 enum 枚举类型。
- 在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。 ```go // 周日将对应 0,周一为 1,以此类推。 const ( Sunday int = iota Monday Tuesday Wednesday Thursday Friday Saturday )
<a name="9ZGCL"></a>
##### 高级
iota 不仅可以生成每次增加 1 的枚举值。还可以利用 iota 来做一些强大的枚举常量值生成器。
<a name="V6ZPq"></a>
###### 示例1:
```go
// a 为1,以此类推。
const (
a int = iota + 1
b
c
)
fmt.Println(a,b,c) // 1, 2, 3
示例2:
const (
// 移位操作,每次将上一次的值左移一位(二进制位),以得出每一位的常量值
FlagNone = 1 << iota
FlagRed
FlagGreen
FlagBlue
)
fmt.Printf("%d %d %d\n", FlagRed, FlagGreen, FlagBlue)
fmt.Printf("%b %b %b\n", FlagRed, FlagGreen, FlagBlue)
// log
2 4 8
10 100 1000
将枚举值转换为字符串
- 枚举在 C# 中是一个独立的类型,可以通过枚举值获取该值对应的字符串。例如,C# 中 Week 枚举值 Monday 为 1,那么可以通过 Week.Monday.ToString() 函数获得 Monday 字符串。
- Go语言中也可以实现这一功能,只不过略有麻烦。
// 声明芯片类型
type ChipType int
const (
None ChipType = iota
CPU // 中央处理器
GPU // 图形处理器
)
func (c ChipType) String() string {
switch c {
case None:
return "None"
case CPU:
return "CPU"
case GPU:
return "GPU"
}
return "N/A"
}
func main() {
// 输出CPU的值并以整型格式显示
fmt.Printf("%s %d", CPU, CPU) // CPU 1
}
变量
介绍
- Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。
- 变量的命名规则遵循小骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 。
- 当一个变量被声明之后,系统自动赋予它该类型的零值进行初始化:
- 整型和浮点型变量的默认值为 0 和 0.0。
- 字符串变量的默认值为””。
- 布尔型变量默认为 false。
- 指针、chan(通道)、map(集合)、slice(切片)、func(函数)、 变量的默认为 nil。
声明
1.标准格式
- 以关键字 var 开头,后置变量类型,行尾无须分号。
- var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。 ```go var 变量名 变量类型
var a int
<a name="87lyw"></a>
#### 2.批量格式
使用关键字 var 和括号,可以将一组变量定义放在一起。
```go
var (
a int
b string
c []float32
d func() bool
e struct {
x int
}
)
3.简短格式
- 因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。
- 需要注意的是,简短模式(short variable declaration)有以下限制:
- 定义变量,同时显式初始化。
- 不能提供数据类型。
- 只能用在函数内部。 ```go 名字[,…] := 表达式[,…]
a := 1 b,c := 2,”a”
<a name="Kw1jl"></a>
##### 注意1
使用短变量时该变量必须没有被声明过,若声明过则报错:no new variables on left side of :=
```go
// 声明 hp 变量
var hp int
// 再次声明并赋值
hp := 10 // panic
注意2
- 在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错。
- 重复的变量将会重新赋值给第一次声明的变量。
conn, err := net.Dial("tcp", "127.0.0.1:8080")
conn2, err := net.Dial("tcp", "127.0.0.1:8080")
初始化
1. 标准格式
var 变量名 类型 = 表达式
var name string = "张三"
2. 推导类型
当我们写出初始化值时,编译器已经可以推导出数据类型,此时我们可以省略类型,因为这已经是冗余代码。
var name = "张三"
var age = 24 - 4
height := 180
作用域
介绍
根据变量定义位置的不同,可以分为以下二个类型:
- 局部变量:
- 函数内定义的变量
- 函数的形式参数
- 全局变量:函数外定义的变量
局部变量
函数体内的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。
函数内定义
生命周期:从创建这个变量的声明语句开始,到这个变量不再被引用为止。
func main() {
//声明局部变量 a 和 b 并赋值
var a int = 3
var b int = 4
//声明局部变量 c 并计算 a 和 b 的和
c := a + b
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
形式参数
生命周期:函数被调用的时候创建,函数调用结束后被销毁。 ```go func main() { //局部变量 a 和 b var a int = 3 var b int = 4 fmt.Printf(“main() 函数中 a = %d\n”, a) fmt.Printf(“main() 函数中 b = %d\n”, b) c := sum(a, b) fmt.Printf(“main() 函数中 c = %d\n”, c) } func sum(a, b int) int { fmt.Printf(“sum() 函数中 a = %d\n”, a) fmt.Printf(“sum() 函数中 b = %d\n”, b) num := a + b return num }
// log main() 函数中 a = 3 main() 函数中 b = 4 sum() 函数中 a = 3 sum() 函数中 b = 4 main() 函数中 c = 7
<a name="PqXM0"></a>
#### 全局变量
1. 全局变量声明必须以 var 关键字开头。
1. 生命周期:和整个程序的运行周期是一致。
1. 在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用。
1. 如果想要在外部包中使用全局变量时首字母必须大写。在其他包使用该变量时,需要使用“import”关键字引入全局变量所在的包之后才能使用这个全局变量。
```go
//声明全局变量
var c int
func main() {
//声明局部变量
var a, b int
//初始化参数
a = 3
b = 4
c = a + b
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
注意
Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。
//声明全局变量
var a float32 = 3.14
func main() {
//声明局部变量
var a int = 3
fmt.Printf("a = %d\n", a) // a = 3
}
变量交换
- 编程最简单的算法之一,莫过于变量交换。
- 交换变量的常见算法需要一个中间变量进行变量的临时保存。而go可以直接使用多重赋值搞定。
常规交换
var a int = 100
var b int = 200
var t int
t = a
a = b
b = t
fmt.Println(a, b)
Go语言交换
- 多重赋值时,变量按从左到右的顺序赋值。
- 多重赋值在Go语言会大量地使用,比如冒泡排序的交换变量。 ```go a,b := 100,200 b, a = a, b
fmt.Println(a, b) // 200 100
---
<a name="ZKsZ3"></a>
## nil 未知
1. nil 是Go语言中一个预定义好的标识符,有过其他编程语言开发经验的开发者也许会把 nil 看作其他语言中的 null(NULL),其实这并不是完全正确的,因为Go语言中的 nil 和其他语言中的 null 有很多不同点。
1. Go 语言中 nil 代表未定义(未知)的意思。
<br />
<a name="7P2Cm"></a>
##### 不能比较
从语法来讲 未知 不能与 未知 判断相等。<br />**panic:**invalid operation: nil == nil (operator == not defined on nil)
```go
func main() {
fmt.Println(nil==nil)
}
不是关键字或保留字
nil 并不是Go语言的关键字或者保留字,也就是说我们可以定义一个名称为 nil 的变量,虽然上可以通过编译,但是并不提倡这么做。
var nil = errors.New("my god")
不同类型 nil 的指针是一样的
func main() {
var arr []int
var num *int
fmt.Printf("%p\n", arr)
fmt.Printf("%p", num)
}
// log
0x0
0x0
不同类型的 nil 值占用内存不一样
具体的大小取决于编译器和架构,上面打印的结果是在 64 位架构和标准编译器下完成的,对应 32 位的架构的,打印的大小将减半。
func main() {
var p *struct{}
fmt.Println( unsafe.Sizeof( p ) ) // 8
var s []int
fmt.Println( unsafe.Sizeof( s ) ) // 24
var m map[int]bool
fmt.Println( unsafe.Sizeof( m ) ) // 8
var c chan string
fmt.Println( unsafe.Sizeof( c ) ) // 8
var f func()
fmt.Println( unsafe.Sizeof( f ) ) // 8
var i interface{}
fmt.Println( unsafe.Sizeof( i ) ) // 16
}
// log
8
24
8
8
8
16