前言

Go起源

  • Ken Thompson、Rob Pike、Robert Griesemer:为了解决21世纪多核和网络环境越来越复杂的问题。
  • “C类似语言”、”21世纪的C语言”
  • 自动垃圾回收、包系统、函数一等公民、词法作用域、系统调用接口、原生支持Unicode。
  • 静态、编译。简单的类型系统。
  • 没有隐式数值转换、没有构造函数和析构函数、没有运算符重载、没有默认参数、没有继承、没有泛型、没有异常、没有宏、没有函数修饰等。

入门

Hello, World

package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}
  • main函数是程序执行的入口
  • 函数的声明由func关键字、函数名、参数列表、返回值列表、函数体组成。

命令行参数

for initialization; condition; post {
    // zero or more statements
}
  • 空标识符 _ 用于语法需要逻辑不需要的时候。

练习1.1

package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    fmt.Println(strings.Join(os.Args[0:], " "))
}

练习1.2

package main

import (
    "fmt"
    "os"
)

func main() {
    for i, arg := range os.Args[1:] {
        fmt.Println(i, arg)
    }
}

查找重复的行

  • 函数和包级别的变量可以以任意顺序声明,并不影响其被调用。
  • 常量声明的值必须是一个数字值、字符串、布尔值。
  • map作为参数传递给某函数时,该函数接受这个引用的一份拷贝,类似c++中的引用传递,实际上指针是另一个指针,但指向同一块内存。
%d 十进制整数
%x, %o, %b 十六进制,八进制,二进制
%f, %g, %e 浮点数
%t 布尔
%c 字符
%s 字符串
%v 变量的自然形式
%T 变量的类型

获取URL

练习1.7

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    for _, url := range os.Args[1:] {
        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)
        _, err1 := io.Copy(os.Stdout, resp.Body)
        resp.Body.Close()
        if err1 != nil {
            fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err1)
            os.Exit(1)
        }
        fmt.Printf("%v", os.Stdout)
    }
}

本章总结

  • Go语言里没有指针运算,不能像c语言里对指针进行加减操作。
  • 在源文件开头和函数开头写注释是个好习惯,会被godoc之类的工具检测到。

程序结构

命名

  • 命名规则:以字母或下划线开头,后面可以跟任意数量的字母、数字、下划线。大小写字母是不同的。
  • 在函数内部定义的变量只在函数内部有效,在函数外部定义的变量在整个包内有效。
  • 包级变量如果以大写字母开头则可以被外部包访问,小写开头的变量则不行。
  • 推荐使用 驼峰式 命名。

声明

  • 每个源文件以包的声明开始,之后是import语句导入依赖的其他包,之后是包一级的类型、变量、常亮、函数的声明,包一级的各种类型声明顺序无关紧要。
  • 包一级的变量可以在包对应的所有源文件中访问,而不仅仅是在声明它的源文件中访问。

变量

  • 变量声明的语法:var 变量名称 变量类型 = 表达式
  • 包级别声明的变量在main函数执行前初始化,局部变量在执行到的时候初始化。
  • 简短变量声明左边的变量可能之前声明过的,这些变量是赋值行为,注意简短变量声明中必须要有新变量。
  • 在go中返回局部变量的地址是安全的。
  • 变量的生命周期:包一级的变量和整个程序的运行周期一致;局部变量的生命周期是动态的:从创建开始到不再被引用为止。
类型 零值
数值 0
布尔 false
字符串 空字符串
接口、引用(slice/map/chan/函数) nil
数组、结构体 每个元素或字段的零值

赋值

  • 自增自减是语句而不是表达式,因此x = i++之类的语句是错误的。
  • 可赋值性的简单规则:类型必须完全匹配,nil可以赋值给任何指针或者引用类型的变量。

类型

  • 类型定义了数值在内存中的存储大小、如何组织的、支持的操作等。
  • 类型声明语句:type 类型名字 底层类型;
  • 对于每一个类型T,都有一个类型转换操作T(x),用于将x类型转换为T类型。转换的前提是两种类型拥有相同的底层类型。

包和文件

  • 目的是为了支持模块化、封装、单独编译和代码重用。
  • 每个包对应一个独立的命名空间。
  • 一个包通常只有一个源文件有包注释。

基础数据类型

Go语言数据类型分类:基础类型、符合类型、引用类型、接口类型。基础类型包括:数字、字符串、布尔类型;符合类型包括:数组、结构体;引用类型包括:指针、切片、map、函数、通道。

整型

  • 有符号整型:int8,int16,int32,int64分别对应8/16/32/64位有符号整数。
  • 无符号整型:uint8,uint16,uint32,uint64分别对应8/16/32/64位无符号整数。
  • int和uint是32位还是64位取决于编译器和机器平台。
  • rune类型和int32类型等价,byte类型和uint8类型等价。

浮点型

  • float32类型大约6个十进制精度、float64类型大约15个十进制精度。
  • %g %e %f用于打印浮点数。

复数

  • 类型:complex64/complex128分别对应float32和float64的精度。
  • 定义:var x complex128 = complex(1, 2) // 1 + 2i.或者 x := 1 + 2i.
  • 返回复数的实部和虚部分别用内建函数real和imag.

布尔型

  • 布尔型的值只有两个:true和false.
  • 布尔类型可以和&&、   操作符进行运算。如果运算符左边的值可以确定整个表达式的值,则运算符右边的值不再执行。
  • 布尔值不会隐式转换为0和1.

字符串

  • 字符串是不可改变的序列。
  • 第i个字节并不一定是字符串的第i个字符,对于飞ASCII字符的UTF-8编码会要两个或多个字节表示一个字符。
  • s[i:j]基于原始字符串的第i个字节开始到第j个字节(不包括j本身)生成一个新的字符串。
  • 讲一个整数转型为字符串意思是生成对应Unicode码点的UTF-8字符串。

