Go语言笔记(上) | 青训营

90 阅读7分钟

Go语言圣经笔记(上)

1.2 命令行参数

  • os. Args 是一个字符串的切片 . os. Args[0]是命令本身的名字,接下来是参数
  • var 声明并初始化变量,如果没有显示初始化,会赋予类型的零值.
  • i++ 是语句而不是表达式,所以不能作为右值. ++ 只能放在后面.
  • for _, arg := range os.Args[1:]{}
  • 变量声明的常见方式
	s := ""
	s, sep := "", ""
	var s string

1.3 查找重复的行

  • dup 1
func main(){
	counts := make(map[string]int)
	input := bufio.NewScanner(os.Stdin)
	for input.Scan(){
		counts[input.Text()]++
	}
	for line, n := range counts {
		if n > 1 {
			fmt.Printf(%d\t%s\n", n, line)
		}
	}
}
  • 函数和包级别的变量可以任意顺序声明
  • map 是一个由 make 函数创建的数据结构的引用,当 map 作为参数传给函数时,是引用传递.
  • bufio. NewScanner 是以流模式读取输入,并根据需要拆分成多个行
  • ioutil (来自 io/ioutil)的 ReadFile 函数可以把全部输入数据读到内存中,然后可以用 strings. Split 一次性分割
data,err := ioutil.ReadFile(filename)
// ReadFile 返回一个字节切片,需要转化为 string 再分割
for _, line := range strings.Split(string(data), "\n"){ ...}

1.5 获取 URL

  • os.Exit(1) 终止进程并返回 status=1 错误码
  • io.Copy(dst,rsc) 从 src 中读取,结果写入 dst

2.3 变量

  • 声明
var s string // 不显示初始化就是零值
var i, j, k int
var b, f, s = true, 2.3, "four" // bool, float64, string
i, j := 0, 1
i, j = j, i // 交换 i 和 j 的值

f, err := os.Open(infile)
...
f, err := os.Create(outfile) // × 简短变量声明语句中必须至少有一个新的变量
  • 指针
    • 非空:p != nil
    • 安全的指针
var p = f()

func f() *int {
    v := 1
    return &v
} // go 语言的逃逸分析可以智能决定变量存在堆上还是栈上
  • flag 包
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")
// flag.Bool 返回 *bool, flag.String 返回 *string
// -n 默认值 false, 说明 omit trailing newline
  • new 函数 p := new(int)

2.4 赋值

x, y = y, x + y
f, err = os.Open("foo.txt")
v, ok = m[key] // map lookup
v, ok = x.(T)  // type assertion
x, ok = <-ch   // channel receive
medals := []string{"gold", "silver", "bronze"}
  • 两个值能否用‘ == ’或' != '比较,要看第二个值对第一个值的类型对应的变量是否是可赋值的.

2.5 类型

  • type 类型名字 底层类型
  • 一般在包一级,如果类型名字首字符大写,则包外也可以使用
  • 两个底层类型相同但类型名字不同的变量不能直接比较混用等.
// Celsius 的 String 方法, 当 fmt 的包打印时,优先使用类型对应的 String 方法
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
var c Celsius = 1.0
fmt.Printf("%v\n")

2.6 包和文件

  • 一个 package 是一组代码的集合,用于代码的封装和复用.
  • 一个 module 则是一组相关的 packages,用于解决依赖管理的问题.
  • 为了让 VS Code 识别到 Go Modules,需要在 golearning 目录下创建一个 go.mod 文件,命令行输入 go mod init golearning
  • 包的初始化
    • 用表达式初始化
    • func init() { /*...*/ }

3.2 浮点数

  • math.NaN(), math.IsNaN() NaN 与任何数都不相等

3.5 字符串

  • len(s) 返回字符串中字节数
  • s[i:j] 基于 s 的 i 到 j (不含) 个字节生成新字符串
  • s[0] = 'L' 是被禁止的, 因为字符串是不可修改的
  • 原生字符串的形式是用反引号代替双引号
  • rune 是 int 32 的等价类型, 对应 Unicode 码点
  • for i, r := range s 隐式解码 utf-8
  • rune 大小一致, 方便切割
s := "プログラム"
fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0"
r := []rune(s)
fmt.Printf("%x\n", r)  // "[30d7 30ed 30b0 30e9 30e0]"
  • bytes 和 strings 实用函数
func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) string

func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte
  • 整数转字符串
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"
  • 字符串转整数
x, err := strconv.Atoi("123")             // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits

4.2 Slice

  • 数组与切片声明
// 声明数组 
var arr [5]int 
var arr2 [...]int{1,2,3}
var arr3 [...]int{1:1,2:2}
// 声明切片 
var slice []int
  • slice 由指针, 长度和容量组成, 作为参数不用复制
func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}
a := [...]int{0, 1, 2, 3, 4, 5}
reverse(a[:])  // 把数组用切片形式传入
fmt.Println(a) // "[5 4 3 2 1 0]"
  • 使用 make 创建指定长度容量的 slice
make([]T, len)
make([]T, len, cap) // same as make([]T, cap)[:len]
  • append 函数
    • 原理
