入门

  • go run编译一个或者多个以.go结尾的源文件,链接库文件,并运行最终生成的可执行文件。
  • go build会编译程序,并且保存编译结果。会生成可执行的二进制文件。
  • 每个源文件都以一条package声明语句开始。表示该文件属于哪个包。
  • main包定义了一个独立可执行的程序而不是一个库。main函数是程序执行时候的入口。
  • gofmt将代码格式化为标准格式。go fmt会对包,默认为当前目录的.go源文件应用gofmt命令。
  • goimports可以根据代码需要, 自动地添加或删除import声明。

命令行参数

  • 程序的命令行参数可从os包的Args变量获取,os.Args变量是一个字符串的切片
  • os.Args[0], 是命令本身的名字
  • s := "" 短变量声明,只能用在函数内部,不能用于包变量。可以使用var s string, var s = "", var s string = ""
  • stringsJoin函数: strings.Join(os.Args[1:], " ")

查找重复的行

  • map中的键,要能够用==运算符比较。map的迭代顺序不确定。
  • %v 变量的自然形式
  • 读取输入

    1
    2
    3
    4
    5
    6
    7
    import "bufio"
    // func 下
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
    counts[input.Text()]++
    }
  • 函数参数为map的时候,map会以引用的拷贝来传递,被调用的函数对map的数据结构的修改,调用者函数的map也会相应变化。实际上传递的是目前map指针的拷贝,是另外一个指针,但是内部存储的值是指向同一块内存。

  • 一行一行读取文件,以流模式读取输入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import "os"
    // func 下
    f, err := os.Open(filename)
    if err != nil {
    return
    }
    input := bufio.NewScanner(f)
    for input.Scan() {
    counts[input.Text()]++
    }
  • 读取整个文件的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import "io/ioutil"
    data, err := ioutil.ReadFile(filename)
    if err != nil {
    return
    }
    // ReadFile返回字节切片,必须转换为String,才能用Split分割
    for _, line := range strings.Split(string(data), "\n) {
    counts[line]++
    }

获取URL

  • struct是一组值或者叫字段的集合。
  • http.Get返回的resp.Body是一个可读的服务器响应流,使用ioutil.ReadAll函数从resp中读取到所有内容,然后使用resp.Body.Close关闭流。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    resp, err := http.Get(url)
    if err != nil {
    fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
    os.Exit(1)
    }
    b, err := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    if err != nil {
    fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
    os.Exit(1)
    }

并发获取多个URL

  • nbytes, err := io.Copy(ioutil.Discard, resp.Body) 会将响应的Body内容拷贝到ioutil.Discard输出流中,这个是一个垃圾桶。返回字节数。
  • 使用channel: ch := make(chan string)

Web服务

  • 发送到这个服务的请求是一个http.Request类型的对象,这个对象中包含了请求中的一系列相关字段
  • 服务器每一次接收请求处理时都会另起一个goroutine,这样服务器就可以同一时间处理多个请求。
  • 共同的接口,被调用需要一个标准流输出时都可以满足。这个接口叫作io.Writer
  • 方法是和命名类型关联的一类函数。Go语言里比较特殊的是方法可以被关联到任意一种命名类型。
  • 源代码文件或者函数开头之前写注释,可以被godoc检测到。

程序结构

命名,声明和变量

  • 驼峰式命名
  • var, const, type, func,type是类型
  • 包一级声明语句声明的名字可在整个包对应的每个源文件中访问。
  • var 变量名字 类型 = 表达式, var i, j, k int
  • 接口或者引用类型变量对应的零值是nil。数组或者结构体聚合类型对应的零值是每个元素或者字段对应该类型的零值。
  • 指针对应的数据类型是*int*p表示读取指针指向的变量的值。
  • 指针的零值是nil
  • 指针包含了变量地址,将指针作为参数调用函数,将来可以在函数中通过指针来更新变量的值。
  • new(T) 将创建一个T类型的匿名变量,初始化T类型的零值,然后返回变量地址,返回的指针类型为*T
  • 从每个包级变量和每个当前运行函数的每一个局部变量开始,通过指针或者引用的访问路径遍历,是否可以找到该变量。
  • 在函数退出后,如果局部变量被全局变量引用了,那么该局部变量就是从函数中逃逸了,那么它将会被在堆中分配内存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var global *int
    func f() {
    var x int
    x = 1
    global = &x
    }
    func g() {
    y := new(int)
    *y = 1
    }
  • 如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收

赋值

  • 以下三个操作会返回ok

    1
    2
    3
    v, ok = m[key] // map
    v, ok = x.(string) // 类型断言
    v, ok = <-ch // channel
  • nil可以赋值给任何指针或者引用类型的变量。

  • 两个值要想比较,那么第二个值必须是对第一个值类型对应的变量是可以赋值的。

类型

  • 一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。比如type Celsius float64摄氏温度
  • 如果两个值有着不同的类型,那么就不能直接进行比较。
  • 使用fmt包的打印方法时候,将会优先使用该类型对应的String方法返回的结果。

包和文件

  • 每个包都有一个全局唯一的导入路径。比如gopl.io/ch2/tempconv
  • 另外,每个包都有包名,一般是导入路径的最后一个字段。
  • 包的初始化首先是解决包级变量的依赖顺序,按照包级变量声明出现的顺序依次进行初始化。
  • 可以使用init函数来进行初始化函数,每个文件可以包含多个init初始化函数。按照声明的顺序被自动调用。
  • 导入的包,每个包只会被初始化一次。
  • 如果for i := range pc,是省略了第二个值,也可以是这样for i, value := range pc

作用域

  • 作用域是一个编译时候的属性,生命周期是程序运行时候变量存在的有效时间段。
  • 语法块(花括弧包起来的)内部声明的名字是无法被外部语法块访问的。for循环的初始化,条件测试和循环后的部分都是隐式的词法域。
  • 导入的包是源文件级的作用域,只能在当前的文件中访问导入的包。

基础数据类型

  1. 基础类型 - 数字,字符串和布尔
  2. 复合类型 - 数组,结构体
  3. 引用类型 - 指针,切片,字典,函数,通道
  4. 接口类型

整型

  • Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。
  • byte是unit8的等价类型,用于强调数值是一个原始数据而不是小整数。
  • 无符号整数类型uintptr,容纳指针。
  • 需要将int当作int32类型的时候需要一个显式的类型转换。
  • /运算符的行为依赖操作数是否全为整数。
  • 许多整形数之间的相互转换并不会改变数值,它们只是告诉编译器如何解释这个值。
  • 字符面值通过单引号来包含对应字符。%q来打印单引号字符
  • 浮点数的相等比较是有问题的,切记
  • 数字到布尔型必须进行转换i != 0

字符串

  • 文本字符串被解释为采用UTF8编码的Unicode码点(rune)序列
  • 如果试图访问超出字符串索引范围的字节会导致panic异常
  • 字符串的值是不可变的
  • len函数回返回字符串中的字节数目,而非ASCII字符的UTF8编码会要两个或者多个字节。所以第i个字节并不一定是第i个字符。