常量

  • 常量表达式的值在编译期计算,而不是在运行期。
  • 常量之间的算数运算、逻辑运算、比较运算的结果也是常量。
  • 无类型常量
  • 无类型整数常量默认转换为int,浮点数和复数常量默认转换为float64和complex128.

复合数据类型

数组是由同构的元素组成,结构体是由异构的元素组成。

数组

  • 数组是由一个固定长度的特定类型元素组成的序列,由于数组的长度是固定的,在Go中很少使用数组,而是和它对应的slice.
  • 数组的长度是数组类型的一部分,因此[2]int和[3]int是不同的类型。
  • 数组的长度必须是常量表达式,数组的长度在编译时确定。

Slice

  • slice代表变长的序列,序列中每个元素有相同的类型,一个slice类型通常写作[]T.
  • 构成:指针、长度、容量。
  • slice之间不能像数组一样直接比较。
  • 内置的make函数创建一个制定元素类型、长度和容量的slice.

Map

  • 无序key/value对的集合。常数时间检索、更新、删除。
  • key必须是支持==比较运算符的类型。将浮点数作为key是个不好的选择。
  • m := make(map[string]int)
  • m := map[string]int{}创建一个空map.
  • 如果查找失败则会返回value类型的零值。
  • 禁止对map元素取地址。map随着元素增长可能重新分配内存地址。
  • for range对map迭代,顺序是不确定的。
  • map的绝大部分操作,包括查找、删除、len、range循环都可以安全工作在nil值的map上。但是向nil值的map存入元素将导致panic异常。

结构体

  • 结构体是一种聚合类型,由零个或多个任意类型聚合而成。
  • 结构体成员的输入顺序也有重要意义,成员的声明顺序不同定义不同的结构体类型。
  • 名为s的结构体不能再包含s类型的成员,但是可以包含*s类型的成员,比如用于二叉树。
  • 结构体零值是每个成员对应都是零值。
  • 如果要在函数内部修改结构体成员,必须用指针传入。
  • 如果结构体的全部成员是可比较的,那么结构体也可以比较,可以作为map的key.
  • 结构体嵌入和匿名成员。外层的结构体不仅获得匿名成员的所有成员,而且获得的匿名成员的所有方法。

JSON

  • 一种用于发送和接受结构化信息的标准协议。

文本和HTML模板

函数

函数声明

  • 函数声明包括函数名、参数列表、返回值列表(可省略)、函数体。
  • Go语言没有默认参数值。
  • 实参通过值传递,因此对形参的修改不会影响实参。如果实参包括引用类型,比如指针、slice、map、fuction、channel等类型,实参可能会由于函数的间接引用被修改。
  • 没有函数体的函数声明表示函数不是以Go实现的。

多返回值

  • 在Go中一个函数可以返回多个值。许多标准库函数返回两个值,一个是期望得到的值,一个是函数出错时的错误信息。
  • 调用多返回值的函数时,调用者必须显式的将这些值分配给变量,如果某个值不再被使用将其分配给 _
  • 如果一个函数将所有的返回值都显式的命名,则该函数的return语句可以省略操作数,不推荐使用因为这会使得代码难以理解。

错误

  • 内置的error是接口类型,可能是nil或者non-nil。nil意味着函数运行成功,non-nil表示函数运行失败。
  • 当函数返回值是non-nil时其它的返回值是未定义的,在大部分情况下应该忽略这些返回值。
  • 错误处理策略。

函数值

  • 函数被看做第一类值:函数像其它值一样,拥有类型,可以被赋值给其它变量,传递给函数,从函数返回。
  • 函数类型的零值是nil。调用nil值的函数会引发panic异常。
  • 函数之间是不可比较的,不能作为map的key.

匿名函数

  • 闭包的概念。

可变参数

  • 声明可变参数函数时,在参数列表的最后一个参数类型前加上省略符号…表示函数接受任意类型该参数。
  • 可变参数函数和以slice作为参数的函数是不同的。

defer

  • 延迟执行,执行顺序和声明顺序相反。
  • 用于成对的操作:打开、关闭、连接、断开连接、加锁、释放锁。

panic异常

  • panic一般用于严重错误,大部分情况应该使用go提供的错误机制。

recover捕获异常

方法

封装和组合

方法声明

  • 在函数声明时,在名字前放上一个变量,就是一个方法。这个附加的参数会将函数附加到这种类型上。附加的参数称为接受者。
  • 方法不仅可以定义在struct类型上,数值、字符串、slice、map等也可以定义方法。方法可以被声明到任意类型,除了指针和interface.

基于指针对象的方法

  • 当调用一个函数时,会对每一个参数进行拷贝,如果需要更新变量或者参数太大为了避免拷贝,需要用到指针。
  • 一般约定,如果一个类型有一个指针作为接受者的方法,那么这个类型的其它方法也要用指针接受者。
  • 如果t是一个T类型的变量,Method方法被声明为指针接受者,t.Method()可以编译通过,编译器会隐式的用&t调用这个方法。
  • 不管method的receiver是指针类型还是非指针类型,都可以通过指针/非指针类型调用,编译器会做类型转换。

通过嵌入结构体扩展类型

方法值和方法表达式

封装

  • 封装:变量和方法对调用者不可见。

接口

接口是合约

  • 接口类型是一种抽象类型。

接口类型

  • 接口类型描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。
  • 接口内嵌

实现接口的条件

  • 一个类型如果实现了一个接口的所有方法,那么这个类型就实现了这个接口。
  • interface{}被称为空接口,可以将任意类型赋值给空接口。