golang基础

一 ,基础语法

1.1 什么是go语言

  1. 高性能高并发
  2. 语法简单,学习曲线平缓
  3. 丰富的标准库
  4. 完整的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
    也有很强大的交叉编译特性,笔记本上编译的软件树莓派也可以运行
  8. 垃圾回收

image-20230724223505140

课前准备

安装 Go 语言

  1. 访问 https://go.dev/ ,点击 Download ,下载对应平台安装包,安装即可
  2. 如果无法访问上述网址,可以改为访问 https://studygolang.com/dl 下载安装
  3. 如果访问 github 速度比较慢,建议配置 go mod proxy,参考 https://goproxy.cn/ 里面的描述配置,下载第三方依赖包的速度可以大大加快

go run 运行

go build 编译和运行

函数上悬浮一秒钟可以得到在线文档的连接

基础语法

init函数和导包

他是在main函数执行之前执行用来初始化

image-20230727220545520


package main

import (
	"go/gostudy/init/lib1"
	"go/gostudy/init/lib2"
)

func main() {
	lib1.Lib1Test()
	lib2.Lib1Test()

}

导包的时候需要从根目录写起,或者是从gopath写起

还有对外提供的函数必须是大写,小写的函数只能在包内所使用

image-20230727222726769

导入包却不使用会自动给清除,所以可以匿名使用,使得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中的常量没有类型,它根据使用的上下文自动推断类型

image-20230727214628949

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([]类型,长度)

实际上是存储了一个长度+容量+一个指向数组的指针

image-20230727231318544

	//将s1的内容拷贝到s2,深拷贝
	s2 := make([]int, 3)
	copy(s2, s1)

image-20230728002738820

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
}

变量类型是后置的,原生支持返回多个值

指针

image-20230727223853765

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
}

image-20230728130041840

结构体实现继承需要在子结构体里添加父类的结构体,但好像,并不能通过这种方式进行多态,多态只能通过接口

结构体方法

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是是否为某个类型

image-20230728132448824

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
}

image-20230728172822039

r=Book类型,BOOK实现了Reader和Writer,所以r能断言Witter成功,这里是有点类似实现了多个接口,指向他的父类型是哪个就可以使用哪个父接口定义的方法

image-20230728174741871

但是只有接口才能断言

image-20230728125829842

反射

变量的结构

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(),

说明

  1. reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型
  2. reflect.ValueOf:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 “Allen.Wu” 25} 这样的结构体struct的值
  3. 也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是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

image-20230728214727467

反射解析结构体标签

主要用来结构体说明

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
}

协程

image-20230729000620936

image-20230729000651453

cpu本身可以保存状态,且不需要切换

image-20230729002759930

实战

