golang学习笔记

概述

类型检查:编译时
运行环境:编译成机器代码直接运行
编程范式:面向接口,函数式编程,并发编程

Go语言并发编程
采用CSP(Communication Sequential Process)模式
不需要锁,不需要callback
并发编程vs并行计算

变量定义

使用var定义变量

  • var a,b,c, bool
  • var s1,s2 string = “hello”, “world”
  • 可放在函数内,或直接放在包内
  • 使用var()集中定义变量
  • 让编译器自动决定类型
    var a, b, i, s1, s2 = true, false, 3, “hello”, “world”

使用:=定义变量

  • a, b, i, s1, s2 := true, false, 3, “hello”, “world”
  • 只能在函数内使用

内置变量类型:

  • bool, string;
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr;
  • byte, rune
  • float32, float64, complex64, complex128.

其中(u)int的位数由操作系统决定;
uintptr是指针;
byte和rune是int的别名,byte是8位rune是32位;
complex64的实部和虚部都是float32,complex128的实部和虚部都是float64。
go没有隐式类型转换,都要做显示的强制转换。

常量

定义:
const filename = “abc.txt”
const数值可作为各种类型使用(无需强制类型转换)
const a,b = 3,4
var c int = int(math.Sqrt(a * a + b * b))

枚举

const组用作枚举
iota关键字用作初值,后续枚举值自动递增,使用_可以占一个数,如下例中,a b c d值分别为0 2 3 4

