【Go学习】-01-2-常见类型及关键字

【Go学习】-01-2-常见类型及关键字

  • 1 常见类型
    • 1.1 数组
      • 1.1.1 取值:
      • 1.1.2 赋值
      • 1.1.3 比较
      • 1.1.4 多维数组
    • 1.2 切片
      • 1.2.1 声明切片
        • 1.2.1.1 使用 make() 函数构造切片
        • 1.2.1.2 思考题 len和cap
      • 1.2.2 复制切片
    • 1.3 map
      • 1.3.1 遍历map
      • 1.3.2 删除
      • 1.3.3 线程安全的map
  • 2 常见关键字
    • 2.1 nil
    • 2.2 new和make
    • 2.3 if else
      • 2.2.1 特殊写法
    • 2.4 for
      • 2.4.1 代码优化
      • 2.4.2 案例
    • 2.5 for range
    • 2.6 switch
    • 2.7 goto
    • 2.8 break
    • 2.9 continue


1 常见类型

1.1 数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。

因为数组的长度是固定的,所以在Go语言中很少直接使用数组。

Go语言数组的声明:

var 数组变量名 [元素数量]Type
  • 数组变量名:数组声明及使用时的变量名。
  • 元素数量:数组的元素数量,可以是一个表达式,但最终通过编译期计算的结果必须是整型数值,元素数量不能含有到运行时才能确认大小的数值。
  • Type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。

例子:

//默认数组中的值是类型的默认值
var arr [3]int

1.1.1 取值:

  1. 通过索引下标取值,索引从0开始

    fmt.Println(arr[0])
    fmt.Println(arr[1])
    fmt.Println(arr[2])
    
  2. for range获取

    for index,value := range arr{fmt.Printf("索引:%d,值:%d \n",index,value)
    }
    

1.1.2 赋值

  1. 初始化的时候赋值

    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}
    
  2. 通过索引下标赋值

    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

1.1.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

1.1.4 多维数组

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}}

取值:

  1. 通过索引下标取值

    fmt.Println(array[1][0])
    
  2. 循环取值

    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]

1.2 切片

切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。

与数组不同的是,无法通过切片类型来确定其值的长度。

每个切片值都会将数组作为其底层数据结构。

我们也把这样的数组称为切片的底层数组

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型。

这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内(左闭右开的区间)。

Go语言中切片的内部结构包含地址大小容量,切片一般用于快速地操作一块数据集合。

从连续内存区域生成切片是常见的操作,格式如下:

slice [开始位置 : 结束位置]

语法说明如下:

  • 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])

1.2.1 声明切片

除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合。

切片类型声明格式如下:

//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)
1.2.1.1 使用 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() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

1.2.1.2 思考题 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 = myslice[:5]
fmt.Printf("myslice为 %d, 其长度为: %d, 其容量为: %d\n", myslice, len(myslice), cap(myslice))
//为什么 myslice 的长度为2,却能访问到第四个元素
fmt.Printf("myslice的第四个元素为: %d", myslice[3])

因为cap是其实索引到数组末尾,所以自从myslice := numbers4[4:6],切片后,myslice的cap一直为6,虽然被len影响,但是依旧可以访问到cap中的元素。

1.2.2 复制切片

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() {// 设置元素数量为1000const 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是硬链接

1.3 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]intvar mapAssigned map[string]intmapLit = map[string]int{"one": 1, "two": 2}mapAssigned = mapLit//mapAssigned 是 mapList 的引用,对 mapAssigned 的修改也会影响到 mapList 的值。mapAssigned["two"] = 3fmt.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)

1.3.1 遍历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 在遍历时返回某种期望顺序的结果

1.3.2 删除

使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:

delete(map,)

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 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

1.3.3 线程安全的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 会有更好的性能。

2 常见关键字

2.1 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 nilfmt.Printf("%T", nil)print(nil)
}

不同类型 nil 的指针是一样的

