Compare commits
3 Commits
5ab496ae36
...
010c5bc593
Author | SHA1 | Date |
---|---|---|
|
010c5bc593 | |
|
70b913091c | |
|
2734edb601 |
|
@ -67,7 +67,7 @@ func square() func() int {
|
|||
// 而是通过resp.Request.URL解析后的值。
|
||||
// 解析后,这些连接以绝对路径的形式存在,可以直接被http.Get访问。
|
||||
func Extract() ([]string, error) {
|
||||
node, resp := HtmlTools.GetHtmlS()
|
||||
node, resp := HtmlTools.GetHtmlResponse("")
|
||||
var links []string
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "a" {
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"Study/src/study/day7Function/HtmlTools"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"golang.org/x/net/html"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// defer语句经常被用于处理成对的操作,
|
||||
// 如打开、关闭、连接、断开连接、加锁、释放锁。
|
||||
// 通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。
|
||||
// 释放资源的defer应该直接跟在请求资源的语句后。
|
||||
|
||||
func title(url string) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 直到包含defer的函数执行完毕之后,defer的这条语句才会执行
|
||||
defer resp.Body.Close()
|
||||
// Check Content-Type is HTML (e.g., "text/html;charset=utf-8").
|
||||
ct := resp.Header.Get("Content-Type")
|
||||
if ct != "text/html" && !strings.HasPrefix(ct, "text/html;") {
|
||||
return fmt.Errorf("%s has type %s, not text/html", url, ct)
|
||||
}
|
||||
doc, err := html.Parse(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing %s as HTML: %v", url, err)
|
||||
}
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "title" && n.FirstChild != nil {
|
||||
fmt.Println(n.FirstChild.Data)
|
||||
}
|
||||
}
|
||||
HtmlTools.ForEachNode(doc, visitNode, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 每一次bigSlowOperation被调用,程序都会记录函数的进入,退出,持续时间。
|
||||
// (我们用time.Sleep模拟一个耗时的操作)
|
||||
func bigSlowOperation() {
|
||||
defer trace("aaa")()
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
func trace(msg string) func() {
|
||||
start := time.Now()
|
||||
log.Printf("enter %s", msg)
|
||||
return func() {
|
||||
log.Printf("leave %s, (%s)", msg, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
// defer 可在函数调用执行完毕时,打印一些调试信息
|
||||
func double(x int) (result int) {
|
||||
defer func() { fmt.Println(x) }()
|
||||
return x + x
|
||||
}
|
||||
func triple(x int) (result int) {
|
||||
defer func() { result += x }()
|
||||
return double(x)
|
||||
}
|
||||
func main() {
|
||||
//if err := title(os.Args[1]); err != nil {
|
||||
// log.Fatalln(err)
|
||||
//}
|
||||
bigSlowOperation()
|
||||
|
||||
fmt.Println(triple(3)) // 9
|
||||
|
||||
//⚠️注意,在循环体里调用defer时,由于defer所在的语句要在整个函数执行完毕时在执行,
|
||||
// 这样的代码会导致系统的文件描述符耗尽,因为在所有文件都被处理之前,没有文件会被关闭。
|
||||
|
||||
}
|
||||
|
||||
// 我们可以稍微对以前练习1.13的代码做改进
|
||||
func extractRestructure(URL string, isSave bool, saveOriginOnly bool) ([]string, error) {
|
||||
// 检查参数
|
||||
if URL == "" {
|
||||
URL = "https://go.dev/"
|
||||
}
|
||||
|
||||
node, resp := HtmlTools.GetHtmlResponse(URL)
|
||||
originURL := resp.Request.URL.Host
|
||||
var links []string
|
||||
|
||||
// 这样就不会有重复打开文件的浪费资源的bug了,
|
||||
// 也没有在循环体里调用defer
|
||||
// 定义变量
|
||||
var filePath string
|
||||
var file *os.File
|
||||
// 定义保存路径
|
||||
if isSave {
|
||||
var err error
|
||||
filePath = HtmlTools.GetCurrentAbPath() + "/www/urls.txt"
|
||||
// 打开文件
|
||||
file, err = os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("文件打开失败", err)
|
||||
}
|
||||
}
|
||||
// 关闭文件
|
||||
defer file.Close().Error()
|
||||
visitNode := func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "a" {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key != "href" {
|
||||
continue
|
||||
}
|
||||
// 将相对路径和url的内容组合在一起形成完整的绝对路径
|
||||
link, err := resp.Request.URL.Parse(a.Val)
|
||||
if err != nil {
|
||||
// 忽略掉报error的URL
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
// 判断是否只保存同一域名下的地址
|
||||
if saveOriginOnly && (link.Host != originURL) {
|
||||
continue
|
||||
}
|
||||
// 判断是否保存页面
|
||||
if isSave && link.String() != "" {
|
||||
write := bufio.NewWriter(file)
|
||||
if _, err := write.WriteString(link.String() + "\r\n"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// 将缓存写入文件
|
||||
if err := write.Flush(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// 将link添加到切片
|
||||
links = append(links, link.String())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
HtmlTools.ForEachNode(node, visitNode, nil)
|
||||
return links, nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package HtmlTools
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// see https://zhuanlan.zhihu.com/p/363714760
|
||||
// 最终方案-全兼容
|
||||
func GetCurrentAbPath() string {
|
||||
dir := getCurrentAbPathByExecutable()
|
||||
if strings.Contains(dir, getTmpDir()) {
|
||||
return getCurrentAbPathByCaller()
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// 获取系统临时目录,兼容go run
|
||||
func getTmpDir() string {
|
||||
dir := os.Getenv("TEMP")
|
||||
if dir == "" {
|
||||
dir = os.Getenv("TMP")
|
||||
}
|
||||
res, _ := filepath.EvalSymlinks(dir)
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取当前执行文件绝对路径
|
||||
func getCurrentAbPathByExecutable() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取当前执行文件绝对路径(go run)
|
||||
func getCurrentAbPathByCaller() string {
|
||||
var abPath string
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if ok {
|
||||
abPath = path.Dir(filename)
|
||||
}
|
||||
return abPath
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"Study/src/study/day7Function/HtmlTools"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"golang.org/x/net/html"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ElementsByTagName(doc *html.Node, name ...string) []*html.Node {
|
||||
// 创建一个map, 用于保存传入的tag name和是否已被找到的状态
|
||||
tags := make(map[string]bool)
|
||||
for _, tag := range name {
|
||||
tags[tag] = false
|
||||
}
|
||||
// 创建用于保存查找到的元素的切片
|
||||
var elements []*html.Node
|
||||
// 声明查找函数
|
||||
var visit func(*html.Node)
|
||||
visit = func(n *html.Node) {
|
||||
// 如果当前节点是html元素节点
|
||||
if n.Type == html.ElementNode {
|
||||
// 检测tags中是否存在 n.Data
|
||||
// 该版本为只返回找到的第一个节点node
|
||||
// 如果要找到doc节点下所有的符合条件的node的话
|
||||
// 请打开下面这行代码, 这行代码表示只会检查tags map里是否存在n.Data
|
||||
//if _, ok := tags[n.Data]; ok {
|
||||
// 并注释掉下面的这一行代码, 这行表示会同时检查是否存在和是否添加过同名节点
|
||||
if isFind, ok := tags[n.Data]; ok && !isFind {
|
||||
// 将该元素加入到结果切片中
|
||||
elements = append(elements, n)
|
||||
// 标记该tag name已被查找到
|
||||
tags[n.Data] = true
|
||||
}
|
||||
}
|
||||
// 递归查找子节点
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
visit(c)
|
||||
}
|
||||
}
|
||||
// 从传入的文档节点开始查找
|
||||
visit(doc)
|
||||
// 返回查找到的元素切片
|
||||
return elements
|
||||
}
|
||||
|
||||
func main() {
|
||||
doc := HtmlTools.GetHtml()
|
||||
images := ElementsByTagName(doc, "img")
|
||||
headings := ElementsByTagName(doc, "h1", "h2", "h3", "h4", "a", "div")
|
||||
for _, v := range images {
|
||||
fmt.Println(v.Data)
|
||||
}
|
||||
fmt.Println()
|
||||
for _, v := range headings {
|
||||
fmt.Println(v.Data)
|
||||
}
|
||||
|
||||
//⚠️当需要检测一个mao中是否存在某一元素时,使用以下代码
|
||||
// v, ok := m2["a"]
|
||||
// v 是 这个map["a"]的值 即 v == m2["a"]
|
||||
// ok是一个bool值, 当m2这个map存在a这个元素时, ok值为true, 反之则为false
|
||||
m2 := make(map[string]any)
|
||||
m2["a"] = "aaa"
|
||||
m2["b"] = "bbb"
|
||||
m2["c"] = "ccc"
|
||||
v, ok := m2["a"]
|
||||
fmt.Println("a ", ok, v) // true, aaa
|
||||
_, ok = m2["b"]
|
||||
fmt.Println("b ", ok) // true
|
||||
_, ok = m2["c"]
|
||||
fmt.Println("c ", ok) // true
|
||||
_, ok = m2["d"]
|
||||
fmt.Println("d ", ok) // false
|
||||
_, ok = m2["e"]
|
||||
fmt.Println("e ", ok) // false
|
||||
_, ok = m2["f"]
|
||||
fmt.Println("f ", ok) // false
|
||||
|
||||
var buf *bytes.Buffer
|
||||
//fmt.Printf("buf: \t(%T, %[1]v)\n", buf)
|
||||
f(buf)
|
||||
}
|
||||
|
||||
// If out is non-nil, output will be written to it.
|
||||
func f(out io.Writer) {
|
||||
// ...do something...
|
||||
var a *bytes.Buffer // 结构体空指针
|
||||
var b io.Writer // io.write 接口
|
||||
var c interface{} // 空接口
|
||||
var d io.Writer = a
|
||||
|
||||
fmt.Printf("a: \t(%T, %[1]v)\n", a)
|
||||
fmt.Printf("b: \t(%T, %[1]v)\n", b)
|
||||
fmt.Printf("c: \t(%T, %[1]v)\n", c)
|
||||
fmt.Printf("d: \t(%T, %[1]v)\n", d)
|
||||
fmt.Printf("out: \t(%T, %[1]v)\n", out)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("a == b", a == b)
|
||||
fmt.Println("a == c", a == c)
|
||||
fmt.Println("a == d", a == d)
|
||||
fmt.Println("a == out", a == out)
|
||||
fmt.Println("a == nil", a == nil)
|
||||
fmt.Println()
|
||||
fmt.Println("b == c", b == c)
|
||||
fmt.Println("b == d", b == d)
|
||||
fmt.Println("b == out", b == out)
|
||||
fmt.Println("b == nil", b == nil)
|
||||
fmt.Println()
|
||||
fmt.Println("c == d", c == d)
|
||||
fmt.Println("c == out", c == out)
|
||||
fmt.Println("c == nil", c == nil)
|
||||
fmt.Println()
|
||||
fmt.Println("d == out", d == out)
|
||||
fmt.Println("d == nil", d == nil)
|
||||
|
||||
fmt.Println("out == nil", out == nil)
|
||||
if out != a {
|
||||
fmt.Fprintf(out, "Hello, %s!\n", "world")
|
||||
//fmt.Printf("(%T, %[1]v)\n", out)
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -7,10 +7,6 @@ import (
|
|||
"golang.org/x/net/html"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BreadthFirst 队列中的每节点都调用f()。
|
||||
|
@ -43,7 +39,7 @@ func BreadthFirst(f func(item string) []string, worklist []string) {
|
|||
// saveOriginOnly bool, 是否只保存与当前页面域名相同的页面
|
||||
//
|
||||
// return []string, error, 提取到的链接列表和错误信息
|
||||
func ExtractRestructure(URL string, isSave bool, saveOriginOnly bool) ([]string, error) {
|
||||
func extractRestructure(URL string, isSave bool, saveOriginOnly bool) ([]string, error) {
|
||||
// 检查参数
|
||||
if URL == "" {
|
||||
URL = "https://go.dev/"
|
||||
|
@ -64,7 +60,6 @@ func ExtractRestructure(URL string, isSave bool, saveOriginOnly bool) ([]string,
|
|||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 将link添加到切片
|
||||
links = append(links, link.String())
|
||||
// 判断是否只保存同一域名下的地址
|
||||
|
@ -73,10 +68,7 @@ func ExtractRestructure(URL string, isSave bool, saveOriginOnly bool) ([]string,
|
|||
}
|
||||
// 判断是否保存页面
|
||||
if isSave && link.String() != "" {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
filePath := getCurrentAbPath() + "/www/urls.txt"
|
||||
filePath := HtmlTools.GetCurrentAbPath() + "/www/urls.txt"
|
||||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("文件打开失败", err)
|
||||
|
@ -104,7 +96,7 @@ func main() {
|
|||
// crawl 此函数返回一个[]string, 内容为url
|
||||
crawl := func(url string) []string {
|
||||
fmt.Println("Crawl:", url)
|
||||
list, err := ExtractRestructure(url, true, true)
|
||||
list, err := extractRestructure(url, true, true)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
@ -115,43 +107,3 @@ func main() {
|
|||
// starting from the command-line arguments.
|
||||
BreadthFirst(crawl, []string{"https://go.dev/"})
|
||||
}
|
||||
|
||||
// see https://zhuanlan.zhihu.com/p/363714760
|
||||
// 最终方案-全兼容
|
||||
func getCurrentAbPath() string {
|
||||
dir := getCurrentAbPathByExecutable()
|
||||
if strings.Contains(dir, getTmpDir()) {
|
||||
return getCurrentAbPathByCaller()
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// 获取系统临时目录,兼容go run
|
||||
func getTmpDir() string {
|
||||
dir := os.Getenv("TEMP")
|
||||
if dir == "" {
|
||||
dir = os.Getenv("TMP")
|
||||
}
|
||||
res, _ := filepath.EvalSymlinks(dir)
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取当前执行文件绝对路径
|
||||
func getCurrentAbPathByExecutable() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
|
||||
return res
|
||||
}
|
||||
|
||||
// 获取当前执行文件绝对路径(go run)
|
||||
func getCurrentAbPathByCaller() string {
|
||||
var abPath string
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if ok {
|
||||
abPath = path.Dir(filename)
|
||||
}
|
||||
return abPath
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"Study/src/study/day7Function/HtmlTools"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 有向无环图
|
||||
var prereqs = map[string][]string{
|
||||
"algorithms": {"data structures"},
|
||||
"linear algebra": {"calculus"},
|
||||
"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"},
|
||||
}
|
||||
breadthFirstDigraph(prereqs)
|
||||
|
||||
}
|
||||
func breadthFirstDigraph(m map[string][]string) []string {
|
||||
|
||||
var worklist, res []string
|
||||
// 将第一层节点加入到队列
|
||||
for k, _ := range m {
|
||||
worklist = append(worklist, k)
|
||||
}
|
||||
// seen
|
||||
seen := make(map[string]bool)
|
||||
for len(worklist) > 0 {
|
||||
// 复制当前队列
|
||||
items := worklist
|
||||
// 清空队列
|
||||
worklist = nil
|
||||
// 遍历复制的当前队列
|
||||
for _, item := range items {
|
||||
// 判断seen, 防止重复添加到队列
|
||||
if !seen[item] {
|
||||
seen[item] = true
|
||||
// 添加到队列
|
||||
worklist = append(worklist, m[item]...)
|
||||
fmt.Println(item)
|
||||
res = append(res, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
func crawl(url string) []string {
|
||||
fmt.Println(url)
|
||||
list, err := HtmlTools.Extract()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
return list
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// 上面的代码中,maxMin 函数接收可变参数,并返回参数中的最大值和最小值。
|
||||
// 如果参数个数为0,则返回错误。
|
||||
// maxMin 函数接收可变参数,并返回最大值和最小值
|
||||
func maxMin(nums ...int) (max int, min int, err error) {
|
||||
// 如果参数个数为0,则返回错误
|
||||
err = fmt.Errorf("err: 参数个数为0")
|
||||
if len(nums) == 0 {
|
||||
return 0, 0, err
|
||||
}
|
||||
// 初始化最小值为最大整数
|
||||
min = math.MaxInt
|
||||
// 遍历所有参数,求最大值和最小值
|
||||
for _, num := range nums {
|
||||
if num > max {
|
||||
max = num
|
||||
}
|
||||
if num < min {
|
||||
min = num
|
||||
}
|
||||
}
|
||||
return max, min, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var maxNum, minNum int
|
||||
// 可以选择处理错误, 也可以选择不处理,直接丢弃掉
|
||||
maxNum, minNum, _ = maxMin(4, 2, 3)
|
||||
fmt.Println(maxNum, minNum)
|
||||
// 也可以选择处理此错误
|
||||
maxNum, minNum, err := maxMin()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Join 函数接受一个分隔符 sep 和一个可变长度的字符串元素 elems,
|
||||
// 并将 elems 中的元素用 sep 分隔符连接起来,返回连接后的字符串。
|
||||
func Join(sep string, elems ...string) string {
|
||||
// 处理特殊情况。
|
||||
switch len(elems) {
|
||||
case 0:
|
||||
// 如果 elems 为空,返回空字符串。
|
||||
return ""
|
||||
case 1:
|
||||
// 如果 elems 只有一个元素,返回该元素。
|
||||
return elems[0]
|
||||
}
|
||||
|
||||
// 计算连接后的字符串长度。
|
||||
n := len(sep) * (len(elems) - 1)
|
||||
for i := 0; i < len(elems); i++ {
|
||||
n += len(elems[i])
|
||||
}
|
||||
|
||||
// 使用 strings.Builder 来高效地构建连接后的字符串。
|
||||
var b strings.Builder
|
||||
b.Grow(n)
|
||||
b.WriteString(elems[0])
|
||||
for _, s := range elems[1:] {
|
||||
b.WriteString(sep)
|
||||
b.WriteString(s)
|
||||
}
|
||||
|
||||
// 返回连接后的字符串。
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 不太懂这个题的意思
|
||||
// 是将 elems 切片参数改为可变参数吗?
|
||||
str := []string{"a", "b", "c", "d", "e", "f"}
|
||||
// str... 为解切片操作。
|
||||
fmt.Println(Join("-", str...))
|
||||
fmt.Println(Join(",", "abc", "def", "ghi"))
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Fetch downloads the URL and returns the
|
||||
// name and length of the local file.
|
||||
func fetch(url string) (filename string, n int64, err error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
local := path.Base(resp.Request.URL.Path)
|
||||
if local == "/" {
|
||||
local = "index.html"
|
||||
}
|
||||
fmt.Println(local)
|
||||
f, err := os.Create(local)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
n, err = io.Copy(f, resp.Body)
|
||||
|
||||
// 这里要搞明白 return defer 和 返回值被赋值给调用者 的先后执行顺序
|
||||
// 这里先说结论 defer的执行顺序在return之后,但是在返回值返回给调用方之前
|
||||
|
||||
// 匿名返回值是在return执行时被声明,有名返回值则是在函数声明的同时被声明,
|
||||
// 因此在defer语句中只能访问有名返回值,而不能直接访问匿名返回值
|
||||
|
||||
// return其实应该包含前后两个步骤:
|
||||
// 第一步是给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值);
|
||||
// 第二步是调用RET返回指令并传入返回值,而RET则会检查defer是否存在,若存在就先逆序插播defer语句,
|
||||
// 最后RET携带返回值退出函数;
|
||||
|
||||
// 回到这里这个例子, err是被显示定义的有名返回值变量, 所以defer可以访问err
|
||||
// 这个例子中, 执行到defer时如果Close函数发生错误, 那么返回值中的err将会被改变
|
||||
defer func() {
|
||||
if closeErr := f.Close(); closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
return local, n, err
|
||||
}
|
||||
func main() {
|
||||
if l, n, err := fetch("https://go.dev/"); err != nil {
|
||||
log.Fatalln(l, n, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 可变参数
|
||||
// 在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“...”,
|
||||
// 这表示该函数会接收任意数量的该类型参数。
|
||||
func sum(vals ...int) int {
|
||||
total := 0
|
||||
for _, val := range vals {
|
||||
total += val
|
||||
}
|
||||
return total
|
||||
}
|
||||
func main() {
|
||||
fmt.Println(sum()) // "0"
|
||||
fmt.Println(sum(3)) // "3"
|
||||
fmt.Println(sum(1, 2, 3, 4)) // "10"
|
||||
|
||||
// 虽然在可变参数函数内部,...int 型参数的行为看起来很像切片类型,
|
||||
// 但实际上,可变参数函数和以切片作为参数的函数是不同的。
|
||||
var f = func(a ...int) {
|
||||
fmt.Printf("%T\n", a)
|
||||
}
|
||||
var g = func(a []int) {
|
||||
fmt.Printf("%T\n", a)
|
||||
}
|
||||
fmt.Printf("%T\n", f) // "func(...int)"
|
||||
fmt.Printf("%T\n", g) // "func([]int)"
|
||||
|
||||
// 但是可变参数函数内部,实际传入的参数还是一个切片
|
||||
f(1, 2, 3)
|
||||
g([]int{1, 2, 3})
|
||||
|
||||
// 可变参数函数经常被用于格式化字符串。
|
||||
// 下面的errorf函数构造了一个以行号开头的,
|
||||
// 经过格式化的错误信息。函数名的后缀f是一种通用的命名规范,
|
||||
// 代表该可变参数函数可以接收Printf风格的格式化字符串。
|
||||
errorf := func(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"
|
||||
|
||||
}
|
Loading…
Reference in New Issue