const(
    a = iota
    _
    b
    c
    d

也可以使用公式,如下:

const (
    b = 1 << (10 * iota)
    kb
    mb
    gb
    tb
    pb
)

要点

  • 变量类型写在变量名之后
  • 编译器可推测变量类型
  • 没有char,只有rune
  • 原生支持复数类型

条件语句

  • if条件里可以赋
  • if条件里赋值的变量作用域为if这个语句块
  • switch语句可以没有表达式,在case中给出条件就行
  • switch语句不需要break,默认自动break,不break的时候使用fallthrough;
  • panic相当于报错,进入条件后调用panic,程序中断运行
func grade(score int) string {
    g := ""
    switch {
    case score < 0 || score >100:
        panic(fmt.Sprintf("wrong score: %d", score))
    case sore < 60:
        g = "F"
    case sore < 70:
        g = "C" 
    case sore < 90:
        g = "B" 
    case sore < 100:
        g = "A"  
    }
    return g
}

循环语句

for的条件里不需要括号
for的条件里可以省略初始条件,结束条件,递增表达式
go语言没有while

函数

函数返回值类型放在后面
函数可以作为参数传递
没有默认参数,可选参数,有变长参数
函数可以返回多个值
可以为返回的多个值起名字,但最好用于简单函数;起不起名字对调用者来说没区别

func div(a, b int) (int, int) {
    return a / b, a % b
}

func div2(a, b int) (q, r int) {
    return a / b, a % b
}

func div3(a, b int) (q, r int) {
    q = a / b
    r = a % b
    return
}

一般来说,函数的第二个返回值用来返回error,以便调用者处理

func eval2(a, b int, op string) (int, error) {
    switch op {
    case "+":
        return a + b, nil
    case "-":
        return a - b, nil
    case "*":
        return a * b, nil
    case "/":
        return a / b, nil
    default:
        return 0, fmt.Errorf("Invalid operation: %s", op)
    }
}

函数可以作为函数的入参

func apply(op func(int, int) int, a, b int) int {
    p := reflect.ValueOf(op).Pointer()
    opName := runtime.FuncForPC(p).Name()

    fmt.Printf("Calling Function %s with args(%d, %d)\n", opName, a, b)

    return op(a, b)
}

只有值传递一种方式,即将参数进行拷贝传入函数
指针不能运算

数组

定义:

var arr1 [5]int 
var arr2 := [3]int{3, 4, 5}
var arr3 := [...]int{1, 2, 3, 4, 5, 6}  // 缺省数组长度,由编译器确定
var grid [4][[5]int

遍历:

len、range、省略变量可以用_代替

numbers := [6]int {1, 2, 3, 4, 5, 6}
for i := 0; i < len(numbers); i++ {
    fmt.Println(numbers[i])
}

maxi := -1
maxValue := -1
for i,v := range numbers {
    if v > maxValue {
        maxi, maxValue = i, v
    }
}
fmt.Println(maxi, maxValue)

sum := 0
for _,v := range numbers {
    sum += v
}
fmt.Println(sum)

for i := range numbers {
    fmt.Println(i)
}
  • 数组是值类型,arr [5]int,arr [10]int是不同的类型
  • 函数传入数组,是对数组做了拷贝 func f(arr [10]int)
  • 可以使用指针,在函数内对入参数组做更改
func printArray(arr *[6]int) {
    arr[1] = 100
    for _, v := range arr {
        fmt.Println(v)
    }
}
func main() {
    numbers := [6]int {1, 2, 3, 4, 5, 6}
    printArray(&numbers)
    fmt.Println(numbers)
}
  • 一般不直接使用数组,使用slice

slice

slice本身没有数据,是对底层array的一个view

slice的内部:
ptr,指向slice开头的元素,
len,说明slice的长度,方括号取值时只能取到length里面的值,下标超出就报错,
cap(capacity:容量),从ptr开始到结束的整个array的长度;

扩展时,只要不超过capacity就可以扩展,只能向后扩展;
cap(s):slice的容量, len(s):slice的长度

向slice添加元素:

添加元素时如果超过cap,系统会重新分配更大的底层数组
由于是值传递,必须有参数接收append的返回值
s = append(s, val)

slice操作

var s []int /* 此处定义slice的初值为nil */
for i := 0; i < 10; i++ {
    s = append(s, i*2+1)
}
s1 := []int{2, 4, 6, 8}
s2 := make([]int, 16)
s3 := make([]int, 10, 32) /* slice长度、容量 */

fmt.Println("Copying slice")
copy(s2, s1)
printSlice(s2)

fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)

fmt.Println("Poping from front")
front := s2[0]
s2 = s2[1:]

fmt.Println("Poping from back")
back := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(front, back)
printSlice(s2)

Map

Map的操作

  • 创建:make(map[string]int) 复合map:map[string]map[sring]int
  • 获取元素:m[key] key不存在时,获得Value类型的初始值
  • 用value, ok = m[key]来判断是否存在key
  • 用delete删除一个key
  • 使用range遍历key,或者遍历key,value对 (不保证遍历顺序,如需顺序,需手动对key排序)
  • 使用map获取长度
    map的底层实现是hash表
m := map[string]string{
"name":   "xyz",
"sex":    "famale",
"high":   "167cm",
"weight": "53kg",
}

mm := map[string]map[string]string {
"xx": {"name": "xyz",},
}

m2 := make(map[string]int) /* m2 == empty map */

var m3 map[string]int

fmt.Println("Traversing Map")

for k, v := range m {
fmt.Println(k, v)
}

if namey, flagy := m["namey"]; flagy {
fmt.Println("namey value", namey)
} else {
fmt.Println("key does not exist")
}

fmt.Println("Deleting Element")
delete(m, "name")
namez, flagz := m["name"]
fmt.Println(namez, flagz)

map的key

  • map使用哈希表,必须可以比较相等
  • 除了slice,map,function的内建类型都可以作为key
  • Struct类型不包含上述字段,也可以作为key

字符和字符串

  • rune类型是go的字符类型,其实就是int32的别名
  • 使用range遍历pos,rune对,
    (将带中文字符的string强转为[]rune,可以获得连续索引的中文字符;
  • 这是又开辟了一块空间,存放解码后的字符;)
  • 使用utf8.RuneCountInString获得字符数量
  • 使用len获得字节长度
  • 使用[]byte获得字节
package main

import (
    "unicode/utf8"
    "fmt"
)

func main() {
    s := "kitty咘咘和臭臭" // UTF-8
    fmt.Println(s)
    for _, b := range []byte(s) {
        fmt.Printf("%X ", b)
    }

    fmt.Println()

    for i, ch := range s {
        fmt.Printf("(%d %c)", i, ch)
    }

    fmt.Println()

    fmt.Println("Rune count in string:",
        utf8.RuneCountInString(s))

    bytes := []byte(s)
    for len(bytes) > 0 {
        ch, size := utf8.DecodeRune(bytes)
        bytes = bytes[size:]
        fmt.Printf("%c ", ch)
    }

    fmt.Println()

    for i, ch := range []rune(s) {
        fmt.Printf("(%d %c)", i, ch)
    }
}

打印结果

kitty咘咘和臭臭
6B 69 74 74 79 E5 92 98 E5 92 98 E5 92 8C E8 87 AD E8 87 AD 
(0 k)(1 i)(2 t)(3 t)(4 y)(5 咘)(8 咘)(11 和)(14 臭)(17 臭)
Rune count in string: 10
k i t t y 咘 咘 和 臭 臭 
(0 k)(1 i)(2 t)(3 t)(4 y)(5 咘)(6 咘)(7 和)(8 臭)(9 臭)

其他字符串操作
- Fields, Split, Join
- Contains, Index
- ToLower, ToUpper
- Trim, TrimRight, TrimLeft

面向对象

仅支持封装,不支持继承和多态
没有class,仅有struct
不论是地址还是结构本身,一律使用.来访问成员
没有构造函数,使用自定义工厂函数,函数返回的是局部变量地址
go语言的编译系统会视具体情况将内存分配在栈上或者堆上,当堆上的变量不再使用时,便会启用垃圾回收,回收这块内存,程序员不需要关心。

方法

type treeNode struct {
    value int
    left, right *treeNode
}

/* 显示定义和命名结构体方法 */
func (node treeNode) print() {
    fmt.Print(node.value, " ")
}
/* 只有使用指针才可以改变结构内容 */
func (node *treeNode) setValue(value int) {
    node.value = value
}
/* 中序遍历 */
func (node *treeNode) traverse() {
    if node == nil {
        return
    }

    node.left.traverse()
    node.print()
    node.right.traverse()
}

func createTreeNode(value int) *treeNode {
    return &treeNode{value:value}
}

func main() {
    var root treeNode
    root = treeNode{value:1}

    root.left = &treeNode{}
    root.right = &treeNode{11, nil, nil}

    root.left.left = new(treeNode)
    root.right.right = createTreeNode(111)


    nodes := []treeNode{
        {value:1},
        {},
        {11, nil, &root},
    }

    fmt.Println(root)
    fmt.Println(nodes)

    root.print()
    root.right.right.print()
    root.left.left.setValue(100)
    root.left.left.print()

    /* nil也可以访问指针接收者 */
    var pRoot *treeNode
    pRoot.setValue(100)
    pRoot = &root
    pRoot.setValue(1111)
    pRoot.print()
}

{1 0xc042060400 0xc042060420}
[{1 <nil> <nil>} {0 <nil> <nil>} {11 <nil> 0xc0420603e0}]

值接收者 vs 指针接受者
- 要改变内容必须使用指针接收者
- 结构过大也考虑使用指针接受者
- 一致性:如有指针接收者,最好都是指针接受者
值接收者是go语言特有
值/指针接受者均可接收值/指针

封装

  • 名字一般使用CamelCase
  • 首字母大写:public
  • 首字母小写:private

  • 每个目录一个包
  • main包包含可执行入口
  • 如果一个目录下有main函数,那这个目录下只能有一个main包
  • 为结构定义的方法必须放在同一个包内
  • 方法可以放在不同的文件中(同一个包名)

扩展已有类型

  • 定义别名
  • 使用组合
type myTreeNode struct {
    node *tree.Node
}

func (myNode *myTreeNode) postOrder() {
    if myNode == nil || myNode.node == nil {
        return
    }

    left := myTreeNode{myNode.node.Left}
    right := myTreeNode{myNode.node.Right}

    left.postOrder()
    right.postOrder()
    myNode.node.Print()
}

func main() {
    var root tree.Node
    root = tree.Node{Value:1}
    root.Left = &tree.Node{}
    root.Left.Left = new(tree.Node)

    fmt.Print("\nMy own post-order traversal: ")
    myRoot := myTreeNode{&root}
    myRoot.postOrder()
    fmt.Println()
}
package queue

type Queue []int

func (q *Queue) Push(v int) {
    *q = append(*q, v)
}

func (q *Queue) Pop() int {
    head := (*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool {
    return 0 == len(*q)
}

gopath以及目录结构

go get获取第三方库
使用gopm来获取无法下载的包
go get -v github.com/gpmgo/gopm
gopm get -g -v golang.org/x/tools/cmd/goimports
go bulid golang.org/x/tools/cmd/goimports
go install golang.org/x/tools/cmd/goimports

Gopath下包含src pkg bin;文件在src中,pkg是编译衍生物,编译生成的可执行文件在bin中

接口

go语言的duck typing同时完成多种功能
同时具有python,c++的duck typing的灵活性
又具有java的类型检查

接口的定义

接口由使用者定义
接口的实现是隐式的
只要struct实现接口声明的方法,就可以传递给接口参数使用

接口变量

  • 接口变量:实现者的类型 + 实现者的值/指针
  • 一般不会定义接口类型的指针变量
  • 指针接收者实现只能以指针方式使用;值接收者都可以
查看接口变量
  • 表示任何类型:interface{}
package queue

type Queue []interface{}

func (q *Queue) Push(v interface{}) {
    *q = append(*q, v)
    //*q = append(*q, v.(int))
}

//func (q *Queue) Pop() int {
func (q *Queue) Pop() interface{} {
    head := (*q)[0]
    *q = (*q)[1:]
    return head
    //return head.(int)
}

func (q *Queue) IsEmpty() interface{} {
    return 0 == len(*q)
}

package main

import (
    "fmt"
    "mypractice.com/practice/queue"
)

func main() {
    q := queue.Queue{1}

    q.Push(2)
    q.Push(3)
    q.Push("abc")
    fmt.Println(q.Pop())
    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())
    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())
    fmt.Println(q.Pop())
    fmt.Println(q.IsEmpty())
}
  • Type Assertion
  • Type Switch
    .(type)只能配合switch case使用,用来判断变量类型;还可以使用.(bufio.Reader)
    使用者有最大的自由去定义组合接口,而不用管实现者到底是在一个还是多个接口中分别实现的功能
type Retriever struct {
    Contents string
}

func (r *Retriever) String() string {
    return fmt.Sprintf(
        "Retriever: {Contents=%s}", r.Contents)
}

func (r *Retriever) Get(url string) string {
    return r.Contents
}

func (r *Retriever) Post(url string,
    form map[string]string) string {
    r.Contents = form["contents"]
    return "OK"
}
type Retriever interface {
    Get(url string) string
}

type Poster interface {
    Post(url string,
    form map[string]string) string
}

func download(r Retriever) string {
    return r.Get("https://www.csdn.net")
}

func post(poster Poster) {
    poster.Post("https://www.csdn.net", 
    map[string]string {
        "name": "qifangyuan",
        "blog": "Blog",
    })
}

//组合接口
type RetrieverPoster interface {
    Retriever
    Poster
}

func session(s RetrieverPoster) string{
    s.Post("fake https://www.csdn.net",
    map[string]string {
        "contents": "another test interface",
    })
    /*s.Post("https://www.csdn.net", 
    map[string]string {
        "name": "qqq",
        "blog": "Blog",
    })*/
    return s.Get("https://www.csdn.net")
}

func inspect(r Retriever) {
    fmt.Println("Inspecting", r)
    fmt.Printf(" > Type:%T Value:%v\n", r, r)
    fmt.Print(" > Type switch: ")
    // .(type)只能配合switch case使用,用来判断变量类型
    // 还可以使用.(bufio.Reader)
    switch v := r.(type) {
    case *mock.Retriever:
        fmt.Println("Contents:", v.Contents)
    case *real.Retriever:
        fmt.Println("UserAgent:", v.UserAgent)
    }
    fmt.Println()
}

func main() {

    var r Retriever
    mockRetriever := mock.Retriever{
        Contents:"test interface"}

    r = &mockRetriever  
    inspect(r)

    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut:   time.Minute,
    }
    inspect(r)

    realRetriever := r.(*real.Retriever)
    fmt.Println(realRetriever.UserAgent)
    if mockRetriever, ok := r.(*mock.Retriever); ok {
        fmt.Println(mockRetriever.Contents)
    }else {
        fmt.Println("not a mock retriever")
    }

    fmt.Println(
        "Try a session with mockRetriever")
    fmt.Println(session(&mockRetriever))

}

