GO语言基础
1. Hello World
package main //main:程序的入口文件包
//在go中,应用程序的入口包要为main,而编译源码没有main包时,将无法编译输出可执行的文件,也就会导致错误
import "fmt" //导入包:fmt
func main() {
fmt.Println("hello world")
}
P.S.任何一个包中只能有一个Go文件带有main()函数
2. 变量
var a = "sky" //同js,根据后面的内容推断类型
var b, c int = 1, 2 //在后面声明类型
var e float64
f := float32(e) //这相当于是强转把()
g := a + "foo" //字符串拼接
fmt.Println(a, b, c, e, f)
fmt.Println(g)
3. 条件判断
-
单条件判断
if 7%2 == 0 { fmt.Println("7 is even") } -
switch-case
- 不需要在每一个case后面写break
- 判断变量类型可以是基本数据类型,也可以是字符串,结构体
t := time.Now() //判断当前系统时间 switch { case t.Hour() < 12: fmt.Println("上午") default: fmt.Println("下午") }
4. 循环
i := 1 //初始化i=1
for {
fmt.Println("loop") //通过break跳出循环
break
}
for j := 7; j < 9; j++ { //类似于java中的for
fmt.Println(j)
}
for n := 0; n < 5; n++ { //通过continue跳过一轮循环
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 { //相当于设置了boolean值,当i <= 3为true时循环继续
fmt.Println(i)
i++
}
5. 数组
var a [5]int //定义了一个长度为5的int数组
a[4] = 100
fmt.Println(a[4], len(a)) //获取对应下标的数组元素和其数组长度
6. 切片(可变数组)
s := make([]string, 3) //创建切片
s[0] = "a"
s[1] = "b"
s[2] = "c"
s = append(s, "d") //给切片扩容
c := make([]string, len(s))
copy(c, s) //元素拷贝
fmt.Println(s[1:3]) //输出下标1-3的元素
fmt.Println(s[:5]) //输出下标0-4的元素(右值是取不到的)
7. map
m := make(map[string]int) //key:string | value:int
m["one"] = 1
m["two"] = 2
fmt.Println(m)
fmt.Println(len(m))
fmt.Println("-------")
fmt.Println(m["one"])
fmt.Println(m["unknow"])
/**
这是因为在Go语言中,当你尝试访问一个map中不存在的键时,它会返回该类型的零值。
由于map的值类型为int,所以返回的零值为0。而ok变量返回的是一个布尔值,表示键是否在map中存在。
ok是一个特殊的布尔变量
由于"unknow"这个键并不存在于map中,所以ok的值为false。
*/
r, ok := m["unknow"]
fmt.Println(r, ok)
delete(m, "one")
8. Range
- 当用于遍历数组、切片或字符串时,
range会返回两个值:索引和元素的值。当用于遍历map时,range会返回两个值:键和值
arr := []int{1, 2, 3}
for i, v := range arr {
fmt.Println(i, v) //i是索引,v是元素
}
9. 函数
func add(a int, b int) int {
return a + b
}
func main() {
res := add(1, 2)
fmt.Println(res)
}
10. 结构体
type user struct {
name string
pwd string
}
func main() {
a := user{name: "yx", pwd: "123"}
b := user{"yx1", "123456"}
c := user{name: "yx2"}
c.pwd = "1024"
fmt.Println(checkPwd(b, "123456"))
}
func checkPwd(u user, pwd string) bool {
return u.pwd == pwd
}
-
结构体方法
type user struct { name string pwd string } func (u *user) resetPwd(pwd string) { //带指针可以对结构体进行修改 u.pwd = pwd } func main() { a := user{name: "yx", pwd: "123"} b := user{"yx1", "123456"} c := user{name: "yx2"} c.pwd = "1024" fmt.Println(checkPwd(b, "123456")) a.resetPwd("2048") fmt.Println(a.pwd) }
11. 错误处理
func findUser(users []user, name string) (v *user, err error) { //使用一个err返回错误信息
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found") //如果没有错误,直接new一个错误对象写上无错误即可
}
12. 字符串操作
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
13. 字符串格式化
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
//跟C一样,用printf进行格式化
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
14. JSON处理
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a) //用json包下的Marshal进行序列化
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b) //反序列化
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
15. 时间处理
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 1648738080
16. 字符串和数字转换
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64) //第一个参数是进制,第二个是精度
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123") //用Atoi进行快速转换
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
17. 进程信息
// go run example/20-env/main.go a b c d //输出一些进程信息
fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
GO实战案例
1. 在线词典
1.1 步骤
- 在网址上进行翻译信息抓取(fanyi.caiyunapp.com/)
-
代码生成
- 将复制的curl粘贴上去(P.S. 生成的代码可能会有错误,记得修改!)
-
代码解读
// DictRequest 请求体来自请求负载的值 type DictRequest struct { TransType string `json:"trans_type"` Source string `json:"source"` UserID string `json:"user_id"` } func main() { // client := &http.Client{} request := DictRequest{TransType: "en2zh", Source: "good"} //序列化结构体 buf, err := json.Marshal(request) if err != nil { log.Fatal(err) } var data = bytes.NewReader(buf) req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) if err != nil { log.Fatal(err) } //通过前面curl获取到的请求头书写代码 req.Header.Set("Connection", "keep-alive") req.Header.Set("DNT", "1") req.Header.Set("os-version", "") req.Header.Set("sec-ch-ua-mobile", "?0") req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") req.Header.Set("app-name", "xy") req.Header.Set("Content-Type", "application/json;charset=UTF-8") req.Header.Set("Accept", "application/json, text/plain, */*") req.Header.Set("device-id", "") req.Header.Set("os-type", "web") req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") req.Header.Set("Origin", "https://fanyi.caiyunapp.com") req.Header.Set("Sec-Fetch-Site", "cross-site") req.Header.Set("Sec-Fetch-Mode", "cors") req.Header.Set("Sec-Fetch-Dest", "empty") req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872") //利用client的DO方法发送请求 resp, err := client.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() bodyText, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf("%s\n", bodyText) }在Go语言中,Client类型代表HTTP客户端。它的零值(DefaultClient)是一个可用的使用DefaultTransport的客户端。Client的Transport字段一般会含有内部状态(缓存TCP连接),因此Client类型值应尽量被重用而不是每次需要都创建新的。Client类型值可以安全的被多个go程同时使用。 你可以使用Client类型的Do方法发送HTTP请求并获取响应。这个方法会遵守客户端设置的策略(如重定向、cookie、认证)。如果客户端的策略(如重定向)返回错误或存在HTTP协议错误时,本方法将返回该错误;如果回应的状态码不是2xx,本方法并不会返回错误。 -
解析response Body(JSON转Golang Struct - 在线工具 - OKTools)
-
代码优化
package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" ) // DictRequest 请求体来自请求负载的值 type DictRequest struct { TransType string `json:"trans_type"` Source string `json:"source"` UserID string `json:"user_id"` } //根据上面生成的json->Go的反序列化代码生成 type DictResponse struct { Rc int `json:"rc"` Wiki struct { KnownInLaguages int `json:"known_in_laguages"` Description struct { Source string `json:"source"` Target interface{} `json:"target"` } `json:"description"` ID string `json:"id"` Item struct { Source string `json:"source"` Target string `json:"target"` } `json:"item"` ImageURL string `json:"image_url"` IsSubject string `json:"is_subject"` Sitelink string `json:"sitelink"` } `json:"wiki"` Dictionary struct { Prons struct { EnUs string `json:"en-us"` En string `json:"en"` } `json:"prons"` Explanations []string `json:"explanations"` Synonym []string `json:"synonym"` Antonym []string `json:"antonym"` WqxExample [][]string `json:"wqx_example"` Entry string `json:"entry"` Type string `json:"type"` Related []interface{} `json:"related"` Source string `json:"source"` } `json:"dictionary"` } //专门用一个query函数来接收需要查询的词 func query(word string) { client := &http.Client{} request := DictRequest{TransType: "en2zh", Source: word} buf, err := json.Marshal(request) if err != nil { log.Fatal(err) } var data = bytes.NewReader(buf) req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) if err != nil { log.Fatal(err) } //通过前面curl获取到的请求头书写代码 req.Header.Set("Connection", "keep-alive") req.Header.Set("DNT", "1") req.Header.Set("os-version", "") req.Header.Set("sec-ch-ua-mobile", "?0") req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36") req.Header.Set("app-name", "xy") req.Header.Set("Content-Type", "application/json;charset=UTF-8") req.Header.Set("Accept", "application/json, text/plain, */*") req.Header.Set("device-id", "") req.Header.Set("os-type", "web") req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi") req.Header.Set("Origin", "https://fanyi.caiyunapp.com") req.Header.Set("Sec-Fetch-Site", "cross-site") req.Header.Set("Sec-Fetch-Mode", "cors") req.Header.Set("Sec-Fetch-Dest", "empty") req.Header.Set("Referer", "https://fanyi.caiyunapp.com/") req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872") //利用client的DO方法发送请求 resp, err := client.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() bodyText, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } //如果状态码不是200需要进行判断,方便状态诊断 if resp.StatusCode != 200 { log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText)) } //初始化response结构体变量 var dictResponse DictResponse //将bodytext反序列化录入dictResponse结构体中 err = json.Unmarshal(bodyText, &dictResponse) if err != nil { log.Fatal(err) } fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs) for _, item := range dictResponse.Dictionary.Explanations { fmt.Println(item) } } func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, `usage: simpleDict WORD example: simpleDict hello `) os.Exit(1) } word := os.Args[1] query(word) }//os.Args是一个字符串切片,它包含了命令行参数。os.Args[0]是程序的路径,而os.Args[1:]包含了程序的命令行参数。
GO语言进阶
- '快'
- GO可以充分发挥多核优势,高效运行(在多个核心上的cpu运行)
1. Goroutine
1.1 协程与线程
- 协程,用户态,轻量级线程,栈KB级别
- 线程,内核态,线程中可以跑到多个协程,栈MB级别
func main() {
HelloGoRoutine()
}
func hello(i int) {
fmt.Println("hello goroutine " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) { //利用go关键字创建协程
//在第一次循环中,i的值为0,所以会创建一个新的协程来运行匿名函数,并将0作为参数传递给它。在这个协程中,匿名函数会调用hello函数,并将0作为参数传递给它。因此,第一个协程中调用的hello函数会接收到整数参数0。
hello(j)
}(i)
}
time.Sleep(time.Second) //子协程在完成之前主线程就退出
}
这段代码的输出结果是不确定的,因为它依赖于协程的调度顺序。在每次循环中,都会创建一个新的协程来运行匿名函数,并将当前循环计数器i的值作为参数传递给它。这样,在每个协程中调用的hello函数都会接收到不同的整数参数。
然而,协程的调度顺序是不确定的,所以每个协程中调用的hello函数可能会以任意顺序执行。因此,这段代码的输出结果可能是任意顺序的整数。
在你给出的输出结果中,第一个协程中调用的hello函数接收到了整数参数4,所以它输出了hello goroutine 4。第二个协程中调用的hello函数接收到了整数参数0,所以它输出了hello goroutine 0。其余协程也是类似的。
1.2 CSP
-
Communicating Sequential Processes
-
Go提倡通过通道共享内存而不是通过共享内存而实现通信
1.3 Channel
make(chan 元素类型,[缓冲大小【通道中能存放多少个元素】])- 无缓冲通道
make (chan int) - 有缓冲通道
make(chan int,2)
src := make(chan int)
dst := make(chan int, 3)
go func() {
defer close(src) //defer 延迟后面的操作,这个函数会在包含它的函数返回之前被调用。
for i := 0; i < 10; i++ { //将0-9存入src这个通道
src <- i
}
}()
go func() {
defer close(dst)
for i := range src { //遍历src中的元素,将src通道中的元素平方后送进dst
dst <- i * i
}
}()
for i := range dst { //输出平方
//复杂操作
println(i)
}
1.4 并发安全 Lock
-
类似于OS中学习过的信号量实现临界区资源互斥,有多个进程并发执行,对同一资源(x)的竞争,所以我们通过对x进行加锁(Lock)来保证临界区资源的安全
在并发编程中,多个协程可能会同时访问和修改同一个数据。如果不加以控制,这可能会导致数据不一致或其他错误。为了避免这种情况,我们需要使用锁来保证并发安全。 锁是一种同步机制,它可以用来保护临界区,确保同一时间只有一个协程能够访问临界区中的数据。当一个协程需要访问临界区时,它必须先获取锁。如果锁已经被其他协程占用,那么这个协程就会被阻塞,直到锁被释放。当协程完成对临界区的访问后,它必须释放锁,以便其他协程能够获取锁并访问临界区。 使用锁可以有效地避免多个协程同时修改同一个数据,从而保证数据的一致性和程序的正确性
1.5 WaitGroup
WaitGroup是Go语言中的一个结构体类型,它可以用来等待一组协程的完成。它提供了三个方法:Add、Done和Wait。
Add方法用于增加等待计数。当你启动一个新的协程时,你应该调用Add(1)来增加等待计数。Done方法用于减少等待计数。当一个协程完成时,它应该调用Done方法来减少等待计数。Wait方法用于阻塞当前协程,直到等待计数变为0。当你需要等待所有协程完成时,你应该调用Wait方法。
下面是一个简单的例子,展示了如何使用WaitGroup来等待一组协程的完成:
var wg sync.WaitGroup
func main() {
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(i)
}(i)
}
wg.Wait()
}
在这个例子中,我们创建了一个WaitGroup变量wg。在for循环中,每次循环都会调用wg.Add(1)来增加等待计数,并启动一个新的协程。在每个协程中,都会调用wg.Done方法来减少等待计数。在for循环结束后,主协程调用wg.Wait()方法来阻塞,直到所有协程都完成。
- 分析:
wg.Add(5)因为创建了5个进程,所以初始等待数为5- 每当一个
hello()函数执行完后,协程执行完毕,所以等待数-1 wg.Wait()等待所有子协程执行完毕,主进程才结束
GO依赖管理
-
GOPATH
-
GO VENDOR
GO MOD
依赖管理三要素
- 配置文件,描述依赖 -> go.mod
- 中心仓库管理依赖库 -> Proxy
- 本地工具 -> go get/mod
GO语言测试
- 回归测试:
- 集成测试:对功能维度进行测试
- 单元测试:测试开发阶段,开发者对某一功能进行测试
1. 单元测试
- 单元:函数,模块,...
- 优点:保证质量,提升效率
- 规则:
- 所有测试文件以
_test.go结尾 func TestXxx(*testing.T)- 初始化逻辑放到
TestMain方法中
- 所有测试文件以
- 覆盖率
- 1、
- 2