Golang Go语言 学习笔记(4)

Golang Go语言 学习笔记(4)

函数是一组逻辑的集合,能把大的任务拆成一个一个小的任务,供软件的各个位置去调用。

函数声明

func name(parameter-list) (result-list) {
    body
}
  • 参数列表:参数类型相同可以只在最后一个相同参数后面定义参数类型
    • 形参与实参:按顺序赋值形参,只有当实参是引用类型,如指针,slice(切片)、map、function、channel 等类型当做引用传递可以在函数内部修改实参的值,否则其他情况,形参只是实参的拷贝。
  • 返回值:返回值可以没有或者多个返回值,只有一个返回值时,可以只写一个返回值类型即可
func add(x int, y int) int   {return x + y}
func sub(x, y int) (z int)   { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int      { return 0 }

fmt.Printf("%T\n", add) // “func(int, int) int” fmt.Printf("%T\n", sub) // “func(int, int) int” fmt.Printf("%T\n", first) // “func(int, int) int” fmt.Printf("%T\n", zero) // “func(int, int) int”

递归

  • 递归:函数自己调用自己就是递归调用。
  • 大部分编程语言使用固定大小的函数调用栈,常见的大小从 64KB 到 2MB 不等。固定大小栈会限制递归的深度,当你用递归处理大量数据时,需要避免栈溢出;除此之外,还会导致安全性问题。与相反,Go 语言使用可变栈,栈的大小按需增加(初始时很小)。这使得我们使用递归时不必考虑溢出和安全问题。

多指返回函数

  • 惯例前面返回期望的值,最后一个返回 erro 值
  • 不需要处理的返回值,用_来处理
  • 如果在返回值列表里定义好了返回值的名字,可以用默认 return。可以减少代码量,但是增加维护难度。不宜过度使用。
// CountWordsAndImages does an HTTP GET request for the HTML
// document url and returns the number of words and images in it.
func CountWordsAndImages(url string) (words, images int, err error) {
    resp, err := http.Get(url)
    if err != nil {
        return
    }
    doc, err := html.Parse(resp.Body)
    resp.Body.Close()
    if err != nil {
        err = fmt.Errorf("parsing HTML: %s", err)
    return
    }
    words, images = countWordsAndImages(doc)
    return
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }

错误

  • go 不将返回的 erro 类型当初异常来处理,值当做预期的值来看待。
  • go 错误处理策略
    • 第一种传播的方式
    • 第二种重试的方式:注意设置重试时间和次数
    • 第三种输出错误并退出:这种策略只应在 main 中执行。对库函数而言,应仅向上传播错误,除非该错误意味着程序内部包含不一致性,即遇到了 bug,才能在库函数中结束程序
    • 第四种有时,我们只需要输出错误信息就足够了,不需要中断程序的运行。我们可以通过 log 包提供函数
    • 第五种,也是最后一种策略:我们可以直接忽略掉错误。
  • 固定类型错误:io.EOF,可以根据错误信息执行特别的操作

函数值

  • 在 Go 语言中函数被定义为第一类值,有类型,可以被赋值给其他变量,传递个给函数。
//利用函数变量便利 html 标签

// forEachNode 针对每个结点 x,都会调用 pre(x)和 post(x)。// pre 和 post 都是可选的。// 遍历孩子结点之前,pre 被调用 // 遍历孩子结点之后,post 被调用 func forEachNode(n *html.Node, pre, post func(n *html.Node)) { if pre != nil { pre(n) } for c := n.FirstChild; c != nil; c = c.NextSibling { forEachNode(c, pre, post) } if post != nil { post(n) } }

var depth intfunc startElement(n *html.Node) { if n.Type == html.ElementNode { fmt.Printf("%s<%s>\n", depth2, “”, n.Data) depth++ } } func endElement(n *html.Node) { if n.Type == html.ElementNode { depth– fmt.Printf("%s</%s>\n", depth2, “”, n.Data) } }

匿名函数

  • 通过这种方式定义的函数可以访问完整的词法环境( lexical environment ),这意味着在函数中定义的内部函数可以 引用 该函数的变量
// squares 返回一个匿名函数。// 该匿名函数每次被调用时都会返回下一个数的平方。func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}
func main() {
    f := squares()
    fmt.Println(f()) // "1"
    fmt.Println(f()) // "4"
    fmt.Println(f()) // "9"
    fmt.Println(f()) // "16"
}
  • 利用匿名函数实现拓扑排序算法
// prereqs 记录了每个课程的前置课程 var prereqs = map[string][]string{
    "algorithms": {"data structures"},
    "calculus": {"linear algebra"},
    "compilers": {
        "data structures",
        "formal languages",
        "computer organization",
    },
    "data structures":       {"discrete math"},
    "databases":             {"data structures"},
    "discrete math":         {"intro to programming"},
    "formal languages":      {"discrete math"},
    "networks":              {"operating systems"},
    "operating systems":     {"data structures", "computer organization"},
    "programming languages": {"data structures", "computer organization"},
}

