IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Go程序设计语言读书笔记

    youngxhui发表于 2021-05-21 12:39:17
    love 0

    重新系统学习 Golang。之前学习Golang都是用什么学什么,不系统,不全面,很多知识点一知半解。这次通过阅读 《Go程序设计语言》这本书来系统的学习一下。

    命令行参数

    这是我第一次知道 Golang 其实是可以从启动是直接输入参数的,之前看到很多库都是使用 flag,也让我一度认为启动时传入参数必须使用 flag,比较这也好理解,谁让 Golang 的 main 函数和 java 或者 c 不太一样呢?

    1
    2
    3
    4
    5
    
    // java
    public static void main(String[] args)
    
    // c
    int main(int argc, char *argv[])
    

    Golang 直接 func main() ,想传入参数也不知道如何进行。

    原来 Golang 是有个 os.Args 这个方法,Args的底层数据结构是一个字符串切片 var Args []string,这样就方便的获取到从命令行输入的参数。但是这里的 Args[0] 并不是传入的第一个元素,而是该程序的名字。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    func main() {
    	fmt.Println(os.Args[0]) 
        // 输出结果 C:\Users\YOUNGX~1\AppData\Local\Temp\go-build2749142914\b001\exe\echo1.exe
    }
    

    所以要获得该程序从命令行传入的参数应该为 Args[1:],这样可以获取全部传入的值。

    指针

    首先指针未赋值初始化为 nil。

    其他零值

    官方对其他类型做出了规定 The Zero value

    类型 初始值
    bool false
    numeric 0
    string ""
    pointers,functions,interfaces nil
    slices,channels,map nil

    同时指针是可比较的,当且仅当指向同一个变量或者都为 nil 的时候才相同。

    new()

    new() 函数通过传入一个类型,得到该类型的指针。同时每次执行new() 返回的指针值是不一样的。但是有一些例外:两个变量的类型不携带任何信息且是零值,它们有相同的地址,例如struct{}。 1.16 版本实现好像已经更改。

    基本数据类型

    Go 的基本数据类型分为四类:基本类型,聚合类型,引用类型和接口类型。

    • 基本类型:数字,字符串,布尔
    • 聚合类型:数组,结构体
    • 引用类型:指针,slice,map,function,函数,通道

    字符串

    字符串是不可变类型。这一点和 Java 是一致的,但是为什么呢?不可变意味着两个字符串可以安全地公用同一段底层内存,使得复制任何长度字符串的开销都低廉。

    字符串可以与字节 slice 相互转换。

    1
    2
    3
    
    s := "abc"
    b := []byte(s)
    s2  := string(b)
    

    字符串操作包

    之前听过这样一句话,一个语言的成熟与否要看他对字符串的方法多不多。Golang 提供了以下包对字符串进行处理。bytes,strings,strconv,unicode。

    数组

    对于数组来说, [3]int 和 [4]int 是两个不同的数组类型。数组的长度必须是常量表达式(要你有何用?)。

    Golang 的数组还挺有意思的,例如下面这样

    1
    
    symbol := [...]int{99:-1}
    

    表示长度为 100 的数组,其中前99个都是 0,最后一个为 -1。

    Golang 在数组和其他类型上都是值传递。需要引用传递使用指针。

    1
    2
    3
    
    func zero(ptr *[32]byte) {
    	*prt = [32]byte{}
    }
    

    Golang 中的数组和 PHP 的数组还有点相似,可以作为k-v使用,但是 k 只能是数。arr := []string{0: "1", 2: "2"} ,这个数组遍历后的结果为如下:

    1
    2
    3
    
    0 1
    1 
    2 2
    

    会多出一个新的索引 1 。

    Slice

    Slice 的底层为一个可变数组。

    1
    2
    3
    4
    5
    6
    
    // src/runtime/slice.go
    type slice struct {
    	array unsafe.Pointer
    	len   int
    	cap   int
    }
    

    slice 无法用 == 比较,与 nil 可以,标准库中只有 bytes.Equal 比较两个 byte 的slice。 其他的就要自己实现了。(难道是因为没有泛型,作者要写多个实现?)

    Struct

    struct 结构体嵌套,而且匿名嵌套

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    type Point struct {
    	x int
    	y int
    }
    
    type Circle struct {
    	Point
    	Radius int
    }
    
    type Wheel struct {
    	Circle
    	Spokes int
    }
    

    这里的 Point 和 Circle 可以采用这种匿名方式。而调用可以直接调用,像下面这种情况,第3行和第4行为相同的调用。

    1
    2
    3
    4
    5
    
    func main() {
    	var w Wheel
    	w.x = 5
    	w.Circle.Point.x = 5
    }
    

    但是不允许在同一个结构体中定义两个相同类型的匿名成员。

    匿名函数

    匿名函数可以作为函数的参数和返回值。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    package main
    
    import "fmt"
    
    func squares() func() int {
    	var x int
    	return func() int {
    		x++
    		return x * x
    	}
    }
    
    func main() {
    	f := squares()
    	fmt.Println(f())
    	fmt.Println(f())
    	fmt.Println(f())
    	fmt.Println(f())
    }
    

    defer

    之前只用来作为资源关闭的操作,没想到还有 其他sao 操作。

    defer后面可以为一个函数,通过函数调用来实现更多功能。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    func bigSlowOperation() {
    	defer trace("bigSlowOperation")()
    	time.Sleep(10 * time.Second)
    }
    
    func trace(msg string) func() {
    	start := time.Now()
    	log.Printf("enter %s", msg)
    	return func() {
    		log.Printf("exit %s (%s)", msg, time.Since(start))
    	}
    }
    

    使用 defer 也有很多需要注意的地方。例如在循环中使用 defer。这个是无效的,并不能及时的回收资源,最好是将循环体和 defer 封装为一个函数,每次调用函数后会执行 defer。

    宕机与恢复

    宕机会引起程序异常退出。宕机代表的程序执行的终止,但是 Golang 提供了宕机恢复函数 recover。recover 会终止当前的宕机状态,并且返回宕机值。

    方法与函数

    Golang 中对方法和函数有定义,emmmm🦤,方法是特殊的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // 函数
    func name(parameter-list) (result-list) {
        body
    }
    
    // 方法
    func (t Type)name(parameter-list) (result-list) {
        body
    }
    


沪ICP备19023445号-2号
友情链接