Golang basic_leamingM 2023 3 topic

Posted 知其黑、受其白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang basic_leamingM 2023 3 topic相关的知识,希望对你有一定的参考价值。

阅读目录

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 1

Golang basic_leamingM 2023 3 topic

Golang basic_leamingM 2023 2 theory

golang:发布订阅系统

魔兽兄弟 调试逆向

二分查找:如何快速定位IP对应的省份地址?