常用的系统接口:
Stringer
Reader/Writer

函数式编程

参数、变量、返回值都可以是函数
高阶函数
函数->闭包
这里写图片描述

应用举例:
斐波那契额
函数接口
二叉树遍历

go语言闭包的应用:
更为自然,不需要修饰如何访问自由变量
没有lambda表达式,但是有匿名函数

资源管理与出错处理

defer调用

  • 确保调用在函数结束时发生
  • 参数在defer语句时计算
  • defer列表为先进后出
    使用时机:
  • Open/Close
  • Lock/Unlock
  • PrintHeader/PrintFooter

错误处理

主要是防止程序出错中断,一般是直接返回
- 可以处理预期内的错误
- 也可以实现error接口

func writeFile(filename string) {
    file, err := os.OpenFile(filename,
        os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)

    if err != nil {
        if pathError, ok := err.(*os.PathError); !ok {
            panic(err)
        } else {
            fmt.Printf("%s, %s, %s\n",
                pathError.Op,
                pathError.Path,
                pathError.Err)
        }
        return
    }
}

使用函数式编程,将错误返回给外层错误处理函数统一处理
综合示例:
- defer + panic + recover
- Type Assertion
- 函数式编程的应用

package filelisting

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

func HandleFilelist(writer http.ResponseWriter,
    request *http.Request) error {
    path := request.URL.Path[len("/list/"):]
    fmt.Println(path)
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()

    all, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    writer.Write(all)

    return nil
}
/****************************************************************/

