Golang basic_leamingM 2023 3 topic
Posted 知其黑、受其白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang basic_leamingM 2023 3 topic相关的知识,希望对你有一定的参考价值。
阅读目录
- topic
- 1 使用值为 nil 的 slice、map 会发生啥
- 2 访问 map 中的 key,需要注意啥
- 3 写关闭异常
- 4 string 类型的值可以修改吗
- 5 switch 中如何强制执行下一个 case 代码块
- 6 如何关闭 HTTP 的响应体
- 7 解析 JSON 数据时,默认将数值当做哪种类型
- 8 如何从 panic 中恢复
- 9 闭包错误引用同一个变量问题怎么处理
- 10 在循环内部执行 defer 语句会发生啥
- 11 Array 类型的值作为函数参数
- 12 Golang 可变参数
- 13 range 迭代 slice 时,怎么修改值
- 14 协程还没执行,程序就结束了?
- 15 九九乘法表
- 16 质数 - 求 1 到 100 以内的质数
- 17 nil interface 的区别
- 18 水仙花
- 19 字符统计
- 20 数组插入排序
- 21 选择排序
- 22 冒泡排序
topic
1 使用值为 nil 的 slice、map 会发生啥
允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素,则会造成运行时 panic。
package main
func main()
// var m map[string]int
// error: panic: assignment to entry in nil map
// map 的正确声明,分配了实际的内存
m := make(map[string]int)
m["one"] = 1
println(m["one"]) // 1
package main
func main()
var s []int
s = append(s, 1)
println(s[0]) // 1
2 访问 map 中的 key,需要注意啥
当访问 map 中不存在的 key 时,Go 则会返回元素对应数据类型的零值,比如 nil、’’ 、false 和 0
,取值操作总有值返回,故不能通过取出来的值,来判断 key 是不是在 map 中。
检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可。
func main()
x := map[string]string"one": "2", "two": "", "three": "3"
if v := x["two"]; v == ""
fmt.Println("key two is no entry")
// 键 two 存不存在都会返回的空字符串
// 输出:key two is no entry
3 写关闭异常
package main
import (
"fmt"
"time"
)
func main()
ticker := time.NewTicker(time.Second * 1)
exit := make(chan int)
go func()
for t := range ticker.C
fmt.Println("ticker被触发", t)
fmt.Println("ticker结束")
<-exit
()
time.Sleep(time.Second * 3)
// ticker.Stop()
exit <- 1
4 string 类型的值可以修改吗
不能,尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的。
string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte
修改后,再转为 string
即可。
package main
import "fmt"
/*
// 修改字符串的错误示例
func main()
x := "text"
x[0] = "T"
fmt.Println(x)
// error: cannot assign to x[0] (value of type byte)
*/
func main()
x := "text"
xBytes := []byte(x)
// 注意此时的 T 是 rune 类型
xBytes[0] = 'T'
x = string(xBytes)
fmt.Println(x) // Text
5 switch 中如何强制执行下一个 case 代码块
switch 语句中的 case 代码块会默认带上 break,但可以使用 fallthrough
来强制执行下一个 case 代码块。
package main
import "fmt"
func main()
isSpace := func(char byte) bool
switch char
// 空格符会直接 break,返回 false // 和其他语言不一样
case ' ':
// fallthrough // 返回 true
case '\\t':
return true
return false
fmt.Println(isSpace('\\t')) // true
fmt.Println(isSpace(' ')) // false
6 如何关闭 HTTP 的响应体
直接在处理 HTTP 响应错误的代码块中,直接关闭非 nil 的响应体;手动调用 defer 来关闭响应体。
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func checkError(err error)
println("err===", err)
func main()
resp, err := http.Get("http://www.baidu.com")
checkError(err)
// 关闭 resp.Body 的正确姿势
if resp != nil
defer resp.Body.Close()
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
checkError(err)
fmt.Println(string(body))
7 解析 JSON 数据时,默认将数值当做哪种类型
在 encode/decode JSON
数据时,Go 默认会将数值当做 float64
处理。
解析到结构体
package main
import (
"encoding/json"
"fmt"
)
type Server struct
ServerName string
ServerIP string
type Serverslice struct
Servers []Server
func main()
var s Serverslice
str := `
"servers": [
"serverName": "Shanghai_VPN",
"serverIP": "127.0.0.1"
,
"serverName": "Beijing_VPN",
"serverIP": "127.0.0.2"
]
`
json.Unmarshal([]byte(str), &s)
fmt.Println(s)
// [Shanghai_VPN 127.0.0.1 Beijing_VPN 127.0.0.2]
fmt.Println(s.Servers[0].ServerIP)
// 127.0.0.1
使用 encoding/json 包来编码和解码 JSON
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type User struct
Firstname string `json:"firstName"`
Lastname string `json:"lastName"`
Age int `json:"age"`
func main()
http.HandleFunc("/decode", func(w http.ResponseWriter, r *http.Request)
var user User
json.NewDecoder(r.Body).Decode(&user)
fmt.Fprintf(w, "%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)
)
http.HandleFunc("/encode", func(w http.ResponseWriter, r *http.Request)
peter := User
Firstname: "John",
Lastname: "Doe",
Age: 25,
json.NewEncoder(w).Encode(peter)
)
http.ListenAndServe(":8080", nil)
8 如何从 panic 中恢复
在一个 defer 延迟执行的函数中调用 recover ,它便能捕捉/中断 panic。
package main
import "fmt"
// 正确的 recover 调用示例
func main()
defer func()
fmt.Println("recovered: ", recover())
()
panic("not good")
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
recovered: not good
PS E:\\TEXT\\test_go\\one>
recover 的执行时机
recover 必须在 defer 函数中运行。
recover 捕获的是祖父级调用时的异常,直接调用时无效。
直接 defer 调用也是无效。
defer 调用时多层嵌套依然无效。
必须在 defer 函数中直接调用才有效。
panic 和 recover 使用原则:
- defer 需要放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。
- recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
- 多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
panic 与 recover 的关系:
如果有 panic 但没有 recover,那么程序会宕机。
如果有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
虽然 panic/recover 能模拟其他语言的异常机制,但并不建议在编写普通函数时也经常性使用这种特性。在 panic 触发的 defer 函数内,可以继续调用 panic,进一步将错误外抛,直到程序整体崩溃。
package main
import "fmt"
func main()
defer func()
if info := recover(); info != nil
fmt.Println("panic happened, Info =", info)
else
fmt.Println("normally exit")
()
panic("fatal error")
fmt.Println("continue...")
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
panic happened, Info = fatal error
PS E:\\TEXT\\test_go\\one>
9 闭包错误引用同一个变量问题怎么处理
在每轮迭代中生成一个局部变量 i 。
如果没有 i := i
这行,将会打印同一个变量。
或者是通过函数参数传入i
。
10 在循环内部执行 defer 语句会发生啥
defer 在函数退出时才能执行,在 for 执行 defer 会导致资源延迟释放。
func 是一个局部函数,在局部函数里面执行 defer 将不会有问题。
11 Array 类型的值作为函数参数
Go 中,数组是值。
作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的。
想改变数组,直接传递指向这个数组的指针类型。
直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)
12 Golang 可变参数
函数方法的参数,可以是任意多个,这种我们称之为可以变参数,比如我们常用的 fmt.Println()
这类函数,可以接收一个可变的参数。
可变参数,可以是任意多个。
我们自己也可以定义可变参数,可变参数的定义,在类型前加上省略号 …
即可。
例子中我们自己定义了一个接受可变参数的函数,效果和 fmt.Println()
一样。可变参数本质上是一个数组,所以我们向使用数组一样使用它,比如例子中的 for range 循环。
13 range 迭代 slice 时,怎么修改值
在 range 迭代中,得到的值其实是元素的一份值拷贝,更新拷贝并不会更改原来的元素,即是拷贝的地址并不是原有元素的地址。
package main
import "fmt"
func main()
data := []int1, 2, 3
for _, v := range data
v *= 10 // data 中原有元素是不会被修改的
fmt.Println("data: ", data) // data: [1 2 3]
如果要修改原有元素的值,应该使用索引直接访问。
package main
import "fmt"
func main()
data := []int1, 2, 3
for i, v := range data
data[i] = v * 10
fmt.Println("data: ", data) // data: [10 20 30]
如果你的集合保存的是指向值的指针,需稍作修改。
依旧需要使用索引访问元素,不过可以使用 range 出来的元素直接更新原有值。
package main
import "fmt"
func main()
data := []*struct num int 1, 2, 3
for _, v := range data
v.num *= 10 // 直接使用指针更新
fmt.Println(data[0], data[1], data[2])
// &10 &20 &30
14 协程还没执行,程序就结束了?
package main
import (
"fmt"
"time"
)
func main()
taskA()
taskB()
fmt.Println("end")
func taskA()
// do...
time.Sleep(time.Second * 5)
fmt.Println("taskA finished")
func taskB()
// do...
time.Sleep(time.Second * 2)
fmt.Println("taskB finished")
整个程序同步执行总耗时约 7 秒。
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
taskA finished
taskB finished
end
PS E:\\TEXT\\test_go\\one>
由于任务 A 和任务 B 没有依赖关系,可以让两个任务异步去执行。通过 go 关键字去创建协程,异步执行。
package main
import (
"fmt"
"time"
)
func main()
go taskA()
taskB()
fmt.Println("end")
func taskA()
// do...
time.Sleep(time.Second * 5)
fmt.Println("taskA finished")
func taskB()
// do...
time.Sleep(time.Second * 2)
fmt.Println("taskB finished")
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
taskB finished
end
PS E:\\TEXT\\test_go\\one>
运行代码可以看到的是,只有任务 B 执行完成,而任务 A 没有执行完成,这是由于任务 A 在一个协程中运行,而任务 B 在主协程中运行。
当主协程运行结束之后,其他协程将被销毁。
也就是说,任务A还没来的及执行就结束了。
那么让主协程 sleep 几秒钟,等待任务A的协程运行完毕,是不是就可以了?
func main()
go taskA()
taskB()
time.Sleep(time.Second * 6)
fmt.Println("end")
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
taskB finished
taskA finished
end
PS E:\\TEXT\\test_go\\one>
运行代码显然可以的,但正常开发中某个任务的运行时间是不可控的。
sync 包中的 WaitGroup 结构体
ync 包中的 WaitGroup 结构体提供了等待所有协程执行完毕的功能。
Add() 方法可以设置需要等待的协程数量。
Done() 方法在协程执行完成后,调用此方法相当于对计数减 1。
Wait() 方法阻塞,直到所有协程都执行完毕,并调用了 Done 方法。
下面通过 WaitGroup 优化上述代码首先,在主协程中声明一个 WaitGroup 类型的结构体,设置需要等待的协程数,然后在任务 A 和任务 B 的方法结束时,调用 Done 方法,最终在主协程中调用 wait 阻塞等待,一直等待任务A和任务B都执行完成,才执行后续代码。
package main
import (
"fmt"
"sync"
"time"
)
func main()
var wg sync.WaitGroup
wg.Add(2)
go taskA(&wg)
go taskB(&wg)
wg.Wait()
fmt.Println("end")
func taskA(wg *sync.WaitGroup)
defer wg.Done()
// do...
time.Sleep(time.Second * 5)
fmt.Println("taskA finished")
func taskB(wg *sync.WaitGroup)
defer wg.Done()
// do...
time.Sleep(time.Second * 2)
fmt.Println("taskB finished")
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
taskB finished
taskA finished
end
PS E:\\TEXT\\test_go\\one>
15 九九乘法表
package main
import "fmt"
func main()
testMulti()
func testMulti()
for i := 0; i < 10; i++
for j := 1; j <= i; j++
fmt.Printf("%d * %d = %d ", j, i, j*i)
fmt.Println()
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81
PS E:\\TEXT\\test_go\\one>
16 质数 - 求 1 到 100 以内的质数
被 1 和自身整除的数是质数。
package main
import "fmt"
func main()
example1()
func justify(n int) bool
if n <= 1
return false
for i := 2; i < n; i++
if n%i == 0
return false
return true
func example1()
for i := 2; i < 100; i++
// if justify(i) == true
if justify(i)
fmt.Printf("[%d] is prime\\n", i)
17 nil interface 的区别
虽然 interface 看起来像指针类型,但它不是。
interface 类型的变量只有在类型和值
均为 nil 时才为 nil 如果你的 interface 变量的值是跟随其他变量变化的,与 nil 比较相等时小心。如果你的函数返回值类型是 interface,更要小心这个坑:
PS E:\\TEXT\\test_go\\one> go run .\\test1.go
<nil> true
<nil> true
<nil> false
PS E:\\TEXT\\test_go\\one>
这种比较永远不会成立;比较的 lhs 被分配了一个具体类型的值。
package main
import "fmt"
// 正确示例
func main()
doIt := func(arg int) interface
var result *struct = nil
if arg > 0
result = &struct
else
return nil // 明确指明返回 nil
return result
if res := doIt(-1); res != nil
fmt.Println("Good result: ", res)
else
fmt.Println("Bad result: ", res)
// Bad result: <nil>
18 水仙花
“水仙花” 是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个“水仙花数”,因为 153=1 的三次方 + 5 的三次方 + 3 的三次方。
求 100 到 1000 之间的所有水仙花数。
package main
import "fmt"
func main()
example1()
func justify(n 以上是关于Golang basic_leamingM 2023 3 topic的主要内容,如果未能解决你的问题,请参考以下文章
Golang basic_leamingM 2023 3 topic