package main
import ("fmt"
)
func main() {var arr []intvar num *intfmt.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]stringvar ptr *intvar c chan intvar sl []intvar 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 ) ) // 8var s []intfmt.Println( unsafe.Sizeof( s ) ) // 24var m map[int]boolfmt.Println( unsafe.Sizeof( m ) ) // 8var c chan stringfmt.Println( unsafe.Sizeof( c ) ) // 8var f func()fmt.Println( unsafe.Sizeof( f ) ) // 8var i interface{}fmt.Println( unsafe.Sizeof( i ) ) // 16
}

具体的大小取决于编译器和架构

2.2 new和make

make 关键字的主要作用是创建 slice、map 和 Channel 等内置的数据结构,而 new 的主要作用是为类型申请一片内存空间,并返回指向这片内存的指针。

  1. make 分配空间后,会进行初始化,new分配的空间被清零
  2. new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
  3. new 可以分配任意类型的数据;

2.3 if else

在Go语言中,关键字if是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号{}括起来的代码块,否则就忽略该代码块继续执行后续的代码。

if condition {// 条件为真执行
}

condition 称之为条件表达式或者布尔表达式,执行结果需返回true或false。{ 必须在条件表达式的尾部

x := 0
if x <= 0 {fmt.Println("为真进入这里")
}

如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行,if 和 else 后的两个代码块是相互独立的分支,只能执行其中一个。

if condition {// 条件为真 执行
} else {// 条件不满足 执行
}
	x := 5if 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 = 100var b int = 200/* 判断条件 */if a == 100 {/* if 条件语句为 true 执行 */if b == 200 {/* if 条件语句为 true 执行 */fmt.Printf("a 的值为 100 , b 的值为 200\n" )}}

2.2.1 特殊写法

if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:

if a := 10; a >5 {fmt.Println(a)return
}

这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。

在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。

2.4 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)
}

结束循环的方式:

  1. return

    step := 2
    for step > 0 {step--fmt.Println(step)//执行一次就结束了return
    }
    //不会执行
    fmt.Println("结束之后的语句....")
    
  2. break

    step := 2
    for step > 0 {step--fmt.Println(step)//跳出循环,还会继续执行循环外的语句break
    }
    //会执行
    fmt.Println("结束之后的语句....")
    
  3. painc

    step := 2
    for step > 0 {step--fmt.Println(step)//报错了,直接结束panic("出错了")}//不会执行fmt.Println("结束之后的语句....")		
    
  4. 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")
    }
    

2.4.1 代码优化

package mainfunc 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 mainfunc 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])}
}

2.4.2 案例

输出九九乘法表

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()}
}

2.5 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只返回管道内的值。

2.6 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 = 90switch 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")
}

注意事项:

  1. 加了fallthrough后,会直接运行【紧跟的后一个】case或default语句,不论条件是否满足都会执行

    var s = "hello"
    switch {
    case s == "hello":fmt.Println("hello")fallthrough
    case s == "world":fmt.Println("world")
    }
    

2.7 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 mainimport ("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 mainimport ("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")returnonExit:exitProcess()
}func secondCheckError() interface{} {return errors.New("错误2")
}func exitProcess() {fmt.Println("exit")//退出os.Exit(1)
}func firstCheckError() interface{} {return errors.New("错误1")
}

2.8 break

break 语句可以结束 for、switch 和 select 的代码块,另外 break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的 forswitchselect 的代码块上。

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 OuterLoopcase 3:fmt.Println(i, j)break OuterLoop}}}
}

2.9 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}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/502019.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

密钥管理系统在数据安全解决方案中的重要性

密钥管理系统在数据安全解决方案中占据着举足轻重的地位&#xff0c;其重要性体现在以下几个方面&#xff1a;一、保障数据机密性 密钥管理系统通过生成、存储和管理加密密钥&#xff0c;确保了数据的机密性。这些密钥用于加密和解密数据&#xff0c;只有授权用户才能访问和使…

关于PINN进一步的探讨