package main

import (
    "log"
    "net/http"
    "os"

    "mypractice.com/practice/errhandling/filelistingserver/filelisting"
)

type apphandler func(writer http.ResponseWriter,
    request *http.Request) error

func errWrapper(
    handler apphandler) func(
    http.ResponseWriter, *http.Request) {
    return func(writer http.ResponseWriter,
        request *http.Request) {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("Panic: %v", r)
                http.Error(writer,
                    http.StatusText(http.StatusInternalServerError),
                    http.StatusInternalServerError)
            }
        }()

        err := handler(writer, request)
        if err != nil {
            log.Printf("Error occurred "+
                "handling request: %s",
                err.Error())
            if userErr, ok := err.(userError); ok {
                http.Error(writer,
                    userErr.Message(),
                    http.StatusBadRequest)
                return
            }
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden
            default:
                code = http.StatusInternalServerError
            }
            http.Error(writer,
                http.StatusText(code), code)
        }
    }
}

func main() {
    http.HandleFunc("/list/",
        errWrapper(filelisting.HandleFilelist))

    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}

panic

  • 停止当前函数执行
  • 一直向上返回,执行每一层的defer
  • 如果没有遇见recover,程序退出

recover

  • 仅在defer调用中使用
  • 获取panic的值
  • 如果无法处理,可重新panic

