一 ,基础语法
1.1 什么是go语言
- 高性能高并发
- 语法简单,学习曲线平缓
- 丰富的标准库
- 完整的工具链
- 静态链接
- 快速编译
- 跨平台
也有很强大的交叉编译特性,笔记本上编译的软件树莓派也可以运行 - 垃圾回收
课前准备
安装 Go 语言
- 访问 https://go.dev/ ,点击 Download ,下载对应平台安装包,安装即可
- 如果无法访问上述网址,可以改为访问 https://studygolang.com/dl 下载安装
- 如果访问 github 速度比较慢,建议配置 go mod proxy,参考 https://goproxy.cn/ 里面的描述配置,下载第三方依赖包的速度可以大大加快
go run 运行
go build 编译和运行
函数上悬浮一秒钟可以得到在线文档的连接
基础语法
init函数和导包
他是在main函数执行之前执行用来初始化
package main
import (
"go/gostudy/init/lib1"
"go/gostudy/init/lib2"
)
func main() {
lib1.Lib1Test()
lib2.Lib1Test()
}
导包的时候需要从根目录写起,或者是从gopath写起
还有对外提供的函数必须是大写,小写的函数只能在包内所使用
导入包却不使用会自动给清除,所以可以匿名使用,使得init函数能正常调用
方法是包前面加个_
起别名
包前面加上别名即可
变量和常量
使用var 或者:=定义变量
使用const 定义常量
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
//多行进行变量声明
var (
vv int = 100
ss boool = ture
)
f := float32(e)
//只能用在函数体内
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
//声明常量(只读)
const s string = "constant"
const h = 500000000
const i = 3e20 / h
//使用常量来定义枚举,第一个是0 然后依次累加,iota只能配合const来使用
const (
BEIJING = iota
SHANGHAI
SHENZHEN
)
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
golang中的常量没有类型,它根据使用的上下文自动推断类型
if else
package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
if中没有小括号
数组
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
package main
import "fmt"
func main() {
var list [10]int
for i := 0; i < len(list); i++ {
list[i] = i + 5
}
for _, value := range list {
fmt.Println(value)
}
}
变量数组可以使用 range
如果不想报定义的变量未使用,可以使用_来接收
切片
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
使用make([]类型,长度)
实际上是存储了一个长度+容量+一个指向数组的指针
//将s1的内容拷贝到s2,深拷贝
s2 := make([]int, 3)
copy(s2, s1)
package main
import "fmt"
func main() {
s1 := make([]int, 3, 5)
fmt.Printf("len= %d,cap=%d", len(s1), cap(s1))
}
map
package main
import "fmt"
func main() {
//实现定好容量
m1:=make(map[string]int,3)
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
//遍历
for key , value := range cityMap {
fmt.Println("key = ", key)fmt.Println("value = ", value)
}
//删除
delete(cityMap,"China")
//修改
cityMap ["USA"] = "DC"
}
语法是make(map[key的类型]value的类型)
获取map的值时在后面加上ok判别map中是否有这个key存在
golang中的map是完全无序的
cap表示之后每次扩容的距离
range
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
}
range 可以遍历数组和map 遍历数组时返回的两个值是索引和数组中的值,遍历map的时候两个值是key和value
函数
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
//返回多个值有形参名称的,这样只需要给这形参名称赋值,最后只return 空就行
func fool(a string, b int) (c int, d string, f string) {
fmt.Println("a= ", a)
fmt.Println("b= ", b)
c = b
d = a
f = "hello"
return
}
func add2(a, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
变量类型是后置的,原生支持返回多个值
指针
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) { //传入一个地址类型的值
*n += 2 //获取地址里面的内容
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n) //这里取地址
fmt.Println(n) // 7
}
结构体
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
func checkPassword(u user, password string) bool {
return u.password == password
}
func checkPassword2(u *user, password string) bool {
return u.password == password
}
结构体实现继承需要在子结构体里添加父类的结构体,但好像,并不能通过这种方式进行多态,多态只能通过接口
结构体方法
package main
import "fmt"
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
第一个不带指针,是结构体的副本,第二个带指针 ,和传统方法相比应该是这款把结构体放在了func后面
如果结构体里面属性名称首字母小写,则只能在结构体内部使用相当于私有属性,大写则可以外部调用
结构体方法大写则别的包也可以使用,不然只能在本包里面使用
接口
package main
import "fmt"
//接口
type Animal interface {
Sleep()
GetColor() string
GetType() string
}
//
type Cat struct {
color string
}
type Dog struct {
color string
}
func (this *Cat) Sleep() {
fmt.Println("cat is sleep")
}
func (this *Cat) GetColor() string {
return this.color
}
func (this *Cat) GetType() string {
return "Cat"
}
func (this *Dog) Sleep() {
fmt.Println("dog is sleep")
}
func (this *Dog) GetColor() string {
return this.color
}
func (this *Dog) GetType() string {
return "Dog"
}
func showAnimal(animal Animal) {
animal.Sleep()
}
func main() {
cat := &Cat{"Green"}
dog := &Dog{"BLACK"}
showAnimal(cat)
showAnimal(dog)
}
实现接口只需要结构体方法和接口里面定义的方法一样即可,然后接口本身是一个指针类型,所以要复制给他的是结构体实例的引用
空接口和断言
interface{}
- 空接口
- int,string,float32,float64,struct 都实现了interface{}
- 就可以用interface{} 类型引用任意的数据类型
这里value是值,ok是是否为某个类型
package main
import "fmt"
func myFun(arg interface{}) {
fmt.Println(arg)
fmt.Printf("%T\n", arg)
value, ok := arg.(int)
if !ok {
fmt.Println("is not string", value)
fmt.Printf("type is %T", value)
} else {
fmt.Println("arg is ", value)
fmt.Printf("type is %T", value)
}
}
func main() {
myFun(11)
}
Golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。
//这种使用方式会出问题,但是可以直接使用a,或者断言
func funcName(a interface{}) string {
return string(a)
}
配合switch使用
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
r=Book类型,BOOK实现了Reader和Writer,所以r能断言Witter成功,这里是有点类似实现了多个接口,指向他的父类型是哪个就可以使用哪个父接口定义的方法
但是只有接口才能断言
反射
变量的结构
var a string
//pair<statictype:string,value:"aceld">
a = "aceld"
//pair<type:string,value:"aceld">
var allType interface{}
allType = a
r=Book类型,BOOK实现了Reader和Writer,所以r能断言Witter成功
编程语言中反射的概念
在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。
多插一句,Golang的gRPC也是通过反射实现的。
interface 和 反射
在讲反射之前,先来看看Golang关于类型设计的一些原则
●变量包括(type, value)两部分
●type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型
●类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.
接下来要讲的反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:
Golang的反射reflect
reflect的基本功能TypeOf和ValueOf
既然反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。那么在Golang的reflect反射包中有什么样的方式可以让我们直接获取到变量内部的信息呢? 它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf() 和 reflect.TypeOf(),
说明
- reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型
- reflect.ValueOf:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 “Allen.Wu” 25} 这样的结构体struct的值
- 也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是reflect.Type和reflect.Value这两种
从relfect.Value中获取接口interface的信息
当执行reflect.ValueOf(interface)之后,就得到了一个类型为”relfect.Value”变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,我们可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明。
已知原有类型【进行“强制转换”】
已知类型后转换为其对应的类型的做法如下,直接通过Interface方法然后强制转换,如下:****
realValue := value.Interface().(已知的类型)
package main
import (
“fmt”
“reflect”
)
func main() {
var num float64 = 1.2345
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)
// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
// Golang 对类型要求非常严格,类型一定要完全符合
// 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
convertPointer := pointer.Interface().(*float64)
convertValue := value.Interface().(float64)
fmt.Println(convertPointer)
fmt.Println(convertValue)
}
运行结果:
0xc42000e238
1.2345
反射解析结构体标签
主要用来结构体说明
package main
import (
"fmt"
"reflect"
)
type resume struct {
Name string `json:"name" doc:"我的名字"`
}
func findDoc(stru interface{}) map[string]string {
t := reflect.TypeOf(stru).Elem()
doc := make(map[string]string)
for i := 0; i < t.NumField(); i++ {
doc[t.Field(i).Tag.Get("json")] = t.Field(i).Tag.Get("doc")
}
return doc
}
func main() {
var stru resume
doc := findDoc(&stru)
fmt.Printf("name字段为:%s\n", doc["name"])
}
error
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
string
package main
import (
"fmt"
"strings"
)
func main() {
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
}
格式化
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
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
}
json
package main
import (
"encoding/json"
"fmt"
)
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)
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"}}
}
json.MarshalIndent作用
ChatGPT
json.MarshalIndent
是 Go 语言标准库中用于将数据结构序列化为 JSON 格式并添加缩进的函数。它将 Go 的数据结构转换为 JSON 字节切片,并根据指定的缩进格式对 JSON 进行格式化,使 JSON 数据在显示时更易读。
函数签名:
goCopy code
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
v
: 要序列化为 JSON 的数据结构,通常为一个 Go 的数据类型,例如 struct、map、slice 等。prefix
: 缩进前缀,用于在每一行缩进级别之前添加的前缀字符串。一般设置为空格或制表符来实现缩进效果。indent
: 每一行缩进的字符串,通常由空格或制表符组成。
json.MarshalIndent
会根据 v
中的数据结构生成一个带有缩进格式的 JSON 字节切片。
示例:
goCopy codepackage main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string
Age int
Country string
}
func main() {
person := Person{
Name: "John",
Age: 30,
Country: "USA",
}
// 使用 json.MarshalIndent 将结构体序列化为带有缩进格式的 JSON
jsonData, err := json.MarshalIndent(person, "", " ")
if err != nil {
fmt.Println("Error marshaling JSON:", err)
return
}
fmt.Println(string(jsonData))
}
在上面的例子中,我们定义了一个 Person
结构体,然后使用 json.MarshalIndent
将其序列化为带有缩进格式的 JSON。输出结果将是类似于下面的内容:
jsonCopy code{
"Name": "John",
"Age": 30,
"Country": "USA"
}
这样的格式化使得生成的 JSON 数据更具可读性,并且更容易在调试和查看 JSON 数据时进行理解。
time
package main
import (
"fmt"
"time"
)
func main() {
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
}
字符串转换
package main
import (
"fmt"
"strconv"
)
func main() {
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")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
env
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// 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
}
协程
cpu本身可以保存状态,且不需要切换
实战
猜数游戏
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("the secret is", secretNumber)
fmt.Println("please input your guess")
//把输入文件转成一个只读的流
reader := bufio.NewReader(os.Stdin)
//读取用户输入,直到遇到换行符为止
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("an error occured while reading input , Please try again", err)
continue
}
input = strings.TrimSuffix(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invaild input", err)
continue
}
fmt.Println("Your gusee is", guess)
if guess > secretNumber {
fmt.Println("your input is bugger ")
} else if guess < secretNumber {
fmt.Println("your input is smaller")
} else {
fmt.Println("correct")
break
}
}
}
是的,Go标准库中还有一些与字符串处理相关的函数,其中一些与 strings.TrimSuffix
类似,用于处理字符串的前缀和后缀。以下是其中几个常用的函数:
strings.TrimPrefix(s, prefix string) string
: 去除字符串开头的指定前缀,返回新的字符串。如果原始字符串以指定前缀开头,则去除该前缀;否则,原始字符串保持不变。strings.HasPrefix(s, prefix string) bool
: 检查字符串是否以指定前缀开头,返回一个布尔值。如果字符串以指定前缀开头,返回true
,否则返回false
。strings.HasSuffix(s, suffix string) bool
: 检查字符串是否以指定后缀结尾,返回一个布尔值。如果字符串以指定后缀结尾,返回true
,否则返回false
。strings.Trim(s string, cutset string) string
: 去除字符串开头和结尾的指定字符集合(cutset)中包含的字符,返回新的字符串。如果 cutset 为空,则会去除字符串开头和结尾的空白字符。strings.TrimSpace(s string) string
: 去除字符串开头和结尾的空白字符,返回新的字符串。
这些函数在处理字符串时非常有用,特别是在需要对字符串进行预处理或清理时。使用这些函数可以使字符串处理代码更加简洁和易读。记住,在处理字符串时,要注意处理 Unicode 字符和特殊情况,以确保代码的正确性和鲁棒性。
网络请求
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"io"
"log"
"net/http"
)
type AutoGenerated struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Entry string `json:"entry"`
Explanations []string `json:"explanations"`
Related []interface{} `json:"related"`
Source string `json:"source"`
Prons struct {
} `json:"prons"`
Type string `json:"type"`
} `json:"dictionary"`
}
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserId string `json:"user_id"`
}
func main() {
if len(os.Args) != 2 {
fmt.Fprint(os.Stderr, `usage: simpleDict WORD
example:simpleDict hellp`)
os.Exit(1)
}
word := os.Args[1]
query(word)
}
func query(word string) {
client := &http.Client{}
// var data = strings.NewReader(`{"trans_type":"zh2en","source":"你好"}`)
request := DictRequest{TransType: "zh2en", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
//因为json.Marshal返回的是一个byte数组
var data = bytes.NewReader(buf)
//因为这个参数可能是从文件读取的可能很大很大,所以需要是流
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 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", "9395fef18b8c40f79517da2f52a54705")
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")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
//关闭流不然会泄露,
defer resp.Body.Close()
//得到的bodytext是字节数组
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 {
log.Fatal("bad status", resp.StatusCode, "body", string(bodyText))
}
//字符串输出
// fmt.Printf("%s\n", bodyText)
var dicresponce AutoGenerated
err = json.Unmarshal(bodyText, &dicresponce)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", dicresponce)
}
这是一个简单的函数 process
,它接受一个网络连接 conn
作为参数,并对这个连接进行读取和写入操作。让我们逐步解析这个函数:
func process(conn net.Conn) {
定义了一个名为process
的函数,它接受一个net.Conn
类型的参数conn
,该参数代表网络连接。defer conn.Close()
使用defer
关键字延迟执行conn.Close()
,确保在函数执行完毕后关闭网络连接。defer
关键字会使conn.Close()
在process
函数的末尾执行,无论函数是正常返回还是因为错误而退出,都会执行该语句来关闭连接。reader := bufio.NewReader()
创建一个bufio.Reader
类型的对象reader
,用于从网络连接中读取数据。但在这里,创建bufio.Reader
时没有传递参数,因此这个reader
并未与指定的网络连接conn
关联,应该修改为reader := bufio.NewReader(conn)
。for {
进入一个无限循环,不断从reader
中读取字节并将其写入连接conn
。b, err := reader.ReadByte()
使用ReadByte()
方法从reader
中读取一个字节。ReadByte()
返回两个值,一个是读取的字节b
,另一个是可能出现的错误err
。if err != nil { break }
检查读取操作是否发生错误,如果出现错误,退出循环,跳出for
循环。_, err = conn.Write([]byte{b})
将刚刚读取的字节b
通过conn.Write()
方法写入连接conn
。Write()
方法返回两个值,一个是写入的字节数,另一个是可能出现的错误err
。if err != nil { break }
检查写入操作是否发生错误,如果出现错误,退出循环,跳出for
循环。}
循环会持续进行,不断读取字节并将其写入连接,直到发生读取或写入错误,导致循环终止。
总体来说,这个函数的目的是将从网络连接中读取的数据逐字节原样写回同一个连接。它将连接作为参数传递,然后在一个无限循环中读取连接的内容,并将每个字节再写回该连接。当读取或写入操作发生错误时,函数会关闭连接并终止。注意在修改 bufio.NewReader()
为 bufio.NewReader(conn)
之后,这个函数将能够正常地工作。
在 Go 语言中,defer
关键字用于延迟(defer)函数的执行,使函数在当前函数执行完成后才会被调用。无论函数是正常返回还是遇到错误导致异常退出,defer
关键字都会确保它指定的函数会被执行,类似于其他编程语言中的 “finally” 块。
defer
的作用如下:
- 延迟函数的执行:使用
defer
关键字可以将函数调用推迟到包含defer
语句的函数返回之后执行。这在需要确保某些资源在函数执行完毕后得到释放时非常有用,例如关闭文件句柄、释放锁、关闭数据库连接等。 - 逆序执行:如果一个函数中有多个
defer
语句,它们会按照后进先出(LIFO)的顺序执行,即最后一个defer
语句会最先执行,而第一个defer
语句会最后执行。 - 避免忘记资源释放:使用
defer
可以帮助避免因为遗漏释放资源而导致的内存泄漏或其他资源泄漏问题。由于defer
会在函数退出时执行,因此可以确保在函数执行完毕后及时释放资源。
示例:
goCopy codepackage main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // 在函数返回时关闭文件
// 使用文件进行一些操作,比如读取数据等
// 函数执行完毕时,defer语句会执行,文件会被关闭
}
在上面的例子中,通过 defer file.Close()
,无论函数是否正常返回,file.Close()
都会在函数返回时被调用,确保文件句柄被及时关闭。
总结:defer
是 Go 语言中一种方便的机制,用于处理资源释放、清理等操作,使得代码更加清晰、健壮,同时避免了忘记释放资源的问题。
io.ReadAl
io.ReadAll
是 Go 语言标准库中的一个函数,它的作用是从输入流(io.Reader)中读取所有数据并返回一个字节切片([]byte)表示读取的内容。
函数签名:
goCopy code
func ReadAll(r io.Reader) ([]byte, error)
参数 r
是一个实现了 io.Reader
接口的对象,可以是文件、网络连接、内存缓冲区等等。io.Reader
接口包含了读取数据的基本方法。
io.ReadAll
会读取 r
中的所有数据,直到遇到文件结束符(EOF)为止,并将读取的内容存储在一个字节切片中返回。同时,它也会返回可能出现的错误(如果有的话)。
使用 io.ReadAll
的常见场景是读取小文件或读取来自网络连接等的数据。请注意,对于非常大的数据流,io.ReadAll
可能不是最佳的选择,因为它会将整个数据流读取到内存中,可能导致内存消耗较大。在处理大型数据流时,最好使用逐块读取的方式。
示例:
goCopy codepackage main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println("File contents:")
fmt.Println(string(data))
}
在上面的例子中,我们打开一个名为 “example.txt” 的文件,并使用 io.ReadAll
将文件的所有内容读取到 data
变量中,最后打印出文件的内容。
socket5代理
二 go进阶
1. 并发vs并行
线程其实它是比较系统里面比较昂贵的系统资源,然后它属于内核态,然后它的创建切换停止都属于很重的系统操作
go语言一次可创建上上万左右的协程
1.2 协程之间通信
提倡通过通信共享内存而不是通过共享内存而通信
1.3 Channel
缓冲通道缓冲缓冲通道,它们的一个区别是什么呢?我们可以看下面的一个图,第一个是无缓冲通道的。
然后其实使用无法中断的一个贡献值,它会导致发送的路径和接收的路径是同步的然后其实使用无法中断的一个贡献值,它会导致发送的路径和接收的路径是同步的
只要数据一直不被拿走也会一直被阻塞
package main
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
//可以迭代不断操作channel
for i := range dest {
println(i)
}
}
func main() {
CalSquare()
}
如果管道的接收方的那个gorouting 没有接收到信息就会阻塞,当chan 中传来数据时就会发送唤醒信号进行唤醒,不读也会发生阻塞
从而实现了gorouting之间的通信
生产者的通道是无缓冲,因为生产者较为简单速度较快,消费者通道设置缓冲可以避免消费者消费速度问题影响生产(我不需要等你消费完再生产了)
在Go语言中,()
表示立即执行一个函数。这被称为函数调用表达式。在这种表达式中,函数会被立即执行,并且表达式的结果是该函数的返回值(如果有的话)。
go func() {…}()
在你提供的代码片段中,go func() {...}()
是一个函数调用表达式。让我们逐步解释它:
go
关键字:表示要创建一个新的Go协程来执行函数体中的代码,即异步地执行该函数。func() {...}
:这是一个匿名函数(没有函数名),也称为闭包。在这里,我们定义了一个匿名函数,它接收一个参数,并在函数体内进行操作。()
:这是函数调用操作符,表示要立即调用前面定义的匿名函数。
为什么我们要在匿名函数后面加上()
呢?因为我们想立即执行这个匿名函数。在这个例子中,go func() {...}()
会创建一个新的Go协程并立即执行匿名函数的代码。这样,该匿名函数就会在一个单独的Go协程中运行,并且不会阻塞当前的执行流程,即它会在后台异步地执行。
总结:在Go语言中,通过在匿名函数后面加上()
,我们可以立即执行这个匿名函数。通过在匿名函数前面加上go
关键字,我们可以在一个新的Go协程中异步地执行这个函数,而不会阻塞当前的执行流程。这样可以实现并发执行,提高程序的并发性能。
<- 在go中表示什么
ChatGPT
在Go语言中,<-
是通道操作符,用于发送和接收数据到/从通道。它的意义取决于它是用于通道的发送操作还是接收操作。
- 发送操作:
- 在
chan <- value
的形式中,<-
用于将value
发送到通道chan
。 - 例如:
ch <- 42
表示将整数值42
发送到通道ch
中。
- 在
- 接收操作:
- 在
value := <-chan
的形式中,<-
用于从通道chan
接收数据,并将接收到的值赋给value
变量。 - 例如:
x := <-ch
表示从通道ch
接收数据,并将其赋值给变量x
。
- 在
除了用于通道的发送和接收操作外,<-
还可以用于关闭通道。
-
关闭通道
:
- 在
close(chan)
的形式中,close
是一个内置函数,用于关闭通道。 - 例如:
close(ch)
表示关闭通道ch
。
- 在
需要注意的是,在接收操作中,<-
的左边是变量名,右边是通道名。而在发送操作中,<-
的左边是通道名,右边是要发送的值。这个语法设计是为了与C语言中的指针操作类似,并且便于理解通道的数据流向。
总结:在Go语言中,<-
是通道操作符,用于发送和接收数据到/从通道。它可以表示通道的发送操作、接收操作,以及关闭通道。
channel与select
单流程下go只能监控一个channel的状态,select可以完成监控多个channel的状态
package main
import "fmt"
func fib(c, quit chan int) {
x, y := 1, 1
for {
select {
//如果c可写
case c <- x:
x = y
y = x + y
//如果quit可读
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fib(c, quit)
}
1.4 并发安全 Lock
var ( x int64 lock sync.Mutex ) 解释
ChatGPT
这段代码使用Go语言中的var
关键字来声明了两个变量,x
和lock
。
- x:
x
是一个64位有符号整数(int64)类型的变量。- 在这里,变量
x
被声明为全局变量(在函数体外部声明的变量称为全局变量),并且由于没有初始化值,Go语言会将其初始化为int64
类型的零值,即0
。 - 作为全局变量,
x
的作用域将是整个包(文件)范围内,可以在包中的任何函数中访问和修改它的值。
- lock:
lock
是一个互斥锁(sync.Mutex
)类型的变量。- 互斥锁是用于多个Go协程之间的同步的一种机制。当一个协程获得了互斥锁后,其他协程将被阻塞在该锁上,直到持有锁的协程释放它。
- 在这里,
lock
被声明为全局变量,因此可以在整个包范围内共享,并在不同的函数中使用。 - 互斥锁一般用于保护共享资源的并发访问,确保在任意时刻只有一个协程能够访问或修改共享资源,从而避免竞态条件和数据竞争问题。
总结:这段代码声明了一个全局的int64
类型变量x
和一个全局的互斥锁lock
,用于在包内的多个函数中共享和保护资源。int64
类型的变量x
被初始化为零值,而互斥锁lock
默认为零值,表示未加锁状态。
1.5 WaitGroup
package main
import "fmt"
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
func hello(i int) {
println("hello gorouting" + fmt.Sprint(i))
}
func main() {
CalSquare()
}
2. 依赖管理
背景
2.1 依赖管理演进
2.1.1 GOPath
2.1.2 GO Vendor
- 项目目录下曾江vender文件 ,所有依赖包副本形式放在$ProjectRoot/vender
- 依赖寻址方式: vendor => GOPATH
2.1.3 GO Mode
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
2.2 配置管理三要素
1.配置文件,描述依赖 go.mod
2.中心仓库管理依赖库 Proxy
3.本地工具 go get/ mod
2.3
2.3.1 依赖管理配置 - go mod
2.3.2 依赖配置 -version
语义化版本第一个是大版本,之间是可以不兼容的,第二个是小版本,在兼容的情况下实现更新,第三个是修复bug
2.3.3依赖配置-indirect
2.3.4 依赖配置 -incompatible
2.3.5 版本依赖 依赖图
他会选择一个最低的兼容版本
2.3.6 依赖分发 - 回源
缺点
-
无法保证构建的稳定性
增加/修改/删除软件版本 -
无法保证依赖的可用性
删除软件 -
增加第三方压力
代码托管平台负载问题