基本类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "math/cmplx" ) var ( ToBe bool = false MaxInt uint64 = 1 <<64 - 1 z complex128 = cmplx.Sqrt(-5 + 12i ) ) func main () { const f = "%T(%v)\n" fmt.Printf(f, ToBe, ToBe) fmt.Printf(f, MaxInt, MaxInt) fmt.Printf(f, z, z) }
Go 的基本类型有Basic types:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // uint8 的别名 rune // int32 的别名 // 代表一个Unicode码 float32 float64 complex64 complex128
这个例子演示了具有不同类型的变量。同时与导入语句一样,变量的定义“打包”在一个语法块中。
零值
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var i int var f float64 var b bool var s string fmt.Printf("%v %v %v %q\n" , i, f, b, s) }
变量在定义时没有明确的初始化时会赋值为 零值 。
零值是:
数值类型为 0 ;
布尔类型为 false ;
字符串为 “” (空字符串)。
类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "math" ) func main () { var x, y int = 3 , 4 var f float64 = math.Sqrt(float64 (x*x + y*y)) var z int = int (f) fmt.Println(x, y, z) }
表达式 T(v) 将值 v 转换为类型 T 。
一些关于数值的转换:
1 2 3 var i int = 42 var f float64 = float64 (i)var u uint = uint (f)
或者,更加简单的形式:
1 2 3 i := 42 f := float64 (i) u := uint (f)
与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换 。试着移除例子中 float64 或 int 的转换看看会发生什么。
类型推导
1 2 3 4 5 6 7 8 package mainimport "fmt" func main () { v := 42 fmt.Printf("v is of type %T\n" , v) }
在定义一个变量但不指定其类型时(使用没有类型的 var 或 := 语句), 变量的类型由右值推导得出。
当右值定义了类型时,新变量的类型与其相同:
但是当右边包含了未指名类型的数字常量时,新的变量就可能是 int 、 float64 或 complex128 。 这取决于常量的精度:
1 2 3 i := 42 f := 3.142 g := 0.867 + 0.5i
尝试修改演示代码中 v 的初始值,并观察这是如何影响其类型的。
常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" const pi = 3.14 func main () { const world = "世界" fmt.println ("hello" , world) fmt.println ("happy" , pi, "day" ) const truth = true fmt.println ("go rules?" , truth) }
常量的定义与变量类似,只不过使用 const 关键字。
常量可以是字符、字符串、布尔或数字类型的值。
常量不能使用 :=
语法定义。
数值常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" const ( Big = 1 << 100 Small = Big >> 99 ) func needInt (x int ) int { return x*10 + 1 }func needFloat (x float64 ) float64 { return x * 0.1 } func main () { fmt.Println(needInt(Small)) fmt.Println(needFloat(Small)) fmt.Println(needFloat(Big)) }
数值常量是高精度的值。
一个未指定类型的常量由上下文来决定其类型。
指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { i, j := 42 , 2701 p := &i fmt.Println(*p) *p = 21 fmt.Println(i) p = &j *p = *p / 37 fmt.Println(j) }
Go 具有指针。 指针保存了变量的内存地址。
类型 *T 是指向类型 T 的值的指针。其零值是 nil 。
& 符号会生成一个指向其作用对象的指针。
*
符号表示指针指向的底层的值。
1 2 fmt.Println(*p) *p = 21
这也就是通常所说的“间接引用”或“非直接引用”。
与 C 不同,Go 没有指针运算 。
结构体
定义
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" type Vertex struct { X int Y int } func main () { fmt.Println(Vertex{1 , 2 }) }
一个结构体( struct )就是一个字段的集合。
(而 type
的含义跟其字面意思相符。)
结构体字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" type Vertex struct { X int Y int } func main () { v := Vertex{1 , 2 } v.X = 4 fmt.Println(v.X) }
结构体字段使用点号来访问。
结构体指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" type Vertex struct { X int Y int } func main () { v := Vertex{1 , 2 } p := &v p.X = 1e9 fmt.Println(v) }
结构体文法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" type Vertex struct { X, Y int } var ( v1 = Vertex{1 , 2 } v2 = Vertex{X: 1 } v3 = Vertex{} p = &Vertex{1 , 2 } ) func main () { fmt.Println(v1, p, v2, v3) }
结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用 Name:
语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 &
返回一个指向结构体的指针。
数组
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var a [2 ]string a[0 ] = "Hello" a[1 ] = "World" fmt.Println(a[0 ], a[1 ]) fmt.Println(a) }
类型 [n]T
是一个有 n
个类型为 T
的值的数组。
表达式
定义变量 a 是一个有十个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小 。这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。
slice
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" func main () { s := []int {2 , 3 , 5 , 7 , 11 , 13 } fmt.Println("s ==" , s) for i := 0 ; i < len (s); i++ { fmt.Printf("s[%d] == %d\n" , i, s[i]) } }
一个 slice 会指向一个序列的值,并且包含了长度信息。
[]T
是一个元素类型为 T
的 slice。
对 slice 切片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { s := []int {2 , 3 , 5 , 7 , 11 , 13 } fmt.Println("s ==" , s) fmt.Println("s[1:4] ==" , s[1 :4 ]) fmt.Println("s[:3] ==" , s[:3 ]) fmt.Println("s[4:] ==" , s[4 :]) }
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
表达式
表示从 lo 到 hi-1 的 slice 元素,含两端。因此
是空的,而
有一个元素。
构造 slice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { a := make ([]int , 5 ) printSlice("a" , a) b := make ([]int , 0 , 5 ) printSlice("b" , b) c := b[:2 ] printSlice("c" , c) d := c[2 :5 ] printSlice("d" , d) } func printSlice (s string , x []int ) { fmt.Printf("%s len=%d cap=%d %v\n" , s, len (x), cap (x), x) }
slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:
为了指定容量,可传递第三个参数到 make:
1 2 3 b := make ([]int , 0 , 5 ) b = b[:cap (b)] b = b[1 :]
nil slice
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var z []int fmt.Println(z, len (z), cap (z)) if z == nil { fmt.Println("nil!" ) } }
slice 的零值是 nil
。
一个 nil
的 slice 的长度和容量是 0。
向 slice 添加元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func main () { var a []int printSlice("a" , a) a = append (a, 0 ) printSlice("a" , a) a = append (a, 1 ) printSlice("a" , a) a = append (a, 2 , 3 , 4 ) printSlice("a" , a) } func printSlice (s string , x []int ) { fmt.Printf("%s len=%d cap=%d %v\n" , s, len (x), cap (x), x) }
向 slice 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append
。 内建函数的文档对 append 有详细介绍。
1 func append (s []T, vs ...T) []T
append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice。
append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。
range
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" var pow = []int {1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 }func main () { for i, v := range pow { fmt.Printf("2**%d = %d\n" , i, v) } }
for
循环的 range
格式可以对 slice 或者 map 进行迭代循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { pow := make ([]int , 10 ) for i := range pow { pow[i] = 1 << uint (i) } for _, value := range pow { fmt.Printf("%d\n" , value) } }
可以通过赋值给 _
来忽略序号和值。
如果只需要索引值,去掉 , value
的部分即可。
map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" type Vertex struct { Lat, Long float64 } var m map [string ]Vertexfunc main () { m = make (map [string ]Vertex) m["Bell Labs" ] = Vertex{ 40.68433 , -74.39967 , } fmt.Println(m["Bell Labs" ]) }
map 映射键到值。
map 在使用之前**必须用 make 而不是 new **来创建;值为 nil 的 map 是空的,并且不能赋值。
map 的文法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" type Vertex struct { Lat, Long float64 } var m = map [string ]Vertex{ "Bell Labs" : Vertex{ 40.68433 , -74.39967 , }, "Google" : Vertex{ 37.42202 , -122.08408 , }, } func main () { fmt.Println(m) }
map 的文法跟结构体文法相似,不过必须有键名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" type Vertex struct { Lat, Long float64 } var m = map [string ]Vertex{ "Bell Labs" : {40.68433 , -74.39967 }, "Google" : {37.42202 , -122.08408 }, } func main () { fmt.Println(m) }
如果顶级的类型只有类型名的话,可以在文法的元素中省略键名。
修改 map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { m := make (map [string ]int ) m["Answer" ] = 42 fmt.Println("The value:" , m["Answer" ]) m["Answer" ] = 48 fmt.Println("The value:" , m["Answer" ]) delete (m, "Answer" ) fmt.Println("The value:" , m["Answer" ]) v, ok := m["Answer" ] fmt.Println("The value:" , v, "Present?" , ok) }
插入或修改元素
访问元素
删除元素
检测某个键是否存在
通过双赋值检测某个键存在:
如果 key 在 m 中, ok 为 true。否则, ok 为 false,并且 elem 是 map 的元素类型的零值。
同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
函数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "math" ) func main () { hypot := func (x, y float64 ) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(3 , 4 )) }
函数也是值。
函数的闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" func adder () func (int ) int { sum := 0 return func (x int ) int { sum += x return sum } } func main () { pos, neg := adder(), adder() for i := 0 ; i < 10 ; i++ { fmt.Println( pos(i), neg(-2 *i), ) } }
Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。
深入阅读
Go 切片:用法和本质