func main() { for i, course := range topoSort(prereqs) { fmt.Printf("%d:\t%s\n", i+1, course) } }

func topoSort(m map[string][]string) []string { var order []string seen := make(map[string]bool) var visitAll func(items []string) visitAll = func(items []string) { for _, item := range items { if !seen[item] { seen[item] = true visitAll(m[item]) order = append(order, item) } } } var keys []string for key := range m { keys = append(keys, key) } sort.Strings(keys) visitAll(keys) return order }

  • 利用匿名函数实现广度搜索
// breadthFirst calls f for each item in the worklist.
// Any items returned by f are added to the worklist.
// f is called at most once for each item.func 
breadthFirst(f func(item string) []string, worklist []string) {
    seen := make(map[string]bool)
    for len(worklist) > 0 {
        items := worklist
        worklist = nil
        for _, item := range items {
            if !seen[item] {
                seen[item] = true
                worklist = append(worklist, f(item)...)
            }
        }
    }
}

func crawl(url string) []string { fmt.Println(url) list, err := links.Extract(url) if err != nil { log.Print(err) } return list }

func main() { // Crawl the web breadth-first, // starting from the command-line arguments. breadthFirst(crawl, os.Args[1:]) }

  • for 的循环词法块中局部变量是引用的

可变参数函数

  • 用...来做变量名,一般可变参数函数用来处理字符串
func errorf(linenum int, format string, args ...interface{}) {
    fmt.Fprintf(os.Stderr, "Line %d: ", linenum)
    fmt.Fprintf(os.Stderr, format, args...)
    fmt.Fprintln(os.Stderr)
}
linenum, name := 12, "count"
errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count"

Deferred 函数

  • defer 语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过 defer 机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的 defer 应该直接跟在请求资源的语句后。
  • defer 的声明是从前往后,触发是从后往前的触发
  • 可以做调试函数
func bigSlowOperation() {
    defer trace("bigSlowOperation")() // don't forget the
    extra parentheses
    // ...lots of work …
    time.Sleep(10 * time.Second) // simulate slow
    operation by sleeping
}
func trace(msg string) func() {
    start := time.Now()
    log.Printf("enter %s", msg)
    return func() { 
        log.Printf("exit %s (%s)", msg,time.Since(start)) 
    }
}

Panic 异常

  • 类似 php throw 如果不捕获也会直接中断程序
  • 一般只有严重错误,影响到程序的正常执行才会用到 panic

Recover 捕获异常

  • 无法捕获的情况
    • 沒加 recover 的 goroutine 里 panic
    • os.Exit
    • map 中特殊情况锁机制

总结

函数是程序逻辑的基本单位,是整个程序的基石,了解它的结构是,帮助我们写出合理优雅的代码的基础。总体来说 Go 的匿名函数给我比较深的印象,没想到用法可以大大减少代码量。


更多关于Golang Go语言 学习笔记(4)的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang Go语言 学习笔记(4)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


当然,以下是对“Golang Go语言 学习笔记(4)”这一帖子的专业回复:


看到你在持续记录Go语言的学习笔记,这确实是一个非常好的学习习惯。对于Go语言(Golang)的深入学习,我有几点建议和心得可以与你分享:

  1. 并发编程:Go语言以其强大的并发处理能力著称。在这一阶段的学习中,务必掌握goroutines和channels的使用,它们是Go并发编程的基石。通过实践一些简单的并发案例,如并发下载、生产者-消费者模型等,可以加深理解。

  2. 接口与类型:Go语言的接口设计非常简洁且强大。学习如何定义接口、实现接口以及接口多态性的使用,对于编写灵活、可扩展的代码至关重要。

  3. 错误处理:Go语言鼓励显式地处理错误,这有助于编写健壮的程序。了解error类型、defer语句以及panic和recover机制,能够帮助你更好地处理程序中的异常情况。

  4. 标准库与第三方库:Go语言的标准库已经非常完善,涵盖了文件操作、网络编程、加密解密等多个方面。同时,也有很多优秀的第三方库可供选择。熟悉这些库的使用,可以大大提高开发效率。

  5. 实践项目:理论学习固然重要,但将所学知识应用到实际项目中才能真正巩固。可以尝试编写一些小型项目,如Web服务、命令行工具等,来检验自己的学习成果。

希望这些建议能对你的学习有所帮助。加油,期待你未来在Go语言领域的更多精彩分享!


回到顶部