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
}