func appendInt(x []int, y int) []int {
    var z []int
    zlen := len(x) + 1
    if zlen <= cap(x) {
        // There is room to grow.  Extend the slice.
        z = x[:zlen]
    } else {
        // There is insufficient space.  Allocate a new array.
        // Grow by doubling, for amortized linear complexity.
        zcap := zlen
        if zcap < 2*len(x) {
            zcap = 2 * len(x)
        }
        z = make([]int, zlen, zcap)
        copy(z, x) // a built-in function; see text
    }
    z[len(x)] = y
    return z
}
  • 使用 runes = append(runes, r) , 因为你不知道是否更改了底层结构, 并且需要更新长度和容量

  • 实际的 append 可以追加多个元素, 甚至可以加一个 slice

  • slice 技巧

// 返回原slice上不包含空串的列表
func nonempty(strings []string) []string {
	i := 0
	for _, s := range strings {
		if s != "" {
			strings[i] = s
			i++
		}
	}
	return strings[:i]
}
// 输入的slice和输出的slice会共享同一个底层数组
// 一般这么用
data = nonempty(data)

// 模拟栈
stack = append(stack, v)
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]

// 移除某个元素
func remove(slice []int, i int) [] int {
	copy(slice[i:], slice[i+1:]) // 将后面的子slice向前依次移动一位
	return slice[:len(slice)-1]
}

4.4 结构体

  • 可以用 ==!= 比较
  • 字面值的两种写法
p := Point{1, 2}
q := Point{a: 1, b: 2}
  • 嵌入和匿名成员
type Point struct {
	X, Y int // 大写, 可导出
}

type Circle1 struct {
	Center Point // 嵌入
	Radius int 
}

var c1 Circle1
c1.Center.X = 1

type Circle2 struct {
	Point // 省略名, 只用数据类型
	Radius int 
}

var c2 Circle2
c2.X = 1

4.5 JSON

  • 结构体
type Movie struct {
    Title  string // 大写表示可导出, 才能被编码
    Year   int  `json:"released"`
    Color  bool `json:"color,omitempty"`
    Actors []string
}
  • 编码
data, err := json.Marshal(movies) // 返回字节slice
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)


data, err := json.MarshalIndent(movies, "", "    ") // 便于阅读
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
  • 解码
var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"
  • 使用流解码
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
	resp.Body.Close()
	return nil, err
}

4.6 文本和 HTML 模板

  • 将变量值填充到文本或 HTML 格式的模板
  • text/template
  • html/template

5.1 函数声明

  • 两种格式
func add(x int, y int)int {return x+y}
func sub(x, y int) (z int) {z=x-y return}

5.4 错误

  • 传播错误
resp, err := http.Get(url)
if err != nil {
	return nil, err
}
  • 带前缀信息的传播错误
if err != nil {
	return nil, fmt.Errorf("parsing %s as HTML : %v", url, err)
}
  • 输出信息并结束程序
if err := WaitForServer(url); err != nil {
	// 1
	fmt.Fprintf(os.Stderr, "Site is donw: %v\n", err)
	os.Exit(1)
	// 2
	log.SetPrefix("wait: ")
	log.SetFlags(0)
	log.Fatalf("Site is down: %v\n", err)
}
  • 只输出错误信息
// 1
log.Printf("ping failed: %v", err)
// 2
fmt.Fprintf(os.Stderr, "ping failed: %v", err)
  • 文件结尾错误 (EOF)
in := bufio.NewReader(os.Stdin)
for{
	r, _, err := in.ReadRune() 
	// package io  var EOF = errors.New("EOF")
	if err == io.EOF {
		break
	}
	if err != nil {
		return fmt.Errorf("read failed: %v", err)
	}	
}

5.5 函数值

  • 函数和其他值一样, 拥有类型, 可以赋值, 传递给函数, 从函数返回
func fa(n int) int {return n}
func fb(n int) int {return n*n}
func fc(m, n int) int {return m*n}

f:=a; f(1)
f=b; f(2)
f=c // compile error 类型不符
  • 零值是 nil
var f func(int) int
f(1) // panic
if f != nil {
	f(1)
}
  • Map
func add1(r rune) rune { return r+1 }
fmt.Println(strings.Map(add1, "abc")) // bcd

5.6 匿名函数

  • 例子
strings.Map(func(r rune) rune {return r+1}, "abc")
  • 闭包 匿名函数通过引用获得局部变量
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()) // 2
	fmt.Println(f()) // 3
}
  • 闭包应用

    • 数据封装和私有变量
    • 持久化变量
    • 高阶函数
  • 捕获迭代变量

var rmdirs []func()
for _, d := range tempDirs() {
	dir := d // 因为匿名函数记录的是当时的引用(内存地址)
	os.MkdirAll(dir, 0755)
	rmdirs = append(rmdirs, func() {
		os.RemoveAll(dir)
		// 这里并没有调用匿名函数, 只是把函数装在了rmdirs里, 匿名函数在记录了dir的引用
}) // 利用匿名函数捕获 dir
}
// ...do some work ...
for _, rmdir := range rmdirs {
	rmdir()
	}// 使用匿名函数删除文件夹

5.7 可变参数

  • sum ()
func sum(vals ...int) int {
	total := 0
	for _, val := range vals {
		total += val	
	}
	return total
}

5.8 defer

  • 立刻对其参数求值, 函数本身在包含 defer 的函数执行完毕后执行
func foo() {
	i := 0
	defer fmt.Println(i) // 0
	i++
	return
}