测试

表格驱动测试

  • 分离的测试数据和测试逻辑
  • 明确的出错信息
  • 可以部分失败
  • go语言的语法更易实践表格驱动测试

测试用例文件命名:*_test
可以进入文件所在目录,命令行使用命令:go test . 运行测试文件
testing.T

package main

import "testing"

func TestSubstr(t *testing.T) {
    tests := []struct {
        s   string
        ans int
    }{
        // Normal cases
        {"abcabcbb", 3},
        {"pwwkew", 3},

        // Edge cases
        {"", 0},
        {"b", 1},
        {"bbbbbbbbb", 1},
        {"abcabcabcd", 4},

        // Chinese support
        {"哇哈哈哈哇", 6},
        {"一二三二一", 3},
        {"黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花", 8},
    }

    for _, tt := range tests {
        actual := lengthOfNonRepeatingSubStr(tt.s)
        if actual != tt.ans {
            t.Errorf("got %d for input %s; "+
                "expected %d",
                actual, tt.s, tt.ans)
        }
    }
}

代码覆盖率和性能测试

覆盖率

IDEA里按钮
命令行:go test -coverprofile=c.out
go tool cover 查询用法
go tool cover -html=c.out

性能测试 testing.B

func BenchmarkSubstr(b *testing.B) {
    s := "黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花"
    for i := 0; i < 13; i++ {
        s = s + s
    }
    b.Logf("len(s) = %d", len(s))
    ans := 8
    /*除去测试数据准备时间,只计算实际代码运行时间*/
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        actual := lengthOfNonRepeatingSubStr(s)
        if actual != ans {
            b.Errorf("got %d for input %s; "+
                "expected %d",
                actual, s, ans)
        }
    }
}