猜数游戏

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 类似,用于处理字符串的前缀和后缀。以下是其中几个常用的函数:

  1. strings.TrimPrefix(s, prefix string) string: 去除字符串开头的指定前缀,返回新的字符串。如果原始字符串以指定前缀开头,则去除该前缀;否则,原始字符串保持不变。
  2. strings.HasPrefix(s, prefix string) bool: 检查字符串是否以指定前缀开头,返回一个布尔值。如果字符串以指定前缀开头,返回 true,否则返回 false
  3. strings.HasSuffix(s, suffix string) bool: 检查字符串是否以指定后缀结尾,返回一个布尔值。如果字符串以指定后缀结尾,返回 true,否则返回 false
  4. strings.Trim(s string, cutset string) string: 去除字符串开头和结尾的指定字符集合(cutset)中包含的字符,返回新的字符串。如果 cutset 为空,则会去除字符串开头和结尾的空白字符。
  5. 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 作为参数,并对这个连接进行读取和写入操作。让我们逐步解析这个函数:

  1. func process(conn net.Conn) { 定义了一个名为 process 的函数,它接受一个 net.Conn 类型的参数 conn,该参数代表网络连接。
  2. defer conn.Close() 使用 defer 关键字延迟执行 conn.Close(),确保在函数执行完毕后关闭网络连接。defer 关键字会使 conn.Close()process 函数的末尾执行,无论函数是正常返回还是因为错误而退出,都会执行该语句来关闭连接。
  3. reader := bufio.NewReader() 创建一个 bufio.Reader 类型的对象 reader,用于从网络连接中读取数据。但在这里,创建 bufio.Reader 时没有传递参数,因此这个 reader 并未与指定的网络连接 conn 关联,应该修改为 reader := bufio.NewReader(conn)
  4. for { 进入一个无限循环,不断从 reader 中读取字节并将其写入连接 conn
  5. b, err := reader.ReadByte() 使用 ReadByte() 方法从 reader 中读取一个字节。ReadByte() 返回两个值,一个是读取的字节 b,另一个是可能出现的错误 err
  6. if err != nil { break } 检查读取操作是否发生错误,如果出现错误,退出循环,跳出 for 循环。
  7. _, err = conn.Write([]byte{b}) 将刚刚读取的字节 b 通过 conn.Write() 方法写入连接 connWrite() 方法返回两个值,一个是写入的字节数,另一个是可能出现的错误 err
  8. if err != nil { break } 检查写入操作是否发生错误,如果出现错误,退出循环,跳出 for 循环。
  9. } 循环会持续进行,不断读取字节并将其写入连接,直到发生读取或写入错误,导致循环终止。

总体来说,这个函数的目的是将从网络连接中读取的数据逐字节原样写回同一个连接。它将连接作为参数传递,然后在一个无限循环中读取连接的内容,并将每个字节再写回该连接。当读取或写入操作发生错误时,函数会关闭连接并终止。注意在修改 bufio.NewReader()bufio.NewReader(conn) 之后,这个函数将能够正常地工作。

在 Go 语言中,defer 关键字用于延迟(defer)函数的执行,使函数在当前函数执行完成后才会被调用。无论函数是正常返回还是遇到错误导致异常退出,defer 关键字都会确保它指定的函数会被执行,类似于其他编程语言中的 “finally” 块。

defer 的作用如下:

  1. 延迟函数的执行:使用 defer 关键字可以将函数调用推迟到包含 defer 语句的函数返回之后执行。这在需要确保某些资源在函数执行完毕后得到释放时非常有用,例如关闭文件句柄、释放锁、关闭数据库连接等。
  2. 逆序执行:如果一个函数中有多个 defer 语句,它们会按照后进先出(LIFO)的顺序执行,即最后一个 defer 语句会最先执行,而第一个 defer 语句会最后执行。
  3. 避免忘记资源释放:使用 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代理

image-20230725230419479

二 go进阶

1. 并发vs并行

image-20230726131401973

线程其实它是比较系统里面比较昂贵的系统资源,然后它属于内核态,然后它的创建切换停止都属于很重的系统操作

go语言一次可创建上上万左右的协程

1.2 协程之间通信

image-20230726142755654

提倡通过通信共享内存而不是通过共享内存而通信

1.3 Channel

image-20230726143126761

缓冲通道缓冲缓冲通道,它们的一个区别是什么呢?我们可以看下面的一个图,第一个是无缓冲通道的。

然后其实使用无法中断的一个贡献值,它会导致发送的路径和接收的路径是同步的然后其实使用无法中断的一个贡献值,它会导致发送的路径和接收的路径是同步的

image-20230729005255774

只要数据一直不被拿走也会一直被阻塞

image-20230729011240295

image-20230726145322431

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()
}

image-20230729004019222

如果管道的接收方的那个gorouting 没有接收到信息就会阻塞,当chan 中传来数据时就会发送唤醒信号进行唤醒,不读也会发生阻塞

从而实现了gorouting之间的通信

生产者的通道是无缓冲,因为生产者较为简单速度较快,消费者通道设置缓冲可以避免消费者消费速度问题影响生产(我不需要等你消费完再生产了)

在Go语言中,()表示立即执行一个函数。这被称为函数调用表达式。在这种表达式中,函数会被立即执行,并且表达式的结果是该函数的返回值(如果有的话)。

go func() {…}()

在你提供的代码片段中,go func() {...}()是一个函数调用表达式。让我们逐步解释它:

  1. go 关键字:表示要创建一个新的Go协程来执行函数体中的代码,即异步地执行该函数。
  2. func() {...}:这是一个匿名函数(没有函数名),也称为闭包。在这里,我们定义了一个匿名函数,它接收一个参数,并在函数体内进行操作。
  3. ():这是函数调用操作符,表示要立即调用前面定义的匿名函数。

