go官网教程A Tour of Go
http://tour.golang.org/#1
中文版:https://tour.go-zh.org/
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("Happy", math.Pi, "Day")
}
每个 Go 程序都是由包组成的。 必须要有package xx,不然会报错;
: expected 'package', found 'import'程序运行的入口从包的 main方法。
这个程序使用并导入了包 "fmt" 和 "math"。
按惯例,包名与导入路径的最后一个目录一致。
这个代码用圆括号组合了导入,这是“factored”(分解因子)式导入声明。同样可以编写多个导入语句,例如:
import "fmt"
import "math"
不过通常都会用 factored 格式来使代码工整。
在导入了一个包之后,就可以用其导出的名称来调用它。
在 Go 中,首字母大写的名称是被导出的。
Foo 和 FOO 都是被导出的名称。 名称 foo 是不会被导出的。
执行代码。然后将 math.pi 改为 math.Pi 再试着执行一下。
package main import "fmt" func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }
函数可以有零个或多个参数。
在这个例子中,add 有两个 int 类型的参数。
注意类型声明在变量名之后。(个人认为这个可以理解)
(参考这篇关于 Go 语法定义的文章了解类型以这种形式出现的原因。)
func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) }
函数可以返回任意数量的返回值。
这个函数返回了两个字符串。
func split(sum int) (x, y int) { x = sum * 4/9 y = sum - x return } func main() { fmt.Println(split(17)) }
Functions take parameters. In Go, functions can return multiple "result parameters", not just a single value. They can be named and act just like variables.
If the result parameters are named, a return statement without arguments returns the current values of the results.
函数有参数。在 Go 中,函数可以返回多个“结果参数”,而不仅仅是一个值。它们可以像变量那样被命名和使用。
如果命名了返回值的参数,一个没有参数的 return 语句,会将当前的值作为返回值返回。
Var:
var x, y, z int var c, python, java bool func main() { fmt.Println(x, y, z, c, python, java) }
var 语句声明了一个变量的列表;跟函数的参数列表一样,类型声明在最后面。
0 0 0 false false false
var x, y, z int = 1, 2, 3 var c, python, java = true, false, "no!" func main() { fmt.Println(x, y, z, c, python, java) }
变量声明时可以包含初始值,每个变量对应一个。
如果初始值是存在的,则可以省略类型声明;变量将从初始值中获得类型。
func main() { var x, y, z int = 1, 2, 3 c, python, java := true, false, "no!" fmt.Println(x, y, z, c, python, java) }
在一个函数里面,短赋值语句:= 可以用于替代 var 的隐式类型声明。
(:= 结构不能使用在函数外,函数外的每个语法块都必须以关键字开始。)
Constant:
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) }
Constants are declared like variables, but with the const keyword.
Constants can be character, string, boolean, or numeric values.
注意这个Println。
数值常量:
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)) }
数值常量是高精度的值。
一个未指定类型的常量由上下文来决定其类型。
也尝试一下输出 needInt(Big)吧 溢出
For:
func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
Go 只有一种循环结构,for 循环。
基本的 for 循环看起来跟 C 或者 Java 中做的一样,除了没有了 ( ) 之外(甚至强制不能使用它们,加了()会报错),而 { } 是必须的。
func main() { sum := 1 for ; sum < 1000; { sum += sum } fmt.Println(sum) }
As in C or Java, you can leave the pre and post statements empty.
跟 C 或者 Java 中一样,前置、后置条件可以为空。
func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }
基于这一点,你也可以省略分号: C 的 while 循环在 Go 中也是用 for 实现。
func main() { for ; ; { } }
如果省略了循环条件,它会一直循环下去(译者:死循环或无限循环)。
func main() { for { } }
为了避免累赘,分号也可以省略,这样一个无限循环可以被简洁地表达。
if:
import ( "fmt" "math" ) func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } else { fmt.Printf("%g >= %g\n", v, lim) } // can't use v here, though return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) }
在 if 的简短声明处定义的变量同样可以在对应的 else 块中使用。这点要特别注意。
Go 的基本类型有
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // alias for uint8 rune // alias for int32 // represents a Unicode code point float32 float64 complex64 complex128
Structs
A struct is a collection of fields.
(And a type declaration does what you'd expect.)
一个结构体(struct)就是一个成员变量的集合。
(而 type 定义跟其字面意思相符。)
type Vertex struct { X int Y int } func main() { fmt.Println(Vertex{1, 2}) }
Struct Fields(结构体成员变量)使用点号来访问。
func main(){ v :=Vertex{1,2} v.X=4 fmt.Println(v.X)
}
Pointers
Go has pointers, but no pointer arithmetic.
Struct fields can be accessed through a struct pointer. The indirection through the pointer is transparent.
Go 有指针,但是没有指针运算。
结构体成员变量可以通过结构体指针来访问。通过指针的间接访问也是透明的。
type Vertex struct{ X int Y int } func main(){ p := Vertex{1,2} q :=&p q.X=1e9 fmt.Println(p) }
输出:{1000000000 2}
说明可以直接输出结构体。
Struct Literals
Struct Literals(结构体文法)表示通过结构体成员变量的值作为列表来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 构造了指向结构体文法的指针。
Struct Literals
A struct literal denotes a newly allocated struct value by listing the values of its fields.
You can list just a subset of fields by using the Name: syntax. (And the order of named fields is irrelevant.)
The special prefix & constructs a pointer to a struct literal.
type Vertex struct { X, Y int } var ( p = Vertex{1, 2} // has type Vertex q = &Vertex{1, 2} // has type *Vertex r = Vertex{X: 1} // Y:0 is implicit s = Vertex{} // X:0 and Y:0 ) func main() { fmt.Println(p, q, r, s) }
{1 2} &{1 2} {1 0} {0 0}
new 函数
golang中new是一个函数.
表达式 new(T) 分配了一个零初始化的 T 值,并返回指向它的指针。(感觉这个语法有点奇怪,()里面为类型,怎么初始化呢?
var t *T = new(T)
t := new(T)
The new function
The expression new(T) allocates a zeroed T value and returns a pointer to it.
var t *T = new(T)
or
t := new(T)
type Vertex struct { X, Y int } func main() { v := new(Vertex) fmt.Println(v) v.X, v.Y = 11, 9 fmt.Println(v) }
Map
map 映射键到值。
map 必须用 make 来创建(不是 new);一个值为 nil 的 map 是空的,并且不能赋值。
(
映射
映射将键映射到值。
映射的零值为 nil 。nil 映射既没有键,也不能添加键。
make 函数会返回给定类型的映射,并将其初始化备用。
)
Maps
A map maps keys to values.
Maps must be created with make (not new) before use; the nil map is empty and cannot be assigned to.
type Vertex struct { Lat, Long float64 } var m map[string]Vertex func main() { m = make(map[string]Vertex) m["Bell Labs"] = Vertex{ 40.68433, 74.39967, } fmt.Println(m["Bell Labs"]) }
(
如果没有使用make会报错:
panic: assignment to entry in nil map )map literals(map 的文法)跟struct literals(结构体文法)相似,但是键是必须的。
Maps
Map literals are like struct literals, but the keys are required.
映射的文法
映射的文法与结构体相似,不过必须有键名。
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[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
Map
如果顶层类型只有类型名的话,可以在文法的元素中省略键名。
Maps
If the top-level type is just a type name, you can omit it from the elements of the literal.
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) }
Insert or update an element in map m:
m[key] = elem
Retrieve an element:
elem = m[key]
Delete an element:
delete(m, key)
Test that a key is present with a two-value assignment:
elem, ok = m[key]
If key is in m, ok is true. If not, ok is false and elem is the zero value for the map's element type.
Similarly, when reading from a map if the key is not present the result is the zero value for the map's element type.
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) }
(
我查询的:
定义 Map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
Slice
slice 指向数组的值,并且同时包含了长度信息。
[]T 是一个元素类型为 T 的 slice。
Slices
A slice points to an array of values and also includes a length.
[]T is a slice with elements of type T. (java是T【】)
func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) for i := 0; i < len(p); i++ { fmt.Printf("p[%d] == %d\n", i, p[i]) } }
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
表达式
s[lo:hi]
表示从 lo 到 hi-1 的 slice 元素,含有两端。 因此
s[lo:lo]
是空的,而
s[lo:lo+1]
有一个元素。
func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) fmt.Println("p[1:4] ==", p[1:4]) // missing low index implies 0 fmt.Println("p[:3] ==", p[:3]) // missing high index implies len(s) fmt.Println("p[4:] ==", p[4:]) }
跟python一样。
slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:
a := make([]int, 5) // len(a)=5
slice 有长度和容量。slice 的容量是底层数组可以增长的最大长度。
为了指定容量,可传递第三个参数到 make:
b := make([]int, 0, 5)
// len(b)=0, cap(b)=5
slice 可以通过“重新切片”来扩容(增加容量):
b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
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) e :=make([]int,10) printSlice("e",e); }
打印slice通过printSlice
function
函数也是值。
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(3, 4))
}
函数
函数也是值。它们可以像其它值一样传递。
函数值可以用作函数的参数或返回值。
函数的闭包
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。
Function values
Functions are values too. They can be passed around just like other values.
Function values may be used as function arguments and return values.
Function closures
Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables.
For example, the adder function returns a closure. Each closure is bound to its own sum variable.
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), ) } }
并且函数是完全闭包的。
函数 adder 返回一个闭包。每个闭包被绑定到自己的 sum 变量上。
Functions
And functions are full closures.
The adder function returns a closure. Each closure is bound to its own sumvariable.
Range
可以将值赋值给 _ 来忽略键和值。
如果只需要索引值,去掉“, value”的部分即可。
Range
You can skip the index or value by assigning to _.
If you only want the index, drop the “, value” entirely.
pow := make([]int, 10) for i := range pow { pow[i] = 1<<uint(i) } for _, value := range pow { fmt.Printf("%d\n", value)
Switch
你可能已经猜到 switch 的形式了。
case 语句匹配后会自动终止,除非用 fallthrough 语句作为结尾。
import ( "fmt" "runtime" ) func main() { fmt.Print("Go runs on ") switch os := runtime.GOOS; os { case "darwin": fmt.Println("OS X.") case "linux": fmt.Println("Linux.") default: // freebsd, openbsd, // plan9, windows... fmt.Printf("%s.", os) } }
没有条件的 switch 与 switch true 一样。
这一构造使得可以用更清晰的形式来编写if-then-else。
import ( "fmt" "time" ) func main() { t := time.Now() switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } }
Methods方法
Go 没有类。然而,仍然可以在结构体类型上定义方法。
方法接收者出现在 func 关键字和方法名之间的参数中。
import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := &Vertex{3, 4} fmt.Println(v.Abs()) }
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
事实上,可以对包中的任意类型定义任意方法,而不仅仅是结构体。
不能对来自其他包的类型或基础类型定义方法。
(译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。)
type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }
接收者为指针的方法
方法可以与命名类型或命名类型的指针关联。
刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在MyFloat 值类型上。
有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。
当 v 是 Vertex 的时候 Scale 方法没有任何作用。Scale 修改 v。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。
Abs 的工作方式是一样的。只不过,仅仅读取 v。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。
type Vertex struct { X, Y float64 } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := &Vertex{3, 4} v.Scale(5) fmt.Println(v, v.Abs()) }
&{15 20} 25
去掉指针值没有变化。
方法与指针重定向
比较前两个程序,你大概会注意到带指针参数的函数必须接受一个指针:
var v Vertex ScaleFunc(v, 5) // 编译错误! ScaleFunc(&v, 5) // OK
而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex v.Scale(5) // OK p := &v p.Scale(10) // OK
对于语句 v.Scale(5),即便 v 是个值而非指针,带指针接收者的方法也能被直接调用。 也就是说,由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)。
选择值或指针作为接收者
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
在本例中,Scale 和 Abs 接收者的类型为 *Vertex,即便 Abs 并不需要修改其接收者。
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。(我们会在接下来几页中明白为什么。)
In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both. (We'll see why over the next few pages.)
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := &Vertex{3, 4} fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs()) v.Scale(5) fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs()) }
Interface接口
接口类型是由一组方法定义的。
接口类型的值可以容纳实现这些方法的任何值。
An interface type is defined by a set of methods.
A value of interface type can hold any value that implements those methods.
type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser a = &v // a *Vertex implements Abser a = v // a Vertex, does NOT // implement Abser fmt.Println(a.Abs()) } type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
// 下面一行,v 是一个 Vertex(而不是 *Vertex)
// 所以没有实现 Abser。
a = v
接口与隐式实现
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
Interfaces are implemented implicitly
A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword.
Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.
package main import "fmt" type I interface { M() } type T struct { S string } // 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。 func (t T) M() { fmt.Println(t.S) } func main() { var i I = T{"hello"} i.M() }
一种类型通过实现那些方法来实现接口。
没有显式声明的必要。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
也鼓励明确的接口定义,因为这样就无需找到每一个实现,并对其加上新的接口名称。
Package io 定义了 Reader 和 Writer;但不是一定要这么做。
import ( "fmt" "os" ) type Reader interface { Read(b []byte) (n int, err error) } type Writer interface { Write(b []byte) (n int, err error) } type ReadWriter interface { Reader Writer } func main() { var w Writer // os.Stdout implements Writer w = os.Stdout fmt.Fprintf(w, "hello, writer\n") }
接口值
接口也是值。它们可以像其它值一样传递。
接口值可以用作函数的参数或返回值。
在内部,接口值可以看做包含值和具体类型的元组:
(value, type)
接口值保存了一个具体底层类型的具体值。
接口值调用方法时会执行其底层类型的同名方法。
Interface values
Under the hood, interface values can be thought of as a tuple of a value and a concrete type:
(value, type)
An interface value holds a value of a specific underlying concrete type.
Calling a method on an interface value executes the method of the same name on its underlying type.
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
defer 栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
更多关于 defer 语句的信息,请阅读此博文。
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
Go语言在代码规范中定义未使用的变量会报“declared and not used”错误 。是报错,而不是警告
指针
Go 拥有指针。指针保存了值的内存地址。
类型 *T 是指向 T 类型值的指针。其零值为 nil。
var p *int
& 操作符会生成一个指向其操作数的指针。
i := 42 p = &i
* 操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 i *p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“重定向”。
与 C 不同,Go 没有指针运算。
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
结构体
一个结构体(struct)就是一组字段(field)。
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
结构体指针
结构体字段可以通过结构体指针来访问。
如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
结构体文法 struct literal
结构体文法通过直接列出字段的值来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
func main() {
fmt.Println(v1, p, v2, v3)
}
数组
类型 [n]T 表示拥有 n 个 T 类型的值的数组。
表达式
var a [10]int
会将变量 a 声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T 表示一个元素类型为 T 的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
a[1:4]
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
切片就像数组的引用
切片并不存储任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
与它共享底层数组的切片都会观测到这些修改。
切片文法
切片文法类似于没有长度的数组文法。
这是一个数组文法:
[3]bool{true, true, false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
[]bool{true, true, false}
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
切片的长度与容量
切片拥有 长度 和 容量。
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
你可以通过重新切片来扩展一个切片,给它提供足够的容量。试着修改示例程序中的切片操作,向外扩展它的容量,看看会发生什么。
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
nil 切片
切片的零值是 nil。
nil 切片的长度和容量为 0 且没有底层数组。
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
用 make 创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
切片的切片
切片可包含任何类型,甚至包括其它的切片。
import ( "fmt" "strings" ) func main() { // 创建一个井字板(经典游戏) board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, } // 两个玩家轮流打上 X 和 O board[0][0] = "X" board[2][2] = "O" board[1][2] = "X" board[1][0] = "O" board[0][2] = "X" for i := 0; i < len(board); i++ { fmt.Printf("%s\n", strings.Join(board[i], " ")) } }
向切片追加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。
func append(s []T, vs ...T) []T
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
append 的结果是一个包含原切片所有元素加上新添加元素的切片。
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
(要了解关于切片的更多内容,请阅读文章 Go 切片:用法和本质。)
func main() {
var s []int
printSlice(s)
// 添加一个空切片
s = append(s, 0)
printSlice(s)
// 这个切片会按需增长
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s)
}
Range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
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)
}
}