马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
常见范例
数组
数组是一个由固定长度的特定范例元素构成的序列,一个数组可以由零个或多个元素构成。
由于数组的长度是固定的,以是在Go语言中很少直接使用数组。
Go语言数组的声明:
- 数组变量名:数组声明及使用时的变量名。
- 元素数量:数组的元素数量,可以是一个表达式,但终极通过编译期盘算的效果必须是整型数值,元素数量不能含有到运行时才气确认巨细的数值。
- Type:可以是恣意根本范例,包罗数组自己,范例为数组自己时,可以实现多维数组。
例子:
- //默认数组中的值是类型的默认值
- var arr [3]int
复制代码 取值:
- 通过索引下标取值,索引从0开始
- fmt.Println(arr[0])
- fmt.Println(arr[1])
- fmt.Println(arr[2])
复制代码 - for range获取
- for index,value := range arr{
- fmt.Printf("索引:%d,值:%d \n",index,value)
- }
复制代码 赋值
- 初始化的时间赋值
- var arr [3]int = [3]int{1,2,3}
- //如果第三个不赋值,就是默认值0
- var arr [3]int = [3]int{1,2}
- //可以使用简短声明
- arr := [3]int{1,2,3}
- //如果不写数据数量,而使用...,表示数组的长度是根据初始化值的个数来计算
- arr := [...]int{1,2,3}
复制代码 - 通过索引下标赋值
- var arr [3]int
- arr[0] = 5
- arr[1] = 6
- arr[2] = 7
复制代码 肯定要注意,数组是定长的,不可更改,在编译阶段就决定了
小本事: 如果觉的每次写 [3]int 有点贫苦,你可以为 [3]int 界说一个新的范例。
- type arr3 [3]int
- //这样每次用arr3 代替[3]int,注意前面学过 定义一个类型后 arr3就是一个新的类型
- var arr arr3
- arr[0] = 2
- for index,value := range arr{
- fmt.Printf("索引:%d,值:%d \n",index,value)
- }
复制代码 如果想要只初始化第三个值怎么写?
- //2 给索引为2的赋值 ,所以结果是 0,0,3
- arr := [3]int{2: 3}
- for index, value := range arr {
- fmt.Printf("索引:%d,值:%d \n", index, value)
- }
复制代码 给索引值为2的数组的值赋为3
比力
如果两个数组范例雷同(包罗数组的长度,数组中元素的范例)的情况下,我们可以直接通过较运算符(==和!=)来判定两个数组是否相称,只有当两个数组的全部元素都是相称的时间数组才是相称的,不能比力两个范例差别的数组,否则步伐将无法完成编译。
- a := [2]int{1, 2}
- b := [...]int{1, 2}
- c := [2]int{1, 3}
- fmt.Println(a == b, a == c, b == c) // "true false false"
- d := [3]int{1, 2}
- fmt.Println(a == d) // 编译错误:无法比较 [2]int == [3]int
复制代码 多维数组
Go语言中答应使用多维数组,由于数组属于值范例,以是多维数组的全部维度都会在创建时自动初始化零值,多维数组尤其得当管理具有父子关系大概与坐标系干系联的数据。
声明多维数组的语法如下所示:
- //array_name 为数组的名字,array_type 为数组的类型,size1、size2 等等为数组每一维度的长度。
- var array_name [size1][size2]...[sizen] array_type
复制代码 二维数组是最简朴的多维数组,二维数组本质上是由多个一维数组构成的。
- // 声明一个二维整型数组,两个维度的长度分别是 4 和 2
- var array [4][2]int
- // 使用数组字面量来声明并初始化一个二维整型数组
- array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
- // 声明并初始化数组中索引为 1 和 3 的元素
- array = [4][2]int{1: {20, 21}, 3: {40, 41}}
- // 声明并初始化数组中指定的元素
- array = [4][2]int{1: {0: 20}, 3: {1: 41}}
复制代码 取值:
- 通过索引下标取值
- 循环取值
- for index,value := range array{
- fmt.Printf("索引:%d,值:%d \n",index,value)
- }
复制代码 赋值:
- // 声明一个 2×2 的二维整型数组
- var array [2][2]int
- // 设置每个元素的整型值
- array[0][0] = 10
- array[0][1] = 20
- array[1][0] = 30
- array[1][1] = 40
复制代码 只要范例同等,就可以将多维数组相互赋值,如下所示,多维数组的范例包罗每一维度的长度以及存储在元素中数据的范例:
- // 声明两个二维整型数组 [2]int [2]int
- var array1 [2][2]int
- var array2 [2][2]int
- // 为array2的每个元素赋值
- array2[0][0] = 10
- array2[0][1] = 20
- array2[1][0] = 30
- array2[1][1] = 40
- // 将 array2 的值复制给 array1
- array1 = array2
复制代码 由于数组中每个元素都是一个值,以是可以独立复制某个维度,如下所示:
- // 将 array1 的索引为 1 的维度复制到一个同类型的新数组里
- var array3 [2]int = array1[1]
- // 将数组中指定的整型值复制到新的整型变量里
- var value int = array1[1][0]
复制代码 切片
切片(Slice)与数组一样,也是可以容纳多少范例雷同的元素的容器。
与数组差别的是,无法通过切片范例来确定其值的长度。
每个切片值都会将数组作为其底层数据布局。
我们也把如许的数组称为切片的底层数组。
切片(slice)是对数组的一个连续片断的引用,以是切片是一个引用范例。
这个片断可以是整个数组,也可以是由起始和停止索引标识的一些项的子集,必要注意的是,停止索引标识的项不包罗在切片内(左闭右开的区间)。
Go语言中切片的内部布局包罗地点、巨细和容量,切片一样平常用于快速地利用一块数据聚集。
从连续内存地区天生切片是常见的利用,格式如下:
语法阐明如下:
- slice:表现目的切片对象;
- 开始位置:对应目的切片对象的索引;
- 竣事位置:对应目的切片的竣事索引。
从数组天生切片,代码如下:
- var a = [3]int{1, 2, 3}
- //a[1:2] 生成了一个新的切片
- fmt.Println(a, a[1:2])
复制代码 从数组或切片天生新的切片拥有如下特性:
- 取出的元素数量为:竣事位置 - 开始位置;
- 取出元素不包罗竣事位置对应的索引,切片末了一个元素使用 slice[len(slice)] 获取;
- 当缺省开始位置时,表现从连续地区开头到竣事位置(a[:2]);
- 当缺省竣事位置时,表现从开始位置到整个连续地区末端(a[0:]);
- 两者同时缺省时,与切片自己等效(a[:]);
- 两者同时为 0 时,等效于空切片,一样平常用于切片复位(a[0:0])。
注意:超界会报运行时错误,好比数组长度为3,则竣事位置最大只能为3
切片在指针的底子上增长了巨细,束缚了切片对应的内存地区,切片使用中无法对切片内部的地点和巨细举行手动调解,因此切片比指针更安全、强盛。
示例
切片和数组密不可分,如果将数组明白为一栋办公楼,那么切片就是把差别的连续楼层出租给使用者,出租的过程必要选择开始楼层和竣事楼层,这个过程就会天生切片
- var highRiseBuilding [30]int
- for i := 0; i < 30; i++ {
- highRiseBuilding[i] = i + 1
- }
- // 区间
- fmt.Println(highRiseBuilding[10:15])
- // 中间到尾部的所有元素
- fmt.Println(highRiseBuilding[20:])
- // 开头到中间指定位置的所有元素
- fmt.Println(highRiseBuilding[:2])
复制代码 声明切片
除了可以从原有的数组大概切片中天生切片外,也可以声明一个新的切片,每一种范例都可以拥有其切片范例,表现多个雷同范例元素的连续聚集。
切片范例声明格式如下:
- //name 表示切片的变量名,Type 表示切片对应的元素类型。
- var name []Type
复制代码- // 声明字符串切片
- var strList []string
- // 声明整型切片
- var numList []int
- // 声明一个空切片
- var numListEmpty = []int{}
- // 输出3个切片
- fmt.Println(strList, numList, numListEmpty)
- // 输出3个切片大小
- fmt.Println(len(strList), len(numList), len(numListEmpty))
- // 切片判定空的结果
- fmt.Println(strList == nil)
- fmt.Println(numList == nil)
- fmt.Println(numListEmpty == nil)
复制代码 切片是动态布局,只能与 nil 判定相称,不能相互判定相称。声明新的切片后,可以使用 append() 函数向切片中添加元素。
- var strList []string
- // 追加一个元素
- strList = append(strList, "go教程")
- fmt.Println(strList)
复制代码 使用 make() 函数构造切片
如果必要动态地创建一个切片,可以使用 make() 内建函数,格式如下:
- make( []Type, size, cap )
复制代码 Type 是指切片的元素范例,size 指的是为这个范例分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,低落多次分配空间造成的性能题目。
- a := make([]int, 2)
- b := make([]int, 2, 10)
- fmt.Println(a, b)
- //容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2
- //但如果我们给a 追加一个 a的长度就会变为3
- fmt.Println(len(a), len(b))
复制代码 使用 make() 函数天生的切片肯定发生了内存分配利用,但给定开始与竣事位置(包罗切片复位)的切片只是将新的切片布局指向已经分配好的内存地区,设定开始与竣事位置,不会发生内存分配利用。
思考题 len和cap
- var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- myslice := numbers4[4:6]
- //这打印出来长度为2
- fmt.Printf("myslice为 %d, 其长度为: %d, 其容量为: %d\n", myslice, len(myslice), cap(myslice))
- //myslice为 [5 6], 其长度为: 2, 其容量为: 6
- myslice = myslice[:5]
- fmt.Printf("myslice为 %d, 其长度为: %d, 其容量为: %d\n", myslice, len(myslice), cap(myslice))
- //myslice为 [5 6 7 8 9], 其长度为: 5, 其容量为: 6
- //为什么 myslice 的长度为2,却能访问到第四个元素
- fmt.Printf("myslice的第四个元素为: %d", myslice[3])
- //myslice的第四个元素为: 8
复制代码 由于cap是实在索引到数组末端,以是自从myslice := numbers4[4:6],切片后,myslice的cap不绝为6,固然被len影响,但是仍旧可以访问到cap中的元素。
容量盘算规则:cap = 数组长度 - 切片起始索引
复制切片
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果参加的两个数组切片不一样大,就会按照此中较小的谁人数组切片的元素个数举行复制。
copy() 函数的使用格式如下:
- copy( destSlice, srcSlice []T) int
复制代码 此中 srcSlice 为数据泉源切片,destSlice 为复制的目的(也就是将 srcSlice 复制到 destSlice),目的切片必须分配过空间且富足承载复制的元素个数,而且泉源和目的的范例必须同等,copy() 函数的返回值表现实际发生复制的元素个数。
下面的代码展示了使用 copy() 函数将一个切片复制到另一个切片的过程:
- slice1 := []int{1, 2, 3, 4, 5}
- slice2 := []int{5, 4, 3}
- copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
- copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
复制代码 切片的引用和复制操尴尬刁难切片元素的影响:
- package main
- import "fmt"
- func main() {
- // 设置元素数量为1000
- const elementCount = 1000
- // 预分配足够多的元素切片
- srcData := make([]int, elementCount)
- // 将切片赋值
- for i := 0; i < elementCount; i++ {
- srcData[i] = i
- }
- // 引用切片数据 切片不会因为等号操作进行元素的复制
- refData := srcData
- // 预分配足够多的元素切片
- copyData := make([]int, elementCount)
- // 将数据复制到新的切片空间中
- copy(copyData, srcData)
- // 修改原始数据的第一个元素
- srcData[0] = 999
- // 打印引用切片的第一个元素 引用数据的第一个元素将会发生变化
- fmt.Println(refData[0])
- // 打印复制切片的第一个和最后一个元素 由于数据是复制的,因此不会发生变化。
- fmt.Println(copyData[0], copyData[elementCount-1])
- // 复制原始数据从4到6(不包含)
- copy(copyData, srcData[4:6])
- for i := 0; i < 5; i++ {
- fmt.Printf("%d ", copyData[i])
- }
- }
复制代码 refData是软链接,copyData是硬链接
map
map 是一种无序的键值对的聚集。
map 最紧张的一点是通过 key 来快速检索数据,key 雷同于索引,指向数据的值。
map 是一种聚集,以是我们可以像迭代数组和切片那样迭代它。不外,map 是无序的,我们无法决定它的返回次序,这是由于 map 是使用 hash 表来实现的。
map 是引用范例,可以使用如下方式声明:
- //[keytype] 和 valuetype 之间允许有空格。
- var mapname map[keytype]valuetype
复制代码 此中:
- mapname 为 map 的变量名。
- keytype 为键范例。
- valuetype 是键对应的值范例。
在声明的时间不必要知道 map 的长度,由于 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 键值对的数量。
- package main
- import "fmt"
- func main() {
- var mapLit map[string]int
- var mapAssigned map[string]int
- mapLit = map[string]int{"one": 1, "two": 2}
- mapAssigned = mapLit
- //mapAssigned 是 mapList 的引用,对 mapAssigned 的修改也会影响到 mapList 的值。
- mapAssigned["two"] = 3
- fmt.Printf("Map literal at "one" is: %d\n", mapLit["one"])
- fmt.Printf("Map assigned at "two" is: %d\n", mapLit["two"])
- fmt.Printf("Map literal at "ten" is: %d\n", mapLit["ten"])
- }
复制代码 map的别的一种创建方式:
- make(map[keytype]valuetype)
复制代码 牢记不要使用new创建map,否则会得到一个空引用的指针
map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度大概最大限定,但是也可以选择标明 map 的初始容量 capacity,格式如下:
- make(map[keytype]valuetype, cap)
复制代码 比方:
- map2 := make(map[string]int, 100)
复制代码 当 map 增长到容量上限的时间,如果再增长新的 key-value,map 的巨细会自动加 1,以是出于性能的思量,对于大的 map 大概会快速扩张的 map,纵然只是大概知道容量,也最好先标明。
既然一个 key 只能对应一个 value,而 value 又是一个原始范例,那么如果一个 key 要对应多个值怎么办?
答案是:使用切片
比方,当我们要处置处罚 unix 呆板上的全部历程,以父历程(pid 为整形)作为 key,全部的子历程(以全部子历程的 pid 构成的切片)作为 value。
通过将 value 界说为 []int 范例大概其他范例的切片,就可以优雅的办理这个题目,示例代码如下所示:
- mp1 := make(map[int][]int)
- mp2 := make(map[int]*[]int)
复制代码 遍历map
map 的遍历过程使用 for range 循环完成,代码如下:
- scene := make(map[string]int)
- scene["cat"] = 66
- scene["dog"] = 4
- scene["pig"] = 960
- for k, v := range scene {
- fmt.Println(k, v)
- }
复制代码 注意:map是无序的,不要盼望 map 在遍历时返回某种盼望次序的效果
删除
使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:
map 为要删除的 map 实例,键为要删除的 map 中键值对的键。
- scene := make(map[string]int)
- // 准备map数据
- scene["cat"] = 66
- scene["dog"] = 4
- scene["pig"] = 960
- delete(scene, "dog")
- for k, v := range scene {
- fmt.Println(k, v)
- }
复制代码 Go语言中并没有为 map 提供任何清空全部元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不消担心垃圾接纳的服从,Go语言中的并行垃圾接纳服从比写一个清空函数要高效的多。
注意map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。
线程安全的map
并发情况下读写 map 时会出现题目,代码如下:
- // 创建一个int到int的映射
- m := make(map[int]int)
- // 开启一段并发代码
- go func() {
- // 不停地对map进行写入
- for {
- m[1] = 1
- }
- }()
- // 开启一段并发代码
- go func() {
- // 不停地对map进行读取
- for {
- _ = m[1]
- }
- }()
- // 无限循环, 让并发程序在后台执行
- for {
- }
复制代码 运行代码会报错,输出如下:
fatal error: concurrent map read and map write
错误信息表现,并发的 map 读和 map 写,也就是说使用了两个并发函数不停地对 map 举行读和写而发生了竞态题目,map 内部会对这种并发利用举行查抄并提前发现。
必要并发读写时,一样平常的做法是加锁,但如许性能并不高,Go语言在 1.9 版本中提供了一种服从较高的并发安全的 sync.Map,sync.Map 和 map 差别,不是以语言原生形态提供,而是在 sync 包下的特殊布局。
sync.Map 有以下特性:
- 无须初始化,直接声明即可。
- sync.Map 不能使用 map 的方式举行取值和设置等利用,而是使用 sync.Map 的方法举行调用,Store 表现存储,Load 表现获取,Delete 表现删除。
- 使用 Range 共同一个回调函数举行遍历利用,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在必要继续迭代遍历时,返回 true,停止迭代遍历时,返回 false。
- package main
- import (
- "fmt"
- "sync"
- )
- func main() {
- //sync.Map 不能使用 make 创建
- var scene sync.Map
- // 将键值对保存到sync.Map
- //sync.Map 将键和值以 interface{} 类型进行保存。
- scene.Store("greece", 97)
- scene.Store("london", 100)
- scene.Store("egypt", 200)
- // 从sync.Map中根据键取值
- fmt.Println(scene.Load("london"))
- // 根据键删除对应的键值对
- scene.Delete("london")
- // 遍历所有sync.Map中的键值对
- //遍历需要提供一个匿名函数,参数为 k、v,类型为 interface{},每次 Range() 在遍历一个元素时,都会调用这个匿名函数把结果返回。
- scene.Range(func(k, v interface{}) bool {
- fmt.Println("iterate:", k, v)
- return true
- })
- }
复制代码 sync.Map 为了包管并发安全有一些性能丧失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。
常见关键字
nil
在Go语言中,布尔范例的零值(初始值)为 false,数值范例的零值为 0,字符串范例的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。
nil和其他语言的null是差别的。
nil 标识符是不能比力的
- package main
- import (
- "fmt"
- )
- func main() {
- //invalid operation: nil == nil (operator == not defined on nil)
- fmt.Println(nil==nil)
- }
复制代码 nil 不是关键字或保存字
nil 并不是Go语言的关键字大概保存字,也就是说我们可以界说一个名称为 nil 的变量,好比下面如许:
- //但不提倡这样做
- var nil = errors.New("my god")
复制代码 nil 没有默认范例
- package main
- import (
- "fmt"
- )
- func main() {
- //error :use of untyped nil
- fmt.Printf("%T", nil)
- print(nil)
- }
复制代码 差别范例 nil 的指针是一样的
- package main
- import (
- "fmt"
- )
- func main() {
- var arr []int
- var num *int
- fmt.Printf("%p\n", arr)
- fmt.Printf("%p", num)
- }
复制代码 nil 是 map、slice、pointer、channel、func、interface 的零值
- package main
- import (
- "fmt"
- )
- func main() {
- var m map[int]string
- var ptr *int
- var c chan int
- var sl []int
- var f func()
- var i interface{}
- fmt.Printf("%#v\n", m)
- fmt.Printf("%#v\n", ptr)
- fmt.Printf("%#v\n", c)
- fmt.Printf("%#v\n", sl)
- fmt.Printf("%#v\n", f)
- fmt.Printf("%#v\n", i)
- }
复制代码 零值是Go语言中变量在声明之后但是未初始化被赋予的该范例的一个默认值。
差别范例的 nil 值占用的内存巨细大概是不一样的
- package main
- import (
- "fmt"
- "unsafe"
- )
- 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
- }
复制代码 详细的巨细取决于编译器和架构
new和make
- new 实用于值范例(int、struct、array),仅分配内存,不初始化。
- make 用于引用范例(slice、map、channel),分配内存并初始化。
- interface 和 func 不能用 new,只能直接赋值详细实现。
make 关键字的紧张作用是创建 slice、map 和 Channel 等内置的数据布局,而 new 的紧张作用是为范例申请一片内存空间,并返回指向这片内存的指针。
- make 分配空间后,会举行初始化,new分配的空间被清零
- *new 分配返回的是指针,即范例 Type。make 返回引用,即 Type;
if else
在Go语言中,关键字if是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件创建,则会实验 if 后由大括号{}括起来的代码块,否则就忽略该代码块继续实验后续的代码。
- if condition {
- // 条件为真执行
- }
复制代码 condition 称之为条件表达式大概布尔表达式,实验效果需返回true或false。{ 必须在条件表达式的尾部
- x := 0
- if x <= 0 {
- fmt.Println("为真进入这里")
- }
复制代码 如果存在第二个分支,则可以在上面代码的底子上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会实验,if 和 else 后的两个代码块是相互独立的分支,只能实验此中一个。
- if condition {
- // 条件为真 执行
- } else {
- // 条件不满足 执行
- }
复制代码- x := 5
- if x <= 0 {
- fmt.Println("为真进入这里")
- //go语言格式要求很严,else必须写在}后面
- }else{
- fmt.Println("为假进入这里")
- }
复制代码 如果存在第三个分支,则可以使用下面这种三个独立分支的情势:
- if condition1 {
- // condition1 满足 执行
- } else if condition2 {
- // condition1 不满足 condition2满足 执行
- }else {
- // condition1和condition2都不满足 执行
- }
复制代码 else if 分支的数量是没有限定的,但是为了代码的可读性,照旧不要在 if 反面参加太多的 else if 布局
if语句可以嵌套:
- /* 定义局部变量 */
- var a int = 100
- var b int = 200
- /* 判断条件 */
- if a == 100 {
- /* if 条件语句为 true 执行 */
- if b == 200 {
- /* if 条件语句为 true 执行 */
- fmt.Printf("a 的值为 100 , b 的值为 200\n" )
- }
- }
复制代码 特殊写法
if 另有一种特殊的写法,可以在 if 表达式之前添加一个实验语句,再根据变量值举行判定,代码如下:
- if a := 10; a >5 {
- fmt.Println(a)
- return
- }
复制代码 这种写法可以将返回值与判定放在一行举行处置处罚,而且返回值的作用范围被限定在 if、else 语句组合中。
在编程中,变量的作用范围越小,所造成的题目大概性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的实验,但全局变量大概会影响全部代码的实验状态,因此限定变量的作用范围对代码的稳固性有很大的资助。
for
go语言中的循环语句只支持 for 关键字,这个其他语言是差别的。
- sum := 0
- //i := 0; 赋初值,i<10 循环条件 如果为真就继续执行 ;i++ 后置执行 执行后继续循环
- for i := 0; i < 10; i++ {
- sum += i
- }
复制代码 第二种写法:
- sum := 0
- for {
- sum++
- if sum > 100 {
- //break是跳出循环
- break
- }
- }
复制代码 上述的代码,如果没有break跳出循环,那么其将无穷循环
第三种写法:
- n := 10
- for n>0 {
- n--
- fmt.Println(n)
- }
复制代码 我们来看下面一种写法:
- step := 2
- //初值可以省略,但是;必须有,但是这样写step的作用域就比较大了,脱离了for循环
- for ; step > 0; step-- {
- fmt.Println(step)
- }
复制代码 进一步简化代码,将 if 判定整合到 for 中,变为下面的代码:
- step := 2
- for step > 0 {
- step--
- fmt.Println(step)
- }
复制代码 竣事循环的方式:
- return
- step := 2
- for step > 0 {
- step--
- fmt.Println(step)
- //执行一次就结束了
- return
- }
- //不会执行
- fmt.Println("结束之后的语句....")
复制代码 - break
- step := 2
- for step > 0 {
- step--
- fmt.Println(step)
- //跳出循环,还会继续执行循环外的语句
- break
- }
- //会执行
- fmt.Println("结束之后的语句....")
复制代码 - painc
- step := 2
- for step > 0 {
- step--
- fmt.Println(step)
- //报错了,直接结束
- panic("出错了")
- }
- //不会执行
- fmt.Println("结束之后的语句....")
复制代码 - goto
- package main
- import "fmt"
- func main() {
- for x := 0; x < 10; x++ {
- for y := 0; y < 10; y++ {
- if y == 2 {
- // 跳转到标签
- goto breakHere
- }
- }
- }
- // 手动返回, 避免执行进入标签
- return
- // 标签
- breakHere:
- fmt.Println("done")
- }
复制代码 代码优化
- package main
- func length(s string) int {
- println("call length.")
- return len(s)
- }
- func main() {
- s := "abcd"
- // 这样写会多次调用length函数
- for i:= 0; i < length(s); i++ {
- println(i, s[i])
- }
- }
复制代码 优化:
- package main
- func length(s string) int {
- println("call length.")
- return len(s)
- }
- func main() {
- s := "abcd"
- // 这样写会多次调佣length函数
- for i,n:= 0,length(s); i <n; i++ {
- println(i, s[i])
- }
- }
复制代码 案例
输出九九乘法表
- package main
- import "fmt"
- func main() {
- // 遍历, 决定处理第几行
- for y := 1; y <= 9; y++ {
- // 遍历, 决定这一行有多少列
- for x := 1; x <= y; x++ {
- fmt.Printf("%d*%d=%d ", x, y, x*y)
- }
- // 手动生成回车
- fmt.Println()
- }
- }
复制代码 for range
for range 布局是Go语言特有的一种的迭代布局,for range 可以遍历数组、切片、字符串、map 及管道(channel)
- for key, val := range coll {
- ...
- }
复制代码 val 始终为集会集对应索引的值拷贝,因此它一样平常只具有只读性子,对它所做的任何修改都不会影响到集会集原有的值
遍历map:
- m := map[string]int{
- "hello": 100,
- "world": 200,
- }
- for key, value := range m {
- fmt.Println(key, value)
- }
复制代码 字符串也可以使用for range:
- str := "go教程"
- //因为一个字符串是 Unicode 编码的字符(或称之为 rune )集合
- //char 实际类型是 rune 类型
- for pos, char := range str {
- fmt.Println(pos,char)
- }
复制代码 每个 rune 字符和索引在 for range 循环中是逐一对应的,它可以大概自动根据 UTF-8 规则辨认 Unicode 编码的字符。
通过 for range 遍历的返回值有肯定的规律:
- 数组、切片、字符串返回索引和值。
- map 返回键和值。
- channel只返回管道内的值。
switch
switch 语句用于基于差别条件实验差别动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
switch 分支表达式可以是恣意范例,不限于常量。可省略 break,默认自动停止。
switch 语句的语法如下:
- switch var1 {
- case val1:
- ...
- case val2:
- ...
- default:
- ...
- }
复制代码 变量 var1 可以是任何范例,而 val1 和 val2 则可以是同范例的恣意值。
范例不被范围于常量或整数,但必须是雷同的范例;大概终极效果为雷同范例的表达式。
您可以同时测试多个大概符合条件的值,使用逗号分割它们,比方:case val1, val2, val3。
- /* 定义局部变量 */
- var grade string = "B"
- var score int = 90
- switch score {
- case 90: grade = "A"
- case 80: grade = "B"
- case 50,60,70 : grade = "C"
- default: grade = "D"
- }
- //swtich后面如果没有条件表达式,则会对true进行匹配
- //swtich后面如果没有条件表达式,则会对true进行匹配
- switch {
- case grade == "A" :
- fmt.Printf("优秀!\n" )
- case grade == "B", grade == "C" :
- fmt.Printf("良好\n" )
- case grade == "D" :
- fmt.Printf("及格\n" )
- case grade == "F":
- fmt.Printf("不及格\n" )
- default:
- fmt.Printf("差\n" )
- }
- fmt.Printf("你的等级是 %s\n", grade )
复制代码 Go内里switch默认相称于每个case末了带有break,匹配乐成后不会自动向下实验其他case,而是跳出整个switch, 那么怎样做到实验完一个case之后,进入下一个case而不是跳出swtich呢?
答案是:fallthrough
- var s = "hello"
- switch {
- case s == "hello":
- fmt.Println("hello")
- fallthrough
- case s != "world":
- fmt.Println("world")
- }
复制代码 注意事项:
- 加了fallthrough后,会直接运行【紧跟的后一个】case或default语句,岂论条件是否满足都会实验
- var s = "hello"
- switch {
- case s == "hello":
- fmt.Println("hello")
- fallthrough
- case s == "world":
- fmt.Println("world")
- }
复制代码 goto
goto 语句通过标签举行代码间的无条件跳转,同时 goto 语句在快速跳出循环、克制重复退出上也有肯定的资助,使用 goto 语句能简化一些代码的实现过程。
使用 goto 退出多层循环
传统写法:
- package main
- import "fmt"
- func main() {
- var breakAgain bool
- // 外循环
- for x := 0; x < 10; x++ {
- // 内循环
- for y := 0; y < 10; y++ {
- // 满足某个条件时, 退出循环
- if y == 2 {
- // 设置退出标记
- breakAgain = true
- // 退出本次循环
- break
- }
- }
- // 根据标记, 还需要退出一次循环
- if breakAgain {
- break
- }
- }
- fmt.Println("done")
- }
复制代码 使用goto的写法:
- package main
- import "fmt"
- func main() {
- for x := 0; x < 10; x++ {
- for y := 0; y < 10; y++ {
- if y == 2 {
- // 跳转到标签
- goto breakHere
- }
- }
- }
- // 手动返回, 避免执行进入标签
- return
- // 标签
- breakHere:
- fmt.Println("done")
- }
复制代码 使用 goto 语句后,无须额外的变量就可以快速退出全部的循环
使用 goto 会集处置处罚错误
多处错误处置处罚 存在代码重复 比方:
- package main
- import (
- "errors"
- "fmt"
- "os"
- )
- func main() {
- err := firstCheckError()
- if err != nil {
- fmt.Println(err)
- exitProcess()
- }
- err = secondCheckError()
- if err != nil {
- fmt.Println(err)
- exitProcess()
- }
- fmt.Println("done")
- }
- func secondCheckError() interface{} {
- return errors.New("错误2")
- }
- func exitProcess() {
- //退出
- os.Exit(1)
- }
- func firstCheckError() interface{} {
- return errors.New("错误1")
- }
复制代码 使用goto:
- package main
- import (
- "errors"
- "fmt"
- "os"
- )
- func main() {
- err := firstCheckError()
- if err != nil {
- fmt.Println(err)
- goto onExit
- }
- err = secondCheckError()
- if err != nil {
- fmt.Println(err)
- goto onExit
- }
- fmt.Println("done")
- return
- onExit:
- exitProcess()
- }
- func secondCheckError() interface{} {
- return errors.New("错误2")
- }
- func exitProcess() {
- fmt.Println("exit")
- //退出
- os.Exit(1)
- }
- func firstCheckError() interface{} {
- return errors.New("错误1")
- }
复制代码 break
break 语句可以竣事 for、switch 和 select 的代码块,别的 break 语句还可以在语句反面添加标签,表现退出某个标签对应的代码块,标签要求必须界说在对应的 for、switch 和 select 的代码块上。
- package main
- import "fmt"
- func main() {
- OuterLoop:
- for i := 0; i < 2; i++ {
- for j := 0; j < 5; j++ {
- switch j {
- case 2:
- fmt.Println(i, j)
- break OuterLoop
- case 3:
- fmt.Println(i, j)
- break OuterLoop
- }
- }
- }
- }
复制代码 continue
continue 语句可以竣事当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用,在 continue 语句后添加标签时,表现开始标签对应的循环
- package main
- import "fmt"
- func main() {
- OuterLoop:
- for i := 0; i < 2; i++ {
- for j := 0; j < 5; j++ {
- switch j {
- case 2:
- fmt.Println(i, j)
- continue OuterLoop
- }
- }
- }
- }
复制代码 |