pinn 是有监督、无监督、半监督&#xff1f; PINN&#xff08;Physics-Informed Neural Networks&#xff0c;物理信息神经网络&#xff09;通常被归类为一种有监督学习的方法。在PINN中&#xff0c;神经网络的训练过程不仅依赖于数据点&#xff08;例如实验观测数据&#xff0…

设计形成从业务特点到设计模式的关联

规范和指引在应用架构、数据架构等各架构方向上形成规范性约束指导。同一个决策要点、架构单元在统一的架构原则指导下&#xff0c;会因业务特点差异有不同的实现&#xff0c;经过总结形成了最佳实践。在开展新应用的设计时&#xff0c;根据决策要点以及相关的业务特点&#xf…

Framebuffer 驱动

实验环境: 正点原子alpha 开发板 调试自己编写的framebuffer 驱动,加载到内核之后,显示出小企鹅 1. Framebufer 总体框架 fbmem.c 作为Framebuffer的核心层,向上提供app使用的接口,向下屏蔽了底层各种硬件的差异; 准确来说fbmem.c 就是一个字符设备驱动框架的程序,对…

STM32第十一课:STM32-基于标准库的42步进电机的简单IO控制(附电机教程,看到即赚到)

一&#xff1a;步进电机简介 步进电机又称为脉冲电机&#xff0c;简而言之&#xff0c;就是一步一步前进的电机。基于最基本的电磁铁原理,它是一种可以自由回转的电磁铁,其动作原理是依靠气隙磁导的变化来产生电磁转矩&#xff0c;步进电机的角位移量与输入的脉冲个数严格成正…

WPS-JS宏快速上手

WPS JS宏注意事项 代码后面可以不写分号“ ; ”&#xff1b; 缩进对程序的运行影响不大&#xff0c;但为了易读&#xff08;防止自己以后看不懂&#xff09;&#xff0c;还是乖乖写好&#xff1b; 代码是逐行运行的&#xff0c;意味着下面一行代码错了&#xff0c;前面的代码…

Conda 安装 Jupyter Notebook

文章目录 1. 安装 Conda下载与安装步骤&#xff1a; 2. 创建虚拟环境3. 安装 Jupyter Notebook4. 启动 Jupyter Notebook5. 安装扩展功能&#xff08;可选&#xff09;6. 更新与维护7. 总结 Jupyter Notebook 是一款非常流行的交互式开发工具&#xff0c;尤其适合数据科学、机器…

【CVPR 2024】【遥感目标检测】Poly Kernel Inception Network for Remote Sensing Detection

0.论文摘要 摘要 遥感图像&#xff08;RSIs&#xff09;中的目标检测经常面临几个日益增加的挑战&#xff0c;包括目标尺度的巨大变化和不同范围的背景。现有方法试图通过大核卷积或扩张卷积来扩展主干的空间感受野来解决这些挑战。然而&#xff0c;前者通常会引入相当大的背…

C++语言编程————C++的输入与输出

1.面向过程的程序设计和算法 在面向过程的程序设计中&#xff0c;程序设计者必须指定计算机执行的具体步骤&#xff0c;程序设计者不仅要考虑程序要“做什么”&#xff0c;还要解决“怎么做”的问题&#xff0c;根据程序要“做什么”的要求&#xff0c;写出一个个语句&#xff…

Fabric链码部署测试

参考链接&#xff1a;运行 Fabric 应用程序 — Hyperledger Fabric Docs 主文档 (hyperledger-fabric.readthedocs.io) &#xff08;2&#xff09;fabric2.4.3部署运行自己的链码 - 知乎 (zhihu.com) Fabric2.0测试网络部署链码 - 辉哥哥~ - 博客园 (cnblogs.com) 1.启动测试…

《米塔》为什么能突破160万销量?

