《Go 语言程序设计》在线阅读地址:https://yar999.gitbooks.io/gopl-zh/content/
func name(parameter-list) (result-list) {
body
}
形式参数列表描述了函数的参数名以及参数类型。这些参数作为局部变量,其值由参数调用者提供。返回值也可以像形式参数一样被命名,在这种情况下,每个返回值被声明成一个局部变量,并初始化为其类型的零值。
func first(x int, _ int) int { return x }
links, err := findLinks(url)
如果某个值不被使用,可以将其分配给blank identifier:
links, _ := findLinks(url) // errors ignored
// 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) { /* ... */ }
按照函数声明中返回值列表的次序,返回所有的返回值,在上面的例子中,每一个return语句等价于:
return words, images, err
return0,0,err
(Go会将返回值 words和images在函数体的开始处,根据它们的类型,将其初始化为0),最后一处return等价于 returnwords,image,nil
。基于以上原因,不宜过度使用bare return。resp, err := http.Get(url)
Error
函数或者输出函数获得字符串类型的错误信息。fmt.Println(err)
fmt.Printf("%v", err)
func square(n int) int { return n * n }
func negative(n int) int { return -n }
func product(m, n int) int { return m * n }
f := square
fmt.Println(f(3)) // "9"
f = negative
fmt.Println(f(3)) // "-3"
fmt.Printf("%T\n", f) // "func(int) int"
f = product // compile error: can't assign func(int, int) int to func(int) int
var f func(int) int
f(3) // 此处f的值为nil, 会引起panic错误
var f func(int) int
if f != nil {
f(3)
}
但是函数值之间是不可比较的,也不能用函数值作为map的key。
strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
更为重要的是,通过这种方式定义的函数可以访问完整的词法环境(lexical environment),这意味着在函数中定义的内部函数可以引用该函数的变量。
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。
var visitAll func(items []string)
visitAll = func(items []string) {
......
visitAll(m[item])
......
}
否则会出现编译错误
visitAll := func(items []string) {
// ...
visitAll(m[item]) // compile error: undefined: visitAll
// ...
}
func sum(vals...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
sum函数返回任意个int型参数的和。在函数体中,vals被看作是类型为 []int
的切片。sum可以接收任意数量的int型参数:
fmt.Println(sum()) // "0"
fmt.Println(sum(3)) // "3"
fmt.Println(sum(1, 2, 3, 4)) // "10"
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"
// fmt.Println(sum(1, 2, 3, 4))
...int
型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。func f(...int) {}
func g([]int) {}
fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"
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"
...interfac{}
表示函数在 format
参数后可以接收任意个任意类型的参数。interface{}
会在后面介绍。
package ioutil
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return ReadAll(f)
}
var mu sync.Mutex
var m = make(map[string]int)
func lookup(key string) int {
mu.Lock()
defer mu.Unlock()
return m[key]
}
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))
}
}
每一次bigSlowOperation被调用,程序都会记录函数的进入,退出,持续时间。(我们用time.Sleep模拟一个耗时的操作)
$ go build gopl.io/ch5/trace
$ ./trace
2015/11/18 09:53:26 enter bigSlowOperation
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
func double(x int) (result int) {
defer func() { fmt.Printf("double(%d) = %d\n", x,result) }()
return x + x
}
_ = double(4)
// Output:
// "double(4) = 8"
func triple(x int) (result int) {
defer func() { result += x }()
return double(x)
}
fmt.Println(triple(4)) // "12"
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close() // NOTE: risky; could run out of file
descriptors
// ...process f…
}
一种解决方法是将循环体中的文件操作和defer语句移至另外一个函数。在每次循环时,调用这个函数。
for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// ...process f…
}
func Parse(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("internal error: %v", p)
}
}()
// ...parser...
}
func soleTitle(doc *html.Node) (title string, err error) {
type bailout struct{}
defer func() {
switch p := recover(); p {
case nil: // no panic
case bailout{}: // "expected" panic
err = fmt.Errorf("multiple title elements")
default:
panic(p)
}
}()
forEachNode(doc, func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "title" &&
n.FirstChild != nil {
if title != "" {
panic(bailout{}) // multiple titleelements
}
title = n.FirstChild.Data
}
}, nil)
if title == "" {
return "", fmt.Errorf("no title element")
}
return title, nil
}