Article

Golang 基础 2

标准输入输出

os 标准库使用:stdin 和 stdout

package main

import (
	"os"
	"syscall"
)

func main() {
	// 使用 syscall.Stdin(值为0)创建标准输入文件
	stdin := os.NewFile(uintptr(syscall.Stdin), "/dev/stdin")
	defer stdin.Close()

	// 现在可以使用 stdin 进行读取,例如:
	var buf [100]byte
	n, _ := stdin.Read(buf[:])
	os.Stdout.Write(buf[:n])
}

alt text

os 本身在初始化时就用 NewFile 创建了 Stdout、Stdin、Stderr 这三个全局变量,使得我们能够直接使用它们。

也就是所谓的文件描述符

os.Stdin - 标准输入

os.Stdout - 标准输出

os.Stderr - 标准错误

var (
	Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
	Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

bufio

bufio 提供了可缓冲的输出方法,它可以先将数据写入到内存中,积累到了一定阈值后再输出到指定的 Writer 中,默认缓冲区大小是 4kB,在文件 IO, 网络 IO 的时候建议使用这个包。

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()
  writer.WriteString("hello world!")
}

类似重载

为什么说是“类似重载”呢,因为golang里不存在函数重载的概念,这也很符合golang追求少就是多的设计哲学。

但是,在golang里面,可以使用两个类似的方法,但是接收者类型不同的方式,来实现类似“函数重载”的效果。

type A struct{}
func (a A) Do(x int) {}

type B struct{}
func (b B) Do(x, y string) {}
// 合法,因为接受者类型不同

所以:

  1. Go不支持函数重载,无论寒暑签名是否相同,都不能在同一个包内定义同名函数。
  2. 函数签名相同只会导致重复定义错误,不能达到重载的效果。
  3. 如果需要类似重载的行为,可以使用不同的函数名、可变参数、接口、泛型等方式来实现。

匿名函数

下列的main函数内的函数没有名字,所以我们只能在它的函数体后经跟着括号来进行调用。

func main() {
   func(a, b int) int {
      return a + b
   }(1, 2)
}

我们在对结构体切片进行排序的时候会用到这个写法。

type Person struct {
  Name   string
  Age    int
  Salary float64
}

func main() {
  people := []Person{
    {Name: "Alice", Age: 25, Salary: 5000.0},
    {Name: "Bob", Age: 30, Salary: 6000.0},
    {Name: "Charlie", Age: 28, Salary: 5500.0},
  }

  slices.SortFunc(people, func(p1 Person, p2 Person) int {
    if p1.Name > p2.Name {
      return 1
    } else if p1.Name < p2.Name {
      return -1
    }
    return 0
  })
}

这是一个自定义排序规则的例子,slices.SortFunc接受两个参数,一个是切片,另一个就是比较函数,不考虑复用的话,我们就可以直接传递匿名函数。

闭包

闭包(Clousure)这一概念,在一些语言里也成为lambda表达式,与匿名函数一起使用,闭包就等于函数+环境引用。

package main

import "fmt"

func main() {
	grow := Exp(2)
	for i := range 10 {
		fmt.Printf("2^%d=%d\n", i, grow())
	}
}

// 递归求2的幂函数
func Exp(n int) func() int {
	e := 1
	return func() int {
		temp := e
		e *= n
		return temp
	}
}

上述闭包案例和递归很像,但还是有区别的:

  1. 闭包关注的是函数与其外部作用域的绑定,而不是自身调用。
  2. 递归关注的是函数调用本身,而不一定涉及外部变量的捕获。

使用闭包计算斐波那契数列

package main

import "fmt"

func main() {
	fib := Fib(10)
	for n, next := fib(); next; n, next = fib() {
		fmt.Println(n)
	}
}

func Fib(n int) func() (int, bool) {
	a, b, c := 1, 1, 2
	i := 0
	return func() (int, bool) {
		if i >= n {
			return 0, false
		} else if i < 2 {
			f := 1
			i++
			return f, true
		}
		a, b = b, c
		c = a + b
		i++

		return a, true
	}
}