IDEA里运行BenchmarkSubstr
或者命令行里用:go test -bench .

使用pprof进行性能调优

这里写图片描述
go test -bench . -cpuprofile cpu.out
go tool pprof cpu.out
交互式命令行:
(pprof)web (需要先安装Graphviz)
(pprof)quit

生成文档和示例代码

  • 用注释写文档
  • 在测试中加入Example
  • 使用go doc/godoc来查看/生成文档
    godoc -http :6060 在网页里看注释生成的文档

go语言的爬虫库/框架

  • henrylee2cn/pholcus
  • gocrawl
  • colly
  • hu17889/go_spider
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GoLang学习笔记主要包括以下几个方面: 1. 语法规则:Go语言要求按照语法规则编写代码,例如变量声明、函数定义、控制结构等。如果程序中违反了语法规则,编译器会报错。 2. 注释:Go语言中的注释有两种形式,分别是行注释和块注释。行注释使用`//`开头,块注释使用`/*`开头,`*/`结尾。注释可以提高代码的可读性。 3. 规范代码的使用:包括正确的缩进和空白、注释风格、运算符两边加空格等。同时,Go语言的代码风格推荐使用行注释进行注释整个方法和语句。 4. 常用数据结构:如数组、切片、字符串、映射(map)等。可以使用for range遍历这些数据结构。 5. 循环结构:Go语言支持常见的循环结构,如for循环、while循环等。 6. 函数:Go语言中的函数使用`func`关键字定义,可以有参数和返回值。函数可以提高代码的重用性。 7. 指针:Go语言中的指针是一种特殊的变量,它存储的是另一个变量的内存地址。指针可以实现动态内存分配和引用类型。 8. 并发编程:Go语言提供了goroutine和channel两个并发编程的基本单位,可以方便地实现多线程和高并发程序。 9. 标准库:Go语言提供了丰富的标准库,涵盖了网络编程、文件操作、加密解密等多个领域,可以帮助开发者快速实现各种功能。 10. 错误处理:Go语言中的错误处理使用`defer`和`panic`两个关键字实现,可以有效地处理程序运行过程中出现的错误。 通过以上内容的学习,可以掌握Go语言的基本语法和编程思想,为进一步学习和应用Go语言打下坚实的基础。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Golang学习笔记](https://blog.csdn.net/weixin_52310067/article/details/129467041)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [golang学习笔记](https://blog.csdn.net/qq_44336275/article/details/111143767)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值