本教程 链接 : https://tour.go-zh.org
流程控制
for
- 只有这一种循环结构
- 初始化语句:在第一次迭代前执行
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代的结尾执行
例子
sum := 0
for i:=0; i<=100; i++{
sum += i
}
只保留条件表达式
sum := 0
i := 1
for ; i <= 100; {
sum += i
i++
}
无限循环
for {
}
if
- 可以在条件表达前执行一个简短的语句
- 该语句声明的变量作用域仅在 if 之内
例子
func square(v int) int {
return v * v
}
func main(){
x := 10
if v := square(x); v < 100{
fmt.Println("less than 100")
}else{
fmt.Println("great than or equal to 100")
}
}
switch
- Go 类似if 也可以运行一个条件表达式
- Go 自动提供了在这些语言中每个 case 后面所需的 break 语句
- Go 的switch 的 case 无需为常量,且取值不必为整数。
- case 语句从上到下顺次执行,直到匹配成功时停止
- 没有条件的 switch 同 switch true 一样
例子1
switch os := runtime.GOOS;os {
case "linux":
fmt.Println("linux os")
case "windows":
fmt.Println("windows os")
default:
fmt.Println("other os")
}
例子2
v := rand.Int()
switch v%2 {
case 0+0:
fmt.Println("even number")
case 0+1:
fmt.Println("odd number")
default:
fmt.Println("error number")
}
例子3
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")
}
defer
- 推迟的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
例子
for i:=0; i < 10; i++ {
defer fmt.Println(i)
}
更多类型
结构体
- 一个结构体 就是一个字段的集合
- 结构体字段使用点号来访问
- 结构体字段可以通过结构体指针来访问 也可以直接通过指针+下划线访问
例子1
type Vertex struct{
X int
Y int
}
v := Vertex{1,2}
v.X = 4
fmt.Println(v.X)
p := &v
fmt.Println((*p).X) //指针方式访问1
fmt.Println(p.X) //指针方式访问2
数组
- 类型 [n]T 表示有n个T类型的值的数组
- 数组的长度是类型的一部分 因此不能改变大小
例子
var a[2]string
a[0] = "hello"
a[1] = "world"
fmt.Println(a,a[0],a[1])
切片
-
由于数组的大小是固定的 而切片则为数组元素提供动态大小的 灵活的视角
-
类型 []T 表示一个元素类型为T的切片
-
切片提供下标来界定 前开后闭
-
切片就像数组的引用 切片不存储任何数据 它只是描述了底层数组中的一段 更改切片的元素会修改其底层数组对应的元素 与它共享底层数组的切片都会观测到这些修改
-
nil 切片的长度和容量为 0 且没有底层数组。
-
切片可以用内建的函数make来创建 make函数会分配一个元素为零值的数组 并返回一个引用他的切片
-
向切片追加新的元素 使用append函数
-
for循环的range形式 可遍历切片,每次迭代都会返回两个值 第一个值为当前元素的下标 第二个值为该下标所对应元素的一份副本
例子1
names := [4]string{"AAA","BBB","CCC","DDD"}
a := names[0:2]
a[0] = "XXX"
fmt.Println(names) //修改切片a 则 原数组里面的值也
例子2
a := [10]int{1,2,3,4,5,6}
s := a[1:5]
fmt.Println(len(s),cap(s)) //4 9 这里的切片容量是9 因为切片的首个字符是从1 开始的
例子3
a := make([]int,5)
fmt.Println(a) //[0 0 0 0 0]
fmt.Println(len(a),cap(a)) //5 5
b := make([]int,3,5)
fmt.Println(b) //[]
fmt.Println(len(b),cap(b)) //0 5
例子4
var s[]int
fmt.Println(s) //[]
s = append(s ,1)
fmt.Println(s) //[1]
例子5
value := []string{"a","b","c"}
for key,value := range value{
fmt.Println(key,value)
}
映射
- 映射的零值为nil nil映射既没有键 也不能添加键
- make 函数会返回给定映射类型的映射 并将其初始化备用
- delete(映射,key) 删除元素
- 检测键是否存在 elem, ok = m[key]
- 相同的key 被重新赋值 则后面的赋值会覆盖前面的值
- elem, ok = m[key] 检测key是否存在 存在 则ok为true;否则为false
例子1
type Vertex struct {
Lat,Long float64
}
var m map[string]Vertex
func main(){
fmt.Println(m)
//这里没有经过初始化的映射 不能够被直接赋值 否则会报错 panic: assignment to entry in nil map
//m["bell"] = Vertex{
// 10.0,
// 20.0,
//}
m = make(map[string]Vertex)
m["bell"] = Vertex{
10.0,
20.0,
}
fmt.Println(m["bell"]) //{10 20}
}
例子2
var n = map[string]Vertex{
"Bell Labs":Vertex{
10.0,
11.0,
},
"Google":Vertex{
12.0,
13.0,
},
}
//这里初始化的时候 直接赋值
//若顶级类型只是一个类型名 则可以在文法的元素中省略它
var m = map[string]Vertex{
"Bell Labs":{
10.0,
11.0,
},
"Google":{
12.0,
13.0,
},
}
例子3
m := make(map[string]int)
m["answer"] = 100
fmt.Println(m) //map[answer:100]
m["answer"] = 200
m["value"] = 300
fmt.Println(m) //map[answer:200 value:300]
delete(m,"answer")
fmt.Println(m) //map[value:300]
delete(m,"answer2") //删除不存在的key 这里并不会报错
fmt.Println(m["test"]) //不存在的key 输出0
ele,ok := m["answer"]
if ok == true{
fmt.Println(ele) //200
}else{
fmt.Println("no value")
}
函数
- 函数也是值 可以像其它值一样传递
- 函数值也可以用作函数的参数或返回值
例子
hypot := func(x int)int{
return x * x
}
fmt.Println(hypot(10)) //100
fmt.Println(compute(hypot)) //9
闭包
- Go函数 可以是一个闭包 闭包是一个函数值 它引用了其函数体之外的变量 该函数可以访问并赋予其引用的变量的值
- 下面例子中 adder返回一个闭包 每个闭包都被绑定在其各自的sum变量上
例子
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))
fmt.Println(neg(-2*i))
}
}
方法和接口
方法即函数
- 方法只是个带接受者参数的函数
- 也可以为非结构体类型声明方法
例子1
type Vertex struct{
X,Y float64
}
//声明一个函数 需要传递Vertex结构体
//func Abs(v Vertex) float64{
// return math.Sqrt(v.X*v.X + v.Y*v.Y)
//}
//声明为Vertex的一个方法
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())
}
例子2
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())
}
指针接收者
- 可以为指针接受者声明方法 这意味着 对于某类型T 接受者可以用*T的文法
- 指针接收者的方法可以修改接收者指向的值
- 带指针参数的函数必须接受一个指针:
- 而以指针为接收者的方法被调用时,接收者既能为值又能为指针 为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)
- 选择指针接收者的原因 1.可以修改其接收者指向的值 2.避免在每次调用方法时 复制该值
例子1
type Vertex struct{
X,Y float64
}
//声明为Vertex的一个方法
func (v Vertex) Abs() float64{
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64){
v.X = v.X * f
v.Y = v.Y * f
}
func main(){
v := Vertex{3,4}
v.Scale(10)
fmt.Println(v.Abs())
}
例子2
type Vertex struct{
X,Y float64
}
//声明为Vertex的一个方法
func (v Vertex) Abs() float64{
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex,f float64){
v.X = v.X * f
v.Y = v.Y * f
}
func main(){
v := Vertex{3,4}
//Scale(v,10) //编译报错
Scale(&v,10) //必须接收一个指针
//v.Scale(10)
//
fmt.Println(v.Abs())
}
例子3
type Vertex struct{
X,Y float64
}
//声明为Vertex的一个方法
func (v Vertex) Abs() float64{
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex,f float64){
v.X = v.X * f
v.Y = v.Y * f
}
func main(){
v := Vertex{3,4}
//Scale(v,10) //编译报错
Scale(&v,10)
//v.Scale(10)
//
//fmt.Println(v.Abs())
p := &Vertex{3,4}
fmt.Println(p.Abs()) //可以直接通过指针调用
fmt.Println((*p).Abs())
}
接口
- 接口类型是由一组方法签名定义的集合
- 接口类型的值 可以保存任何实现了这些方法的值
例子1
type Abser interface{
Abs() float64
}
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)
}
func main(){
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3,4}
a = f // Myfloat 实现了 Abser
fmt.Println(a.Abs())
a = &v //*Vertex 实现了 Abser
fmt.Println(a.Abs())
//a = v //报错 v是一个Vertex 没有实现 Abser
}
nil接口
- nil 接口既不保存值 也不保存具体的类型
- 为nil 接口 调用方法会产生运行时错误 因为接口的元祖内 并未包含能够指明该调用哪个具体方法的类型
- 即便接口内的具体值为nil 方法任然会被nil接收者调用
例子
type I interface{
M()
}
type T struct {
S string
}
func (t *T) M(){
if t == nil{
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
func main(){
var i I
var t *T
//i.M() //报错 panic: runtime error: invalid memory address or nil pointer dereference
i = t
i.M() //<nil>
i = &T{"Hello"}
i.M() //Hello
}
空接口
- 空接口可保存任何类型的值
- 空接口被用来处理未知类型的值
例子
func describe(i interface{}){
fmt.Printf("(%v, %T)
",i,i)
}
func main(){
var i interface{}
describe(i) //(<nil>, <nil>)
i = 100
describe(i) //(100, int)
i = "hello"
describe(i) //(hello, string)
}
Go程和通道
-
Go程 是由Go运行时管理的轻量级线程
-
通道是带有类型的管道 可以通过它用信道操作符 <- 来发送或者接收值
-
和映射与切片一样 信道在使用前必须创建
-
默认情况下 发送和接收操作在另一端准备好之前 都会阻塞 这使得Go程可以在没有显示的锁或竞台变量情况下进行同步
-
带缓冲的信道 仅当信道的缓冲区填满之后 向其发送数据才会阻塞
-
发送者可以通过close关闭一个通道来表示没有需要发送的值了 接收者可以通过为接收表达式分配第二个参数来测试通道是否被关闭 若没有指可以接收 且信道已被关闭 那么在执行完之后 返回值会被设置为false
-
循环
for i:=range c
会不断从信道接收值 直到它被关闭 -
只有发送者才能关闭信道 而接收者不能 向一个已关闭的信道发送数据 会引发程序恐慌(panic)
例子1
func sum(s []int,c chan int){
sum := 0
for _,v := range s{
sum += v
}
//一般通过指针 或修改返回值 将结果返回给调用者 这里使用通道的方式 将数据同步给调用者
c <- sum
}
func main(){
s := []int{1,1,1,2,2,2}
c := make(chan int)
go sum(s[:len(s)/2],c)
go sum(s[len(s)/2:],c)
x,y := <-c, <-c
fmt.Println(x,y)
}
例子2
c := make(chan int)
//c <- 1 //报错 fatal error: all goroutines are asleep - deadlock!
c = make(chan int,1)
c <- 1
fmt.Println(<-c) //1
例子3
func fib(n int,c chan int){
x,y := 0,1
for i:=0; i < n; i++{
x,y = y,x+y
}
c <-x
close(c)
}
func main(){
c := make(chan int)
go fib(10,c)
for i := range c{
fmt.Println(i)
}
}