为什么我们要在匿名函数后面加上()呢?因为我们想立即执行这个匿名函数。在这个例子中,go func() {...}()会创建一个新的Go协程并立即执行匿名函数的代码。这样,该匿名函数就会在一个单独的Go协程中运行,并且不会阻塞当前的执行流程,即它会在后台异步地执行。

总结:在Go语言中,通过在匿名函数后面加上(),我们可以立即执行这个匿名函数。通过在匿名函数前面加上go关键字,我们可以在一个新的Go协程中异步地执行这个函数,而不会阻塞当前的执行流程。这样可以实现并发执行,提高程序的并发性能。

<- 在go中表示什么

ChatGPT

在Go语言中,<-是通道操作符,用于发送和接收数据到/从通道。它的意义取决于它是用于通道的发送操作还是接收操作。

  1. 发送操作
    • chan <- value的形式中,<-用于将value发送到通道chan
    • 例如:ch <- 42 表示将整数值 42 发送到通道 ch 中。
  2. 接收操作
    • value := <-chan的形式中,<-用于从通道chan接收数据,并将接收到的值赋给value变量。
    • 例如:x := <-ch 表示从通道ch接收数据,并将其赋值给变量x

除了用于通道的发送和接收操作外,<-还可以用于关闭通道。

  1. 关闭通道

    • 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关键字来声明了两个变量,xlock

  1. x
    • x是一个64位有符号整数(int64)类型的变量。
    • 在这里,变量x被声明为全局变量(在函数体外部声明的变量称为全局变量),并且由于没有初始化值,Go语言会将其初始化为int64类型的零值,即0
    • 作为全局变量,x的作用域将是整个包(文件)范围内,可以在包中的任何函数中访问和修改它的值。
  2. lock
    • lock是一个互斥锁(sync.Mutex)类型的变量。
    • 互斥锁是用于多个Go协程之间的同步的一种机制。当一个协程获得了互斥锁后,其他协程将被阻塞在该锁上,直到持有锁的协程释放它。
    • 在这里,lock被声明为全局变量,因此可以在整个包范围内共享,并在不同的函数中使用。
    • 互斥锁一般用于保护共享资源的并发访问,确保在任意时刻只有一个协程能够访问或修改共享资源,从而避免竞态条件和数据竞争问题。

总结:这段代码声明了一个全局的int64类型变量x和一个全局的互斥锁lock,用于在包内的多个函数中共享和保护资源。int64类型的变量x被初始化为零值,而互斥锁lock默认为零值,表示未加锁状态。

1.5 WaitGroup

image-20230726151654882

image-20230726151858460

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. 依赖管理

背景

image-20230726154316466

2.1 依赖管理演进

image-20230726154428195

2.1.1 GOPath

image-20230726154605206

image-20230726154634531

2.1.2 GO Vendor
  • 项目目录下曾江vender文件 ,所有依赖包副本形式放在$ProjectRoot/vender
  • 依赖寻址方式: vendor => GOPATH

image-20230726154943478

image-20230726155134922

2.1.3 GO Mode
  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包

image-20230726155306705

2.2 配置管理三要素

1.配置文件,描述依赖 go.mod
2.中心仓库管理依赖库 Proxy
3.本地工具 go get/ mod

2.3

2.3.1 依赖管理配置 - go mod

image-20230726155623844

image-20230729112346233

2.3.2 依赖配置 -version

image-20230726160134732

语义化版本第一个是大版本,之间是可以不兼容的,第二个是小版本,在兼容的情况下实现更新,第三个是修复bug

2.3.3依赖配置-indirect

image-20230726160623636

2.3.4 依赖配置 -incompatible

image-20230726161239592

2.3.5 版本依赖 依赖图

他会选择一个最低的兼容版本

image-20230726161603211

2.3.6 依赖分发 - 回源

image-20230726162221251

缺点

  • 无法保证构建的稳定性
    增加/修改/删除软件版本

  • 无法保证依赖的可用性
    删除软件

  • 增加第三方压力
    代码托管平台负载问题

2.3.7依赖分发 - proxy

image-20230726162343187

image-20230726162555158

工具 goget

image-20230726163049471

工具 gomode

image-20230726163203083

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值