golang从入门到成仙【day02】
day02
for
break
- 跳出循环,包括break后面的语句也不会再执行
// break
for i := 0; i < 10; i++ {if i == 5 {break // 跳出循环,包括break后面的语句也不会再执行}fmt.Println(i)
}
fmt.Println("overbreak")
continue
- 当符合条件时,本次循环跳过,进入下一个循环,注意本次循环的continue后的语句均不会执行
// continue
for i := 0; i < 10; i++ {if i == 5 {continue // 跳过i==5这次循环,本次循环的continue后的语句不会执行}fmt.Println(i)
}
fmt.Println("overcontinue")
switch
要点
- 不用写break,默认就有break功能
- default最多只能有一个,也可以不写
- switch后面可以不写表达式,可以写在case后面
- 可以在switch后面声明变量switch m := 3; m {},这种写法的m只作用在switch里面
- case后面可以写表达式,可以写单个值,也可以写多个值(多个值用逗号分隔,表示符合其中一个即可)
- fallthrough(不建议使用,就是不要用) 就是穿透,执行满足条件的case语句后,遇到fallthrough,就再执行下面那个case,是为了兼容C语言的case设计
// switch// 不用写break,默认就有break功能// default最多只能有一个,也可以不写// switch后面可以不写表达式,可以写在case后面// 可以在switch后面声明变量switch m := 3; m {},这种写法的m只作用在switch里面// case后面可以写表达式,可以写单个值,也可以写多个值(多个值用逗号分隔,表示符合其中一个即可)// fallthrough(不建议使用,就是不要用) 就是穿透,执行满足条件的case语句后,遇到fallthrough,就再执行下面那个case,是为了兼容C语言的case设计var n = 3switch n {case 1:fmt.Println("大拇指")case 2:fmt.Println("食指")case 3:fmt.Println("中指")case 4:fmt.Println("无名指")case 5:fmt.Println("小拇指")default:fmt.Println("无效的数字")}// switch后面可以不写表达式,可以写在case后面switch {case n == 1:fmt.Println("大拇指")case n == 2:fmt.Println("食指")case n == 3:fmt.Println("中指")case n == 4:fmt.Println("无名指")case n == 5:fmt.Println("小拇指")default:fmt.Println("无效的数字")}// 可以在switch后面声明变量switch m := 3; m {case 1:fmt.Println("大拇指")case 2:fmt.Println("食指")case 3:fmt.Println("中指")case 4:fmt.Println("无名指")case 5:fmt.Println("小拇指")default:fmt.Println("无效的数字")}// case后面可以多值switch m := 3; m {case 1, 3, 5, 7:fmt.Println("奇数")case 2, 4, 6, 8:fmt.Println("偶数")}// fallthrough(不建议使用,就是不要用) 就是穿透,执行满足条件的case语句后,遇到fallthrough,就再执行下面那个case,是为了兼容C语言的case设计switch m := 2; m { // 这段会打印出b 和 ccase 1:fmt.Println("a")case 2:fmt.Println("b")fallthroughcase 3:fmt.Println("c")case 4:fmt.Println("d")case 5:fmt.Println("e")}
goto
- 可以将执行顺序引导到标志语句
- 很少用
// goto语句,可以将执行顺序引导到标志语句for i := 0; i < 100; i++ {for j := 'A'; j < 'Z'; j++ {if j == 'C' {break // 这样使用break,只能跳出内层循环,如果当j为C的时候,想要完全跳出两个for循环,那么就可以使用goto语句}fmt.Printf("%v-%c\n", i, j)}}// goto语句可以跳出所有层的foe循环for i := 0; i < 100; i++ {for j := 'A'; j < 'Z'; j++ {if j == 'C' {goto nextStatement}fmt.Printf("%v-%c\n", i, j)}}
nextStatement:fmt.Println("我跳出了所有for循环,来到了这里,将从这继续往下执行")
操作符
要点
Go语言中,++和–是单独的语句,不是运算符,这句话的意思就是不能用a = b++,不能放在等号右边,只能使用b++
a := 1a++fmt.Println(a)
// Go语言是强类型语言,所以==要类型也相同才为等于,左右类型不同,不是返回false,而是报错。比如
b := "s" fmt.Println(a == b)
这是会报错的Go语言中的逻辑运算符 && || ! 跟js不同,他不会短路,只会返回true或false
位运算符:对整数按照二进制位进行操作,注意:是对整数
& 两位是1才为1
| 有一位是1 就是1
^ 异或:两个不同就为1
<< 左移n位,就是二进制右边补n个0;左移n位就是乘以2的n次方, “a<<b”就是把a的各二进制位左移b个位数,高位丢弃,低位补0
>> 右移n位,就是二进制左边补n个0,右边的界限不变,溢出忽略掉;右移n位就是除以2的n次方取整数部分, “a>>b”就是把a的各二进制位右移b个位数
fmt.Println(2 & 5) // 010 与 101 ,结果为 0 fmt.Println(2 | 5) // 010 与 101, 二进制结果为111,转十进制,结果为7 fmt.Println(2 ^ 5) // 010 与 101, 二进制结果为111,转十进制为7 fmt.Println(2 << 3) // 16,因为2是10,左移3位就是10000,那就是16 fmt.Println(10 << 4) // 就是10 * 2^4 = 160 fmt.Println(10 >> 4) // 10的二进制是 1010,往右移四位,就是|1010,这里的|为最后一位分界线,移出去就不要了,舍弃,所以是0 fmt.Println(10 >> 2) // 10的二进制是 1010,往右移两位,就是10|10,这里的|为最后一位分界线,移出去就不要了,舍弃,所以二进制是10,十进制结果为2
特别注意:
左移右移,不要移出范围
var q = int8(1) // int8只能存8位 fmt.Println(q << 10) // 1左移10位,1000000000,超出了8位,取8位,就是0 // 位运算实际应用在ip 权限 文件操作会讲到
赋值运算
- =
- +=
- -=
- *=
- /=
- %=
- <<=
- >>=
- &=
- |=
- ^=
数组
要点
数组是存放元素的容器
必须指定存放的元素的类型和容量(长度)
数组的类型包含长度和类型,所以长度不同的数组,是不能比较的
数组声明
// 数组的声明 var a1 [3]bool var a2 [4]bool fmt.Printf("a1:%T a2:%T\n", a1, a2) // [3]bool [4]bool
数组初始化1
// 数组的初始化 // 如果不初始化,默认值为零值(布尔值:false,整型和浮点型都是0,字符串是"") // 1.初始化方式1 a1 = [3]bool{true, true} // 想这种长度为3,但是初始化了两个元素,第三个就是零值,为false,所以结果为[true true false] fmt.Println(a1) // [true true false]
数组初始化2
// 2.初始化方式2 // ... 会根据初始值自动推断数组的长度是多少 a3 := [...]int{1, 2, 3, 4, 5, 4, 3, 2, 1} fmt.Println(a3)
数组初始化3
// 3.初始化方式3 // 指定某个索引的值 // 如果指定索引的值,那么用...,长度就是指定的最大的索引值+1 a4 := [5]int{0: 1, 4: 2} fmt.Println(a4) // [1 0 0 0 2] a5 := [...]int{0: 1, 4: 2} fmt.Println(a5) // [1 0 0 0 2]
数组遍历
for或for…range遍历
// 数组的遍历 // 用for或for...range cities := [...]string{"北京", "上海", "杭州"} for i := 0; i < len(cities); i++ {fmt.Println(cities[i]) }for index, city := range cities {fmt.Println(index, city) }
多维数组
// 多维数组 var a11 [3][2]int a11 = [3][2]int{[2]int{0, 1},[2]int{1, 2},[2]int{2, 3}, } fmt.Println(a11) // [[0 1] [1 2] [2 3]]
多维数组的遍历
// 多维数组的遍历 for _, v := range a11 {for index, childV := range v {fmt.Println(index, childV) // 打印出六个} }
重点:数组是值类型,不是引用类型
// 重点:数组是值类型,不是引用类型 b1 := [3]int{1, 2, 3} b2 := b1 b2[0] = 100 fmt.Println(b1, b2) // b1不会变是[1 2 3],因为数组是值类型,不是引用类型; b2是[100 2 3]
数组练习题
求数组[1, 3, 5, 7, 8]所有元素的和
// 1. 求数组[1, 3, 5, 7, 8]所有元素的和 a1 := [...]int{1, 3, 5, 7, 8} sum := 0 for _, v := range a1 {sum += v } fmt.Printf("a1数组的和为%d\n", sum) // a1数组的和为24
找出数组[1, 3, 5, 7, 8]中和为8的两个元素的下表分别是(0, 3)和(1, 2)
// 2. 找出数组[1, 3, 5, 7, 8]中和为8的两个元素的下表分别是(0, 3)和(1, 2) for i, v := range a1 {for j := i + 1; j < len(a1); j++ {if v+a1[j] == 8 {fmt.Printf("(%d, %d)\n", i, j) // (0, 3) (1, 2)}} }
切片
要点
切片的有点:是一个拥有相同类型元素的可变长度的度列。它是基于数组类型做的一层封装。灵活,支持自动扩容。
切片内部结构(3部分):地址、长度、容量
切片是引用类型 (注意:数组是值类型)
a1 := [...]int{1, 3, 5, 7, 9, 11, 13} a1[6] = 1300 // 原数组改变,该数组基础上的切片也会改变,因为切片是引用类型 fmt.Println("s6:", s6) // [7, 9, 11, 1300] fmt.Println("s8:", s8) // [1300]
切片声明
// 切片声明 var s1 []int // 定义一个存放int类型元素的切片 var s2 []string fmt.Println(s1, s2) // [] []
切片的初始化
// 切片的初始化 s1 = []int{1, 2} s2 = []string{"北京", "上海", "杭州"} fmt.Println(s1, s2) // [1 2] [北京 上海 杭州]
由数组得到切片
// 由数组得到切片 a1 := [...]int{1, 3, 5, 7, 9, 11, 13} s3 := a1[0:4] // 基于一个数组切割,左闭右开,左包含右不包含 fmt.Println(s3) // [1 3 5 7] s4 := a1[1:6] fmt.Println(s4) // [3 5 7 9 11] s5 := a1[:4] // 相当于[0:4] s6 := a1[3:] // 相当于[3: len(a1)] [7, 9, 11, 13] s7 := a1[:] // 相当于[0: len(a1)] fmt.Println(s5, s6, s7) // [1, 3, 5, 7] [7, 9, 11, 13] [1, 3, 5, 7, 9, 11, 13]
切片容量
- 切片的容量:底层数组的容量,开始切的位置到原数组的末尾
// 切片的容量指的是底层数组的容量,开始切的位置到原数组的末尾 fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) // len(s5):4 cap(s5):7 fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) // len(s6):4 cap(s6):4
切片再切片
// 切片再切片 s8 := s6[3:] fmt.Println(s8) // [13] fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8)) // len(s8):1 cap(s8):1
通过make创建切片
make(类型, 长度, 容量), 如果容量不写,则跟长度一样
s1 := make([]int, 5, 10) // []int类型 5是长度, 10是容量fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[0 0 0 0 0] len(s1)=5 cap(s1)=10
切片的本质:
- 切片不保存值,就是一个框,框住了一块连续的内存(数组),真正的数据都是保存在底层数组里的
nil与切片
一个nil值的切片并没有底层数组
一个nil值的切片的长度和容量都为0
但是我们不能说一个长度和容量都为0的切片一定是nil
var m1 []int // 是一个切片,为nil,没有底层数组 m2 := []int{} // 是一个切片,不为nil,为[] m3 := make([]int, 0) // 是一个切片,不为nil,为[] fmt.Println(m1 == nil, m2 == nil, m3 == nil) // true false false
判断一个切片是否为空
- 要用 len(s) == 0 来判断,不能用 s == nil 来判断
切片的赋值
sArr := [...]int{1, 3, 5} s3 := sArr[:] s4 := s3 // s3和s4都指向了同一个底层数组,注意,切片不保存值,只是一个框 fmt.Println(s4) // [1 3 5] s3[0] = 1000 fmt.Println(s3) // [1000 3 5] fmt.Println(s4) // [1000 3 5] fmt.Println(sArr) // [1000 3 5] // 上面这段代码告诉我们两点: // 切片是引用类型 // 切片元素值的改变,会同步改变底层数组对应元素的值
切片的遍历
索引遍历
for range 遍历
// 1. 索引遍历 for i := 0; i < len(s3); i++ {fmt.Println(s3[i]) }// 2. for range循环遍历 for i, v := range s3 {fmt.Println(i, v) }
append为切片追加元素
- 调用append函数,必须用切片变量接收返回值,因为当append追加元素的时候,如果底层数组放不下(超出底层数组的容量).Go就会把底层数组换一个更大的,这样需要有一个新的变量去接收新底层数组上的切片
- append追加元素,原来的底层数组放不下的时候,Go就会把底层数组换一个更大的,这里就涉及到扩容策略
- 扩容策略
- 时机:发生在append()调用时候
- 策略:
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
- 注意:切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。
// append() 为切片追加元素func main() {s1 := []string{"北京", "上海", "深圳"}fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳] len(s1)=3 cap(s1)=3// 为s1添加一个杭州// s1[3] = "广州" // 会执行报错,因为切片容量是3,赋值第四个元素超出了,所以报错// 调用append函数,必须用切片变量接收返回值,因为当append追加元素的时候,如果底层数组放不下(超出底层数组的容量).Go就会把底层数组换一个更大的,这样需要有一个新的变量去接收新底层数组上的切片// 重点: append追加元素,原来的底层数组放不下的时候,Go就会把底层数组换一个更大的,这里就涉及到扩容策略/*扩容策略:- 时机:发生在append()调用时候- 策略:- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。- 注意:切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。*/s1 = append(s1, "杭州")fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳 杭州] len(s1)=4 cap(s1)=6s1 = append(s1, "广州", "成都")fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳 杭州 广州 成都] len(s1)=6 cap(s1)=6// 切片追加切片// 把ss的元素追加到s1中// ...表示拓展,拆开所有元素ss := []string{"武汉", "西安", "苏州"}s1 = append(s1, ss...)fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) // s1=[北京 上海 深圳 杭州 广州 成都 武汉 西安 苏州] len(s1)=9 cap(s1)=12}
copy()函数赋值切片
- copy是值拷贝,不是引用拷贝
- 且目标切片的长度是多少,拷贝过来就最多是多少个元素,注意是看长度,不是容量
// copy
// copy()函数复制切片func main() {a1 := []int{1, 3, 5}a2 := a1 // 赋值// var a3 []int // 这样声明变量是nil,没有空间,所以执行下面的copy是拷贝不进去的// copy(a3, a1) // 拷贝var a3 = make([]int, 3, 3)copy(a3, a1) // 拷贝fmt.Println(a1, a2, a3) // [1 3 5] [1 3 5] [1 3 5]a1[0] = 100fmt.Println(a1, a2, a3) // [100 3 5] [100 3 5] [1 3 5]// 上面得出的结论是:// copy是值拷贝,不是引用拷贝// 且目标切片的长度是多少,拷贝过来就最多是多少个元素,注意是看长度,不是容量// 删除元素// 没有专门删除的方法// 把a1中索引为1的3删除oldArr := [...]int{1, 3, 5}aa1 := oldArr[:]fmt.Println("aa1", aa1)aa1 = append(aa1[:1], aa1[2:]...)fmt.Println(aa1) // [1 5]// 以下是重点,易错点fmt.Println(oldArr) // [1 5 5] // 删除操作后,切片变成了[1 5],对应底层数组的元素也变成了1和5,所以底层数组就是[1 5 5]// 记住一点:切片永远不存值,切片改的值永远是底层数组的值// 切片的删除,就相当于把切片的框缩短了,缩短后,再去套底层数组fmt.Println(cap(oldArr)) // 3 删除操作,底层数组的容量是不会变的
}
切片删除元素
见上面的代码
切片练习题
- 切片排序
- sort.Ints(切片)
- 这个Ints是类型,可以换成strings等
- sort.Ints(切片)
// 1. append练习题1
var a = make([]int, 5, 10)
for i := 0; i < 10; i++ {a = append(a, i)
}
fmt.Println(a) // [0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
fmt.Println(cap(a)) // 20// 2. append练习题2
a1 := [...]int{1, 3, 5, 7, 9, 11, 13, 15, 17}
s1 := a1[:]// 删除元素3
s1 = append(s1[:1], s1[2:]...)
fmt.Println(s1) // [1 5 7 9 11 13 15 17]
fmt.Println(a1) // [1 5 7 9 11 13 15 17 17]// 数组排序 利用切片和sort.Ints()
var aa1 = [...]int{3, 7, 8, 9, 1}
sort.Ints(aa1[:])
fmt.Println(aa1) // [1 3 7 8 9]
指针
要点
Go语言中,指针只能读,不能修改,不能修改指针变量指向的地址
两个要点
& 取地址
* 根据地址取值
n := 18 p := &n fmt.Println(p) // 0xc0000a2058 fmt.Printf("%T\n", p) // *int 说明p的类型是int类型的指针// 2. * 根据地址取值 m := *p fmt.Println(m) // 18 fmt.Printf("%T\n", m) // int
new和make
new函数申请一个内存地址
var a = new(int) // 相当于 var a *int,但是区别是使用new会给a申请一块内存地址,而var a *int只是声明,没有内存地址 fmt.Println(a) // 0xc0000140e8 fmt.Println(*a) // 0 *a = 100 // 去内存地址对应的值,赋值为100 fmt.Println(*a) // 100 fmt.Printf("%T\n", a) // *int fmt.Printf("%T\n", *a) // int
make函数申请一个内存地址
与new的区别是,它只作用于slice、map和channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型。因为这三种类型就是引用类型,所以就没必要返回他们的指针了。
var j map[string]string j = make(map[string]string, 3) fmt.Println(j) j["a"] = "a" j["b"] = "b" j["c"] = "c" fmt.Println(j) // map[a:a b:b c:c]
面试题(重点)
make和new的区别是什么?
- make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
- 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
- new很少用,一般给基本数据类型申请内存
new能作用在slice map channel上吗
可以
var mm = new(map[int]int) // 因为map本身就是引用类型,所以new一下就是变成了&map[],而不是*map[] fmt.Println(mm) // &map[] fmt.Println(*mm) // map *mm = map[int]int{1: 2} fmt.Println(mm) // &map[1:2] fmt.Println(*mm) // map[1:2]
必须要时刻注意的点
对于引用类型的变量,我们在使用的时候,要做两步:
- 声明它
- 给它分配内存空间
所以,不要引用变量一声明,就去使用它,会报错的,必须要分配内存空间
举以下两个使用错误的例子:
// 错误例子1: // var a *int // a是nil pointer,没有内存空间 // *a = 100 // 执行到这一句就报错了:panic: runtime error: invalid memory address or nil pointer dereference // fmt.Println(*a)// 错误例子2: // var b map[string]int // b["哆瑞咪发"] = 100 // 执行到这一句就报错了:panic: assignment to entry in nil map // fmt.Println(b)
map
要点
map声明后,必须要分配空间
make声明一个切片,能自动扩容,但最好估算好该map容量,避免在程序运行期间再动态扩容,这样性能更高
map声明
var m1 map[string]int // map声明后,必须要分配空间 m1 = make(map[string]int, 10) // 容量,能自动扩容,最好估算好该map容量,避免在程序运行期间再动态扩容,这样性能更高 m1["理想"] = 18 m1["jiwuming"] = 35fmt.Println(m1) // map[jiwuming:35 理想:18]
map取值
// 取值 fmt.Println(m1["理想"]) // 18 v, ok := m1["娜扎"] fmt.Println(v, ok) // 0 false if !ok {fmt.Println("查无此key") }
map的遍历
for key, v := range m1 {fmt.Println(key, v) }
删除键值对使用delete()
// 删除键值对使用delete() delete(m1, "jiwuming") fmt.Println(m1) //map[理想:18] // 如果删除一个不存在的,则啥也不会发生,不进行操作
元素为map的切片
面试题(特别易出错)
// 元素类型为map的切片 var s1 = make([]map[int]string, 1, 10)/*这里需要特别注意:var s1 = make([]map[int]string, 0, 10)s1[0][100] = "A"会报错,panic: runtime error: index out of range [0] with length 0原因:第一句只是创建了一个切片,但是长度为0,而第二句给第一个元素赋值,那么索引就越界了,就报了上面的错误如果将上面的0改为1,会怎么样?也就是:var s1 = make([]map[int]string, 1, 10)s1[0][100] = "A"会报错:panic: assignment to entry in nil map原因:没有对内部的map进行做初始化。第一句创建了切片,长度为1,但是这个切片的第0个,只是声明map[int]string,并没有创建空间,由于引用类型必须声明且创建空间,所以这时候会报错所以,正确的做法是:第一种:s1[0] = map[int]string{0: "yuhua"}第二种:s1[0] = make(map[int]string, 1)s1[0][100] = "A"*/ // s1[0] = map[int]string{0: "yuhua"} s1[0] = make(map[int]string, 1) s1[0][100] = "A" // [map[100:A]] fmt.Println(s1)
值为切片的map
// 值为切片类型的map
var m1 = make(map[string][]int, 1)
m1["hello"] = []int{1, 2, 3}
fmt.Println(m1) // map[hello:[1 2 3]]
map的练习题
练习题一:按照指定顺序遍历map
思路:
- 先将map中所有的key取出
- 然后对key组成的切片进行排序
- 然后再循环这个key,取出对应的value
举例: 创建100组随机的键值对构成map,然后有序遍历
rand.Seed(time.Now().UnixNano()) // 初始化随机数种子var scoreMap = make(map[string]int, 200)for i := 0; i < 100; i++ {key := fmt.Sprintf("stu%.2d", i)value := rand.Intn(100) // 生成0-99的随机的帧数scoreMap[key] = value }// 取出所有的key存入切片中 var keys = make([]string, 0, 100) for k := range scoreMap {keys = append(keys, k) } sort.Strings(keys) for _, keyVal := range keys {fmt.Printf("key:%s value:%d\n", keyVal, scoreMap[keyVal]) }
练习题二:写一个程序,统计一个字符串中每个单词出现的次数。比如: “how do you do”中how=1 do=2 you=1
// 写一个程序,统计一个字符串中每个单词出现的次数。比如: “how do you do”中how=1 do=2 you=1 words := "how do you do" wordsArr := strings.Split(words, " ") wordsMap := make(map[string]int, 1) for _, v := range wordsArr {_, ok := wordsMap[v]if !ok {wordsMap[v] = 1} else {wordsMap[v]++} } result := "" for key, v := range wordsMap {result += key + fmt.Sprintf("=%d ", v) } fmt.Println(result) // how=1 do=2 you=1
练习题三:写一个程序,判断字符串是回文字符串
回文字符串:字符串从左往右读和从右往左度是一样的,那么就是回文。比如:上海自来水来自海上
func isHuiWen(str string) bool {// 把字符串转成rune类型对切片var strSlice = make([]rune, 0, len(str))for _, v := range str {strSlice = append(strSlice, v)}fmt.Println(strSlice) // [19978 28023 33258 26469 27700 26469 33258 28023 19978]for i := 0; i < len(strSlice)/2; i++ {if strSlice[i] != strSlice[len(strSlice)-1-i] {return false}}return true } fmt.Println(isHuiWen("上海自来水来自海上")) //true
函数
要点
函数的定义
func sum(x int, y int) (ret int) {return x + y }
命名的返回值,就相当于在函数中声明了一个变量
如果返回值是有名的,那么
- 只写return,就返回返回值变量对应的值
- 如果返回值是有名的, 但return后面写了东西,那么就返回return后面的
多个返回值
// 参数的类型简写 func ret4(x, y int, m, n string, i, j bool) int {return x + y }
可变长参数
可变参数是一个切片
可变长参数必须放在函数参数的最后
// 可变长参数,可变参数是一个切片 // 可变长参数必须放在函数参数的最后 func ret5(x string, y ...int) {fmt.Println(x)fmt.Println(y) // 切片[]int } ret5("yh", 1, 2, 3) // yh [1 2 3]
Go语言中没有默认参数的概念
Go语言中传递的都是值类型
golang从入门到成仙【day02】相关推荐
- webgl入门到成仙【入门-04wegbl的实际绘图思路】
04webgl的实际绘图思路 知识点 webgl的绘图思路 找一台电脑 浏览器里内置的webgl渲染引擎,负责渲染webgl图形,只认GLSL ES语言 找一块手绘板 程序对象,承载GLSL ES语言 ...
- Golang 汇编入门知识总结
作者:ivansli,腾讯 IEG 运营开发工程师 在深入学习 Golang 的 runtime 和标准库实现的时候发现,如果对 Golang 汇编没有一定了解的话,很难深入了解其底层实现机制.在这里 ...
- 【Golang 快速入门】高级语法:反射 + 并发
Golang 快速入门 Golang 进阶 反射 变量内置 Pair 结构 reflect 结构体标签 并发知识 基础知识 早期调度器的处理 GMP 模型 调度器的设计策略 并发编程 goroutin ...
- golang快速入门[8.3]-深入理解IEEE754浮点数
前文 golang快速入门[1]-go语言导论 golang快速入门[2.1]-go语言开发环境配置-windows golang快速入门[2.2]-go语言开发环境配置-macOS golang快速 ...
- Golang Web入门(4):如何设计API
Golang Web入门(4):如何设计API 摘要 在之前的几篇文章中,我们从如何实现最简单的HTTP服务器,到如何对路由进行改进,到如何增加中间件.总的来讲,我们已经把Web服务器相关的内容大概梳 ...
- Golang Web入门(3):如何优雅的设计中间件
Golang Web入门(3):如何优雅的设计中间件 摘要 我们上篇文章已经可以实现一个性能较高,且支持RESTful风格的路由了.但是,在Web应用的开发中,我们还需要一些可以被扩展的功能. 因此, ...
- Golang Web入门(2):如何实现一个RESTful风格的路由
Golang Web入门(2):如何实现一个RESTful风格的路由 摘要 在上一篇文章中,我们聊了聊在Golang中怎么实现一个Http服务器.但是在最后我们可以发现,固然DefaultServeM ...
- 【Golang 快速入门】项目实战:即时通信系统
Golang 快速入门 即时通信系统 - 服务端 版本一:构建基础 Server 版本二:用户上线功能 版本三:用户消息广播机制 版本四:用户业务层封装 版本五:在线用户查询 版本六:修改用户名 版本 ...
- C语言—函数_成仙不问道
函数 函数(function):完成特定任务的独立程序代码单. 函数组成:由函数头和函数体组成. 函数头:类型名 函数名(形式参数) 函数体:{语句:return 返回值} //典型函数模型类型名 函 ...
最新文章
- 每天导航超4亿公里,百度地图整合AI功能
- mysql三表查询数据重复_解决mybatis三表连接查询数据重复的问题
- C# 用户控件之温度计
- Redis缓存穿透 缓存击穿 缓存雪崩原因及其解决方案
- XAML和VBA 7规范发布
- 南安职业中专学校计算机专业,南安职专:国家级重点职业中专学校
- 一段比较好的加1操作。能够防止简单的++造成的溢出。
- 一道经典面试题的不同解法
- MAC OSX 正確地同時安裝 PYTHON 2.7 和 PYTHON3
- The Furthest Distance In The World
- CentOS 7下编译FreeSWITCH 1.6
- 马拦过河卒问题 (递推解法)
- C语言学习有感day01
- python 网站 批量 投票_python requests 简单实现易班自动登录,批量_文章发布,投票发布,评论,点赞,v2.0...
- PDF写出:使用fop输出为pdf格式文件的Demo
- 电脑上如何进行屏幕录像?--QVE屏幕录像
- 360奇舞团钟恒:选用Vue.js进行组件化开发,我们遇到了哪些坑?
- WIN32 opengl缩放、旋转、移动图形
- 2038年无数Java应用的崩溃
- 汤小丹计算机操作系统慕课版课后题答案第六章:虚拟储存器
热门文章
- 用python画爱心写一句话_python中用turtle画爱心表白
- [论文翻译]数据集的domian问题:Intramodality Domain Adaptation Using Self Ensembling and Adversarial Training
- Qt项目中,用QPainter进行绘制图形时,边角显示不完整问题的梳理
- 【线性代数】6-1:特征值介绍(Introduction to Eigenvalues)
- ea6500 v1 刷梅林_继续测试:Linksys EA6500 v1 的TT固件
- 苹果系统微信实况图照片发送-竞品分析初步思考
- Redisson 限流器 RRateLimiter的使用
- 吉林大学计算机系高级语言程序设计(C语言)期末题目及解答(上)
- linux系统取消时间同步,linux下时间同步的两种方法分享
- 商品搜索结果页用RecyclerView列表实现的单排和双排展示及切换