1、跟完蛋美女有一定的类似之处&#xff0c;都是针对用户需求打造的商品&#xff0c;所以取得良好的销量不意外。 偏宅的玩家有陪伴、被重视、被爱的需求&#xff0c; 而厂商很懂&#xff0c;无论真人还是二次元都只是手段。 完蛋也是突破百万销量&#xff0c;成为黑马。 2、…

ESP32自动下载电路分享

下面是一个ESP32系列或者ESP8266等电路的一个自动下载电路 在ESP32等模块需要烧写程序的时候&#xff0c;需要通过将EN引脚更改为低电平并将IO0引脚设置为低电平来切换到烧写模式。 有时候也会采用先将IO接到一个按键上&#xff0c;按住按键拉低IO0的同时重新上电的方式进入烧写…

Backend - C# 的日志 NLog日志

目录 一、注入依赖和使用 logger 二、配置记录文件 1.安装插件 NLog 2.创建 nlog.config 配置文件 3. Programs配置日志信息 4. 设置 appsettings.json 的 LogLevel 5. 日志设定文件和日志级别的优先级 &#xff08;1&#xff09;常见的日志级别优先级 &#xff08;2&…

java项目之社区医院信息平台源码(springboot+mysql)

项目简介 社区医院信息平台实现了以下功能&#xff1a; 社区医院信息平台的主要使用者分为管理员可以查看对护士信息进行添加&#xff0c;修改&#xff0c;删除以及查询操作&#xff1b;管理员可以对医生信息进行添加&#xff0c;修改&#xff0c;删除以及查询操作&#xff1…

《普通逻辑》学习记录——命题的判定与自然推理

目录 一、真值 1.1、真值联结词 1.2、真值联结词与逻辑联结词的区别 1.3、真值形式 1.3.1、真值符号的优先级和结合性规则 1.4、真值规则 1.4.1、条件式&#xff08;蕴含式&#xff09; P → Q 的真值规则 1.4.2、双条件式&#xff08;等值式&#xff09; P ↔ Q 的真值规则 1.…

Pycharm连接远程解释器

这里写目录标题 0 前言1 给项目添加解释器2 通过SSH连接3 找到远程服务器的torch环境所对应的python路径&#xff0c;并设置同步映射&#xff08;1&#xff09;配置服务器的系统环境&#xff08;2&#xff09;配置服务器的conda环境 4 进入到程序入口&#xff08;main.py&#…

无刷直流电机(BLDC)六步换向法

文章目录 1、三相BLDCM 基本结构2、三相BLDCM 数学模型3、有霍尔位置传感器直流无刷电机工作原理4、无位置传感器直流无刷电机工作原理5、速度检测6、六步换向双闭环模型仿真6.1 模型总览6.2 系统及参数设置6.3 六步换向模块6.4 仿真效果 7、六步换向速度闭环PWM控制参考 1、三…

Windows11安装Oracle11g以及plsqldev工具连接配置

文章目录 一、安装Oracle数据库软件二、配置数据库三、配置监听&#xff08;listener.ora&#xff09;四、本地网络服务名配置&#xff08;tnsnames.ora&#xff09;五、网络服务名配置以及监听文件路径六、plsqldev工具连接Oracle配置 一、安装Oracle数据库软件 点击“setup.…

IEEE PDF eXpress遇到Font TimesNewRomanPSMT is not embedded的解决方案

IEEE PDF eXpress遇到Font TimesNewRomanPSMT is not embedded的解决方案 问题描述 在IEEE PDF eXpress上上传论文后&#xff0c;出现Font XXX is not embedded的问题。 该问题是指你所插入的图片等&#xff0c;没有将对应的字体嵌入进去。 解决方案 以下以Origin Lab图片…

9.系统学习-卷积神经网络

9.系统学习-卷积神经网络 简介输入层卷积层感受野池化层全连接层代码实现 简介 卷积神经网络是一种用来处理局部和整体相关性的计算网络结构&#xff0c;被应用在图像识别、自然语言处理甚至是语音识别领域&#xff0c;因为图像数据具有显著的局部与整体关系&#xff0c;其在图…