the way to go 基础篇

前言

  • go语言诞生条件

    1. 更快的开发速度
    2. 优越的多核工作
    3. 更好的网络编程环境
    4. 享受开发过程
  • go语言追求短小精悍

第一章 Go语言的起源发展与普及

起源与发展

  1. 起源于07年,09年正式发布,Robert Griesemer, Rob Pike, Ken Thompson
  2. 2019/11/10 完全开源Linux和MacOSX
  3. 2010/1/8 当选2009年年度语言
  4. 2010/5 谷歌开始使用
  5. 2011/5/5 Google App Engine支持Go语言
  6. 几个网址
    • 官网 golang.org
    • github.com/golang/go
      *

语言的主要特性和发展的环境和影响因素

  • go语言追求快速编译(Java),高效执行(C++),易于开发(动态语言)
  • go语言是一门类型安全和内容安全的编程语言
  • go语言存在指针,但是不允许指针运算
  • 对于并发编程,网络编程支持极好-goroutine
  • go语言依赖管理采用分包进行
  • go语言和Java一样有GC机制
  • go install部署第三方包

指导设计原则

语言特性

  • go语言在本质上支持并发编程
  • go语言没有类的概念,用interface来实现多态
  • go语言是类型安全的语言,使用静态类型
  • go语言是强类型语言
  • go语言支持交叉编译
  • go语言代码和字符串的编码方式都是utf-8,支持国际化

语言用途

  • go语言用于web服务器,存储集群,分布式场景,游戏服务
  • go语言的目标是时限CEP, complex Event Process
  • go语言不适合开发实时性要求高的软件

特性缺失

  • 不支持函数重载和操作符重载
  • 不支持隐式转换
  • 不支持类和类型的继承
  • 不支持变体类型
  • 不支持动态加载代码
  • 不支持动态链接库
  • 不支持泛型
  • 通过recover和panic来替代异常机制
  • 不支持静态变量

小结

  • 简化问题,易于学习
  • 内存管理,语法简洁,易于使用
  • 快速编译,高效开发
  • 高效执行
  • 并发支持,轻松驾驭
  • 静态类型
  • 标准类库,规范统一
  • 易于部署

第二章 安装与运行环境

平台与架构

  • 编译器支持的平台:
    • Linux
    • FreeBSD
    • Mac OS X
  • 编译器
    • gc
    • gccgo

Go环境变量

  • $GOROOT:go的安装位置
  • $GOARCH:机器的处理器架构
  • $GOOS:操作系统
  • $GOBIN:编译器和链接器的安装位置
  • $GOPATH:包含多个Go语言远吗文件,包文件和可执行文件,src,pkg,bin三个目录
  • $GOARM:ARM专用
  • $GOMAXPROCS:设置可用的处理器核数

Linux安装go

  1. Linux下通过$HOME/.bashrc来自定义环境

    • 首先配置GOROOT:export GOROOT=$HOME/go
    • 配置path:export PATH=$PATH:$GOROOT/bin
    • 配置工作路径:export GOPATH=$HOME/Applications/Go
    • source ~/.bashrc生效
    • go env查看
  2. go源码获取

    1
    2
    3
    4
    5
    wget https://storage.googleapis.com/golang/go<VERSION>.src.tar.gz
    tar -zxvf go<VERSION>.src.tar.gz
    sudo mv go $GOROOT
    cd $GOROOT/src
    ./all.bash
  3. go version查看版本信息

安装目录

.
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── PATENTS
├── VERSION
├── api
├── bin     // 可执行文件
├── doc     // 文档
├── favicon.ico
├── lib     // 文档模板
├── misc    // 编辑器相关内容
├── pkg
├── robots.txt
├── src     // 完整源码
└── test

go runtime

  • runtime相当于Java的虚拟机,负责内存分配,垃圾回收,栈处理,goroutine,channel,slice,map,reflect等
  • go的gc采用标记-清除回收器

第三章 Go开发环境的基本要求

调试器

  1. 打印语句,print,println,fmt.Println,fmt.Printf
  2. %+v, %#v,%T打印实例的不同信息
  3. 使用panic获取堆栈信息
  4. defer关键字来跟踪代码执行
  5. go build 编译
  6. go install 编译并安装

格式化代码 - gofmt

代码文档 - go doc

  • go doc package 获取包的文档注释,例如:go doc fmt 会显示使用 godoc 生成的 fmt 包的文档注释。
  • go doc package/subpackage 获取子包的文档注释,例如:go doc container/list。
  • go doc package function 获取某个函数在某个包中的文档注释,例如:go doc fmt Printf 会显示有关 fmt.Printf() 的使用说明

第四章 语言的核心结构和技术

文件名、关键字与标识符

  • 文件名以.go结尾
  • _空白标识符
  • go的25个关键字
关键字
break default func interface selcet
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
  • 36个预定义标识符,函数
预定义标识符
append bool byte cap close complex complex64 complex128 unit16
copy false float32 float64 image int int8 int16 uint32
int32 int64 iota len make new nil panic unit64
print println real recover string true uint uint8 uintptr
  • go程序一般由关键字,常量,变量,运算符,类型和函数组成
  • 分隔符 (), [], {}
  • 标点.、,、;、:、...等,但是不用;结尾

Go程序的基本结构要素

包-package

  • 包是结构化的一种方式,每个程序都有包的概念组成,一个包可以由多个.go文件组成
  • 在源文件的第一行指明这个文件属于哪个包
  • 一个包的源文件必须一起编译,包是编译的单元
  • 对一个包进行更改或者重新编译,所有引用了这个包的客户端程序都必须全部重新编译
  • go中包模型采用显示依赖关系机制来打到快速变异的目的
  • 包引入

    1
    2
    3
    4
    5
    6
    7
    import "fmt"
    import "os"
    import (
    "fmt"
    "os"
    )
  • 包名不是以.或者/开头,会在全局文件进行查找,以./开头会在相对目录中查找,以/开头会在绝对路径中查找

  • 可见性规则:标识符的大小写区别
  • 包可以用别名进行区分

函数

  • 定义格式

    1
    2
    3
    func functionName(parameter_list) (return_value_list) {
    }
  • go语言是以;作为语句的结尾,只是由编译器自动完成

  • 内部包命名遵循驼峰命名法,外部调用的遵循Pascal命名法
  • 程序正常退出代码为0,异常退出代码为1

注释

  • // - 单行注释
  • /* */ - 多行注释
  • 每个包都应该有注释,package关键字之前的注释默认为对包的说明
  • 全局作用于的类型、变量、常量、函数、对象都应该有一个合理的注释

类型

  • 类型定义了某个变量的值的集合和可对其进行操作的集合
  • 基础类型:int、float、bool、string、
  • 复合类型:struct、array、slice、map、channel
  • 描述行为的:interface
  • 函数可以作为类型返回

go程序的一般结构

  • 完成包的导入
  • 常量,变量,类型的定义或声明
  • 如果存在init函数,对改函数进行定义,每个含有该函数的文件都会先执行init函数
  • 如果包为main包,则定义main函数
  • 定义其他的函数,首先是类型的方法,函数名字可以按照字母顺序来排列

  • go语言执行顺序:

    • 按顺序导入所有被main包引入的其他包
    • 如果包中引入了其他包,递归执行包的引入
    • 最新引入的包首先开始常量和变量的初始化
    • init
    • main

类型抓换

  • 显示转换
  • a := b(a)

常量

  • const关键字定义
  • 类型:bool,int,float,complex,string
  • const Pi = 3.1415926
  • 常量的值必须在编译时就能够确定
  • 数字型的常量是没有大小和符号的,不会溢出
  • iota特性

变量

  • 实例
    var a string

    (
    1
    2
    3
    4
    a int
    b bool
    str string
    )
  • 变量被声明之后,系统会自动赋给该变量零值0,0.0, false,’’, nil

  • 驼峰命名法
  • 全局变量和局部变量
  • 变量的声明和赋值可以一起进行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a = 15
    var b = false
    var str = "Go says hello to the world!"
    var (
    a = 15
    b = false
    str = "Go says hello to the world!"
    numShips = 50
    city string
    )
  • 通过runtime获取运行环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package main
    import (
    "fmt"
    "runtime"
    "os"
    )
    func main() {
    var goos string = runtime.GOOS
    fmt.Printf("The operating system is: %s\n", goos)
    path := os.Getenv("PATH")
    fmt.Printf("Path is %s\n", path)
    }

值类型和引用类型

  • 指针 &i
  • 一个引用类型的变量r1存储的是r1的值所在的内存地址,或内存地址中第一个字所在的位置
  • go语言中指针,slice,maps,channel都是引用类型

打印

  • fmt.Printf
  • %s-字符串
  • %v-类型默认输出格式
  • %T-类型

:=赋值操作符

  • a := 50,类型会自动判断
  • 函数体内的首选方案,但是不能用于全局变量
  • a, b, c := 5, 5.5, "5"
  • a, b = b, a 交换值
  • _抛弃不要的值

init函数

  • 不能被人为调用
  • 执行优先级高于main
  • 每个源文件只包含一个init函数
  • init中可以定义变量,检查数据,调用后台goroutine

基本类型和运算符

bool类型

  • var b bool = true
  • ==, !=
  • go语言只有类型相同的两个值才可以进行比较
  • !、&&、||,其中&&||
  • 布尔值的命名推荐以is开头,如isSorted,isFinished等

数字类型

  • int,uint - 4个字节或者8个字节
  • uintptr - 存放一个指针的长度

  • 整数:

    • int8(-128 -> 127)
    • int16(-32768 -> 32767)
    • int32(-2,147,483,648 -> 2,147,483,647)
    • int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
  • 无符号整数:
    • uint8(0 -> 255)
    • uint16(0 -> 65,535)
    • uint32(0 -> 4,294,967,295)
    • uint64(0 -> 18,446,744,073,709,551,615)
  • 浮点型(IEEE-754 标准):

    • float32(+- 1e-45 -> +- 3.4 * 1e38)
    • float64(+- 5 1e-324 -> 107 1e308
  • 前缀0表示8进制数

  • 前缀0x表示16进制数
  • e表示10的次方
  • go语言中变量之间不可以混用,常量可以,变量之间需要显示转换
  • 格式化占位符

    • %d - 整数
    • %x,%X - 16进制数字
    • %g - 浮点数
    • %f - 输出浮点数
    • %e - 输出科学计数法数
    • %n.mg - 数字n精确到m位
  • 位运算 - 用于整数类型的变量,并且要求变量等长

    • 按位与&
    • 按位或|
    • 按位异或^
    • 位清除&^将指定位置上的值设置为0
    • 按位补足^
    • 位左移<<
    • 位右移>>
  • 逻辑运算符

    • ==
    • !=
    • <
    • <=
    • >
    • =

  • 算数运算符

    • +
    • -
    • *
    • /
    • %
    • -=
    • +=
    • *=
    • /=
    • ++,–等操作可以用于语句,但是不可以用于表达式 a[i] = b[i++]是不允许的
  • 随机数 - rand包

运算符优先级

1
2
3
4
5
6
7
8
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||

类型别名

  • type TZ int TZ是int的别名
  • 别名不会拥有原类型的方法

字符串

  • utf-8编码中每个字符占据字节数不确定,go中字符可能占据1-4个字节
  • go语言支持两种字面值
    • 解释字符串
      • \n
      • \r
      • \t
      • \u
      • \\
    • 非解释字符串-普通字符串
  • 获取字符串的字节内容:str[i]
  • 拼接符+
  • 效率: bytes.Buffer > strings.Join > +

strings和strconv包

前缀和后缀

  • HasPrefix - 前缀

    1
    2
    3
    4
    // HasPrefix tests whether the string s begins with prefix.
    func HasPrefix(s, prefix string) bool {
    return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
    }
  • HasSuffix - 后缀

1
2
3
4
// HasSuffix tests whether the string s ends with suffix.
func HasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

字符串包含关系

1
2
3
4
// Contains reports whether substr is within s.
func Contains(s, substr string) bool {
return Index(s, substr) >= 0
}

判断子字符串或字符在父字符串中出现的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Index returns the index of the first instance of substr in s, or -1 if substr is not present in s.
func Index(s, substr string) int {
}
// LastIndex returns the index of the last instance of substr in s, or -1 if substr is not present in s.
func LastIndex(s, substr string) int {
}
// IndexRune returns the index of the first instance of the Unicode code point
// r, or -1 if rune is not present in s.
// If r is utf8.RuneError, it returns the first instance of any
// invalid UTF-8 byte sequence.
func IndexRune(s string, r rune) int {
switch {
case 0 <= r && r < utf8.RuneSelf:
return IndexByte(s, byte(r))
case r == utf8.RuneError:
for i, r := range s {
if r == utf8.RuneError {
return i
}
}
return -1
case !utf8.ValidRune(r):
return -1
default:
return Index(s, string(r))
}
}

字符串替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Replace returns a copy of the string s with the first n
// non-overlapping instances of old replaced by new.
// If old is empty, it matches at the beginning of the string
// and after each UTF-8 sequence, yielding up to k+1 replacements
// for a k-rune string.
// If n < 0, there is no limit on the number of replacements.
func Replace(s, old, new string, n int) string {
if old == new || n == 0 {
return s // avoid allocation
}
// Compute number of replacements.
if m := Count(s, old); m == 0 {
return s // avoid allocation
} else if n < 0 || m < n {
n = m
}
// Apply replacements to buffer.
t := make([]byte, len(s)+n*(len(new)-len(old)))
w := 0
start := 0
for i := 0; i < n; i++ {
j := start
if len(old) == 0 {
if i > 0 {
_, wid := utf8.DecodeRuneInString(s[start:])
j += wid
}
} else {
j += Index(s[start:], old)
}
w += copy(t[w:], s[start:j])
w += copy(t[w:], new)
start = j + len(old)
}
w += copy(t[w:], s[start:])
return string(t[0:w])
}

重复字符串

1
2
3
4
5
6
// Repeat returns a new string consisting of count copies of the string s.
//
// It panics if count is negative or if
// the result of (len(s) * count) overflows.
func Repeat(s string, count int) string {
}

修改字符串大小写

  • strings.ToLower(s) string
  • strings.ToUpper(s) string

修剪字符串

  • strings.TrimSpcae(s)-剔除字符串开头和结尾的空白
  • strings.Trim(s, “cut”)-剔除字符串开头和结尾的指定字符
  • TrimLeft,TrimRight

分割字符串

  • strings.Fields(s) - 根据空白字符分割
  • strigns.Split(s, sep) - 根据sep来分割字符串

拼接slice到字符串

  • strings.Join(sl []string, sep string) string - 将slice使用分隔符sep来拼接成一个字符串

从字符串中读取内容

  • strings.NewReader(str)
  • Read()
  • ReadByte()
  • ReadRune()

字符串和其他类型的转换

  • strconv.Itoa(i int) string - int转换成字符串
  • strconv.FormatFloat() string
  • strconv.Atoi(s string) (int, error) 字符串到int
  • strconv.ParseFloat(s string, bitSize int) (f float, err error)

时间和日期

  • time包
  • time.Now
  • time.Day()
  • time.Minute()
  • time.Duration()

指针

  • 取地址符&
  • 一个指针变量可以指向任何一个值的内存地址
  • 通过指针来实现对原来变量值的修改
  • 指针可以用来传递变量的引用,而不是变量的值的拷贝,节省开销

第五章 控制结构

5.1 if-else结构

1
2
3
4
5
6
7
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
  • 判断一个字符串是否为空
1
2
3
4
5
if str == "" {
}
if len(str) == 0 {
}
  • 判断go运行的操作系统
1
2
3
if runtime.GOOS == "windows" {
} else {
}

测试多返回值函数的错误

  • 程序应该在最接近的位置检查所有相关的错误
1
2
3
4
5
6
7
8
9
value, err := pack1.Function1(param1)
if err != nil {
fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
return err
}
if value, ok := readData(); ok {
}

switch结构

  • 使用fallthrough表示执行完当前分支之后继续执行后续分支
  • 分支之间没有break关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
func main() {
var num int = 100
switch num {
case 98, 99:
fmt.Println("..")
case 100:
fmt.Println("..")
default:
fmt.Println("..")
}
}
  • 无初始值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
func main() {
var num1 int = 7
switch {
case num1 < 0:
fmt.Println("Number is negative")
case num1 > 0 && num1 < 10:
fmt.Println("Number is between 0 and 10")
default:
fmt.Println("Number is 10 or greater")
}
}
  • 包含一个初始化语句
1
2
3
4
5
switch a, b := x[i], y[j] {
case a < b: t = -1
case a == b: t = 0
case a > b: t = 1
}

for结构

  • 基于计数器的迭代
1
2
3
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
  • 基于条件判断的迭代
1
2
3
4
for i >= 0 {
i = i - 1
fmt.Printf("The variable i is now: %d\n", i)
}
  • 无线循环 - 服务器
1
2
3
for t, err = p.Token(); err == nil; t, err = p.Token() {
...
}
  • for-range结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "fmt"
func main() {
str := "Go is a beautiful language!"
fmt.Printf("The length of str is: %d\n", len(str))
for pos, char := range str {
fmt.Printf("Character on position %d is: %c \n", pos, char)
}
fmt.Println()
str2 := "Chinese: 日本語"
fmt.Printf("The length of str2 is: %d\n", len(str2))
for pos, char := range str2 {
fmt.Printf("character %c starts at byte position %d\n", char, pos)
}
fmt.Println()
fmt.Println("index int(rune) rune char bytes")
for index, rune := range str2 {
fmt.Printf("%-2d %d %U '%c' % X\n", index, rune, rune, rune, []byte(string(rune)))
}
}

break与continue

  • break关键字可以退出for循环
  • break只会退出一层
  • continue会忽略剩余的循环体跳入下一次循环

标签与goto

  • 标签作用于外部循环
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
  • goto和标签可以来模仿循环
1
2
3
4
5
6
7
8
9
10
func main() {
i:=0
HERE:
print(i)
i++
if i==5 {
return
}
goto HERE
}

第六章 函数

6.1 简介

  • 函数是基本的代码块
  • main写在文件的前面
  • 函数主要是为了代码重用
  • 最后一行或者return退出函数
  • go中包含三种函数
    • 普通带有名字的函数
    • 匿名函数或者lambda函数
    • 方法
  • 出了main,init函数之外,其他所有函数都可以有参数和返回值
  • 参数,返回值,及其类型称为函数签名-和其他语言一致
  • 函数可以将其他函数作为它的参数,类型相符合就可以
  • go语言不允许函数重载
  • 函数是一等公民
  • 函数可以相互比较,引用相同就相等
  • go语言不支持泛型

6.2 函数参数与返回值

  • 函数可以接收参数供自己使用,也可以返回零个或者多个值
  • go语言默认值传递
  • go语言通过指针来实现引用传递,实际上指针也是变量类型,有自己的地址和值,只是指针的值指向一个变量的地址,按引用传递可以理解为按值传递,传递的值就是指针的值。
  • slice,map,interface,channel默认引用传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main() {
fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6))
// var i1 int = MultiPly3Nums(2, 5, 6)
// fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1)
}
func MultiPly3Nums(a int, b int, c int) int {
// var product int = a * b * c
// return product
return a * b * c
}

命名的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import "fmt"
var num int = 10
var numx2, numx3 int
func main() {
numx2, numx3 = getX2AndX3(num)
PrintValues()
numx2, numx3 = getX2AndX3_2(num)
PrintValues()
}
func PrintValues() {
fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
// return x2, x3
return
}
  • 尽量使用命名返回值:会使得代码清晰,更简短,可读性更好。

空白符

  • 空白符用来丢弃一些不需要的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
func main() {
var i1 int
var f1 float32
i1, _, f1 = ThreeValues()
fmt.Printf("The int: %d, the float: %f \n", i1, f1)
}
func ThreeValues() (int, int, float32) {
return 5, 6, 7.5
}

改变外部变量

  • 指针传递不仅可以节省内存,而且赋予函数改变外部变量的能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"fmt"
)
// this function changes reply:
func Multiply(a, b int, reply *int) {
*reply = a * b
}
func main() {
n := 0
reply := &n
Multiply(10, 5, reply)
fmt.Println("Multiply:", *reply) // Multiply: 50
}

6.3 传递变长参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import "fmt"
func main() {
x := min(1, 3, 2, 0)
fmt.Printf("The minimum is: %d\n", x)
slice := []int{7,9,3,5,1}
x = min(slice...)
fmt.Printf("The minimum in the slice is: %d", x)
}
func min(s ...int) int {
if len(s)==0 {
return 0
}
min := s[0]
for _, v := range s {
if v < min {
min = v
}
}
return min
}

6.4 defer和追踪

  • defer语句在函数结束之前才开始执行,适用于资源的释放等操作,类似于finally
  • defer的语句会按顺序接收参数,只是执行在最后
1
2
3
4
5
6
func a() {
i := 0
defer fmt.Println(i)
i++
return
} // print 0
  • 多个defer语句,类似栈,先出现后执行
  • 关闭文件流,解锁,打印最终报告,关闭数据库连接
  • defer可以实现代码追踪,在进入和离开函数时打印日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main
import "fmt"
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}

6.5 内置函数

名称 说明
close 用于管道通信
len 返回某个类型的长度或数量,如字符串,数组,切片,map和管道
cap 返回某个类型的最大容量,如切片,map
new 用于值类型和用户定义的类型的创建
make 用于内置类型的创建
copy,append 用于复制和连接切片
panic,recover 错误处理机制
print, println 底层打印函数
complex, real imag 复数操作

6.6 递归函数

  • 自身调用
1
2
3
4
5
6
func fib(n int) (res int) {
if n <= 1 {
return 1
}
return fib(n - 1) + fib(n - 2)
}
  • 递归的一个问题在于栈溢出
  • go语言中可以使用相互调用的递归函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import (
"fmt"
)
func main() {
fmt.Printf("%d is even: is %t\n", 16, even(16)) // 16 is even: is true
fmt.Printf("%d is odd: is %t\n", 17, odd(17))
// 17 is odd: is true
fmt.Printf("%d is odd: is %t\n", 18, odd(18))
// 18 is odd: is false
}
func even(nr int) bool {
if nr == 0 {
return true
}
return odd(RevSign(nr) - 1)
}
func odd(nr int) bool {
if nr == 0 {
return false
}
return even(RevSign(nr) - 1)
}
func RevSign(nr int) int {
if nr < 0 {
return -nr
}
return nr
}

6.7 将函数作为参数

  • go语言中函数可以作为其他函数的参数进行传递,然后在其他函数内调用执行,称为回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
1
2
3
4
5
6
7
8
func indexFunc(s string, f func(rune) bool, truth bool) int {
for i, r := range s {
if f(r) == truth {
return i
}
}
return -1
}

6.8 闭包

  • 不给函数起名,而是将函数指针赋值给某个变量,通过这个变量来实现函数的调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
func main() {
func() {
sum := 0
for i := 1; i <= 1e6; i++ {
sum += i
}
print(sum)
}()
f()
}
func f() {
for i := 0; i < 4; i++ {
g := func(i int) { fmt.Printf("%d ", i) } //此例子中只是为了演示匿名函数可分配不同的内存地址,在现实开发中,不应该把该部分信息放置到循环中。
g(i)
fmt.Printf(" - g is of type %T and has value %v\n", g, g)
}
}
  • 匿名函数可以被赋值给变量作为值来使用
  • defer关键字和匿名函数配合使用来改变函数的命名返回值
  • 匿名函数被称为闭包,被允许调用定义在其他环境的变量。
  • 一个函数闭包继承了函数所声明时的作用域

闭包应用-将函数作为返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import "fmt"
func main() {
// make an Add2 function, give it a name p2, and call it:
p2 := Add2()
fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))
// make a special Adder function, a gets value 2:
TwoAdder := Adder(2)
fmt.Printf("The result is: %v\n", TwoAdder(3))
}
func Add2() func(b int) int {
return func(b int) int {
return b + 2
}
}
func Adder(a int) func(b int) int {
return func(b int) int {
return a + b
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"
func main() {
var f = Adder()
fmt.Print(f(1), " - ")
fmt.Print(f(20), " - ")
fmt.Print(f(300))
}
func Adder() func(int) int {
var x int
return func(delta int) int {
x += delta
return x
}
}
  • x中的值是可以被保存的
  • 在闭包中使用到的变量可以在函数体内声明,也可以在函数体外声明

使用闭包调试

  • runtime中的函数Caller提供了文件和行数的信息,可以用闭包来实现调试定位
1
2
3
4
5
6
7
8
9
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()

计算函数执行时间

1
2
3
4
5
start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)

通过内存缓存来提升性能

  • 内存缓存来避免重复计算,如斐波那契,空间换时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main
import (
"fmt"
"time"
)
const LIM = 41
var fibs [LIM]uint64
func main() {
var result uint64 = 0
start := time.Now()
for i := 0; i < LIM; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
}
func fibonacci(n int) (res uint64) {
// memoization: check if fibonacci(n) is already known in array:
if fibs[n] != 0 {
res = fibs[n]
return
}
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
fibs[n] = res
return
}

第七章 数组和切片

声明和初始化

  • 数组长度是数组类型的一种
  • 数组长度最大为2Gb
  • 数组声明 var identifier [len]type
  • 数组声明之后所有元素会默认为零值
  • 数组遍历用for循环,range更好
  • 数组可以用new来创建,创建之后返回的是数组的地址,而不是数组的值

数组常量

  • var arr = [5]{18, 10, 12, 19, 23}
  • var arr = [...]{18, 10, 12, 19, 23}
  • var arr = [5]string{3: "aa", 4: "bb"} - {“”. “”, “”, aa, bb}

多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
const (
WIDTH = 1920
HEIGHT = 1080
)
type pixel int
var screen [WIDTH][HEIGHT]pixel
func main() {
for y := 0; y < HEIGHT; y++ {
for x := 0; x < WIDTH; x++ {
screen[x][y] = 0
}
}
}

将数组传递给函数

  • 为了降低内存的消耗,传递数组指针或者切片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
func main() {
array := [3]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
// to pass a pointer to the array
fmt.Printf("The sum of the array is: %f", x)
}
func Sum(a *[3]float64) (sum float64) {
for _, v := range a { // derefencing *a to get back to the array is not necessary!
sum += v
}
return
}

7.2 切片

  • 切片是对数组一个连续片段的引用
  • 切片可以索引,可以len
  • 切片是一个长度可变的数组
  • cap函数来计算切片的最大容量 0 <= len(s) <= cap(s)
  • 切片是引用,不耗内存,效率更高
  • 声明格式 var identifier []type
  • 切片在未初始化之前是nil,长度为0
  • 切片的初始化格式是:var slice1 []type = arr1[start:end] 前闭后开
  • 切片可以扩展到其上限
  • 切片初始化:var x = []int{2, 3, 4, 7, 11}
  • 切片只能向后移
  • 不要用指针指向切片,因为切片本身就是一个指针

将切片传递给函数

  • 与传递数组相比,节省内存开销,同时函数具备了改变外部变量的能力
1
2
3
4
5
6
7
8
9
10
11
12
func sum(a []int) int {
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}
return s
}
func main() {
var arr = [5]int{0, 1, 2, 3, 4}
sum(arr[:])
}

make创建切片

  • var slice []type = make(type[], len),在创建切片的同时创建好相关数组
  • make需要两个参数,元素类型和元素个数

make和new的区别

  • new为每个新的类型T分配一片内存,初始化零值并返回类型为*T的内存地址,适用于数组和结构体
  • make返回一个类型为T的初始值,适合创建slice,map和channel
  • new分配内存,make函数初始化

bytes包

1
2
3
4
5
6
7
8
9
var buffer bytes.Buffer
for {
if s, ok := getNextString(); ok { //method getNextString() not shown here
buffer.WriteString(s)
} else {
break
}
}
fmt.Print(buffer.String(), "\n")
  • 字符串串联效率更高

7.3 for-range结构

1
2
for idx, value := range slice {
}
  • idx是数组或者切片的索引
  • value是索引对应的值
  • _忽略不需要的idx或者value

  • 多维切片下的for-range

1
2
3
4
5
for row := range screen {
for column := range screen[row] {
screen[row][column] = 1
}
}

7.4 切片重组

slice1 := make([]type, start_length, capacity)

  • 切片可以反复扩展直到占据整个数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import "fmt"
func main() {
slice1 := make([]int, 0, 10)
// load the slice, cap(slice1) is 10:
for i := 0; i < cap(slice1); i++ {
slice1 = slice1[0:i+1]
slice1[i] = i
fmt.Printf("The length of slice is %d\n", len(slice1))
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
}

7.5 切片的复制与追加

  • 先创建再拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main() {
sl_from := []int{1, 2, 3}
sl_to := make([]int, 10)
n := copy(sl_to, sl_from)
fmt.Println(sl_to)
fmt.Printf("Copied %d elements\n", n) // n == 3
sl3 := []int{1, 2, 3}
sl3 = append(sl3, 4, 5, 6)
fmt.Println(sl3)
}
  • append方法追加切片并返回新的切片

7.6 字符串、数组和切片的应用

  • 字符串生成切片 - c := []byte(s)

  • 获取字符串的某一部分 - substr := str[start:end]

  • 字符串和切片的内存结构,字符串是一个双字结构,地址指针和长度值

  • go语言中字符串是不可变的,但是可以通过切片操作来改变字符串

  • append函数常见操作

    • 切片追加 - a = append(a, b...)
    • 切片复制 - b = make([]Tm len(a)) copy(b, a)
    • 删除索引i的元素 - a = append(a[:i], a [i+1:])
    • 切除切片 a 中从索引 i 至 j 位置的元素 - a = append(a[:i], a[j:]...)
    • 为切片 a 扩展 j 个元素长度 - a = append(a, make([]T, j)...)
    • 在索引 i 的位置插入元素 x - a = append(a[:i], append([]T{x}, a[i:]...)...)
    • 在索引 i 的位置插入长度为 j 的新切片 - a = append(a[:i], append(make([]T, j), a[i:]...)...)
    • 在索引 i 的位置插入切片 b 的所有元素 - a = append(a[:i], append(b, a[i:]...)...)
    • 取出位于切片 a 最末尾的元素 x - x, a = a[len(a)-1], a[:len(a)-1]
    • 将元素 x 追加到切片 a - a = append(a, x)
  • 底层数组没有切片引用的时候会被回收

第八章 Map

8.1 声明和初始化

声明

  • var map1 map[keyType]valueType
  • var map1 map[string]int
  • map可以动态增长
  • 未初始化的map是nil
  • key可以是string,int,float
  • value可以是任意类型的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import "fmt"
func main() {
var mapLit map[string]int
//var mapCreated map[string]float32
var mapAssigned map[string]int
mapLit = map[string]int{"one": 1, "two": 2}
mapCreated := make(map[string]float32)
mapAssigned = mapLit
mapCreated["key1"] = 4.5
mapCreated["key2"] = 3.14159
mapAssigned["two"] = 3
fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
}
  • map是引用类型,内存用make方法来分配,不要用new
  • map的初始化 - map := make(map[string]float32)

  • map容量 - cap会自动+1扩张

测试键值对是否存在及删除元素

1
2
if _, ok := map[key1]; ok {
}
  • delete(map1, key1)

  • for key, value := range map1 {}

map类型的切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "fmt"
func main() {
// Version A:
items := make([]map[int]int, 5)
for i:= range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)
// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}

8.5 map排序

  • 将key或者value拷贝到切片
  • 对切片排序
  • 根据切片打印map

第九章 包 package

9.1 标准库概述

  • unsafe:C/C++中调用的类型不安全的包
  • syscall-os-os/exec:底层接口包
  • archive/tar,zip-compress:文件压缩解压缩
  • fmt:格式化输入输出
  • io:基本输入输出
  • bufio:缓冲输入输出
  • path/filepath:操作在当前系统中的目标文件名路径
  • flag:对命令行参数的操作
  • strings:字符串操作
  • strconv:字符串类型转换
  • unicode:Unicode类型字符串功能
  • regexp:正则
  • bytes:字符型分片操作
  • index/suffixarray:子字符串快速查询
  • math:基本数学函数
  • math/cmath:对复数的操作
  • math/rand:随机数
  • sort:排序
  • math/big:大数
  • container-list-ring-heap:集合操作
  • list:双链表
  • ring:环形链表
  • time:日期和时间的基本操作
  • log:记录程序运行时产生的日志
  • encoding/json:读取并解码和写入并编码json数据
  • encoding/xml:xml解析器
  • text/template:数据驱动模板
  • net:网络的基本操作
  • http:可扩展的http服务器和基础客户端
  • html:html5解析器
  • runtime:go程序运行时的交互操作
  • reflect:反射

9.2 regexp包

  • 简单模式使用Match方法

ok, _ := regexp.Match(pattern, []byte(searchIn))
ok, _ := regexp.MatchString(pattern, searchIn)

  • 通过Compile方法返回Regrep对象进行匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//目标字符串
searchIn := "John: 2578.34 William: 4567.23 Steve: 5632.18"
pat := "[0-9]+.[0-9]+" //正则
f := func(s string) string{
v, _ := strconv.ParseFloat(s, 32)
return strconv.FormatFloat(v * 2, 'f', 2, 32)
}
if ok, _ := regexp.Match(pat, []byte(searchIn)); ok {
fmt.Println("Match Found!")
}
re, _ := regexp.Compile(pat)
//将匹配到的部分替换为"##.#"
str := re.ReplaceAllString(searchIn, "##.#")
fmt.Println(str)
//参数为函数时
str2 := re.ReplaceAllStringFunc(searchIn, f)
fmt.Println(str2)

9.3 锁和sync包

  • sync.Mutex是一个互斥锁,保证临界区同一个时间只有一个线程进入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import "sync"
type Info struct {
mu sync.Mutex
// ... other fields, e.g.: Str string
}
func Update(info *Info) {
info.mu.Lock()
// critical section:
info.Str = // new value
// end critical section
info.mu.Unlock()
}
  • RWMutex锁:可以通过RLock方法来允许同一时间多个线程读取变量,一个线程写变量

精密计算和big包

  • 整数的高精度计算提供了big包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Here are some calculations with bigInts:
im := big.NewInt(math.MaxInt64)
in := im
io := big.NewInt(1956)
ip := big.NewInt(1)
ip.Mul(im, in).Add(ip, im).Div(ip, io)
fmt.Printf("Big Int: %v\n", ip)
// Here are some calculations with bigInts:
rm := big.NewRat(math.MaxInt64, 1956)
rn := big.NewRat(-1956, math.MaxInt64)
ro := big.NewRat(19, 56)
rp := big.NewRat(1111, 2222)
rq := big.NewRat(1, 1)
rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
fmt.Printf("Big Rat: %v\n", rq)

自定义包和可见性

  • import导入包,路径名或者URL地址
  • 导入外部安装包:go install codesite/..,安装在GOROOT/src/目录下
  • 程序的初始化在于导入包,初始化main包然后调用main函数
  • init函数不能被调用

godoc

  • 命令行执行 godoc -http=:6060 -goroot="."其中.是当前路径,会在127.0.0.1:6060中看到本地go的注释

go install

  • go install是go中自动包安装工具
  • go installl uc会将uc.a包安装到pkg目录下
  • 也可以用gomake来安装
  • go test测试

git打包和安装

  • 安装到GitHub
1
2
3
4
5
git init
git add .
git commit -m ""
git remote add origin git@github.com:NNN/uc.git
git push -u origin master
  • 从GitHub安装 - go get github.com/xxx/xx

外部库的使用

  1. 下载并安装外部库-go install
  2. GOPATH是否有,包会被下载到$GOPATH/PKG/"machine_arch"/

第十章 结构和方法

  • go通过类型别名和结构体的形式支持用户自定义类型
  • 结构体是复合类型,new来创建
  • 数据称为字段fields

10.1 结构体定义

  • 结构体的定义方法:
1
2
3
4
5
6
7
type identifier struct {
field1 type1
field2 type2
...
}
type T struct{a, b int}
  • 数组可以看做使用下标的结构体
  • new函数给结构体分配内存,返回已分配内存的指针
  • var t T,此时t是T的一个实例/对象
  • 结构体打印和%v效果一样
  • 结构体变量和指针都可以通过.来选择字段值
  • p := Point{}是实例,p := &Point{}是指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main
import (
"fmt"
"strings"
)
type Person struct {
firstName string
lastName string
}
func upPerson(p *Person) {
p.firstName = strings.ToUpper(p.firstName)
p.lastName = strings.ToUpper(p.lastName)
}
func main() {
// 1-struct as a value type:
var pers1 Person
pers1.firstName = "Chris"
pers1.lastName = "Woodward"
upPerson(&pers1)
fmt.Printf("The name of the person is %s %s\n", pers1.firstName, pers1.lastName)
// 2—struct as a pointer:
pers2 := new(Person)
pers2.firstName = "Chris"
pers2.lastName = "Woodward"
(*pers2).lastName = "Woodward" // 这是合法的
upPerson(pers2)
fmt.Printf("The name of the person is %s %s\n", pers2.firstName, pers2.lastName)
// 3—struct as a literal:
pers3 := &Person{"Chris","Woodward"}
upPerson(pers3)
fmt.Printf("The name of the person is %s %s\n", pers3.firstName, pers3.lastName)
}
  • go的内存布局,结构体和其字段在内存中是连续存在的,性能更好,空间换时间

  • 递归结构体,链表,树的实现

  • 结构体可以和他别名进行类型转换

10.2 使用工厂方法创建结构体实例

  • 工厂方法以new或者New开头
1
2
3
4
5
6
7
8
9
10
11
type File struct {
fd int
name string
}
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
return &File{fd, name}
}
  • 推荐使用工厂方法来创建结构体,结构体字段就可以定义为私有的,OO思想

10.3 使用自定义包中的结构体

直接import就OK了

10.4 带标签的结构体

  • 结构体中的字段出了名字和类型之外,还有一个可选的标签
  • 标签是附属于字段的字符串,文档注释等操作
  • reflect可以取到tag
1
2
3
4
5
type TagType struct { // tags
field1 bool "An important answer"
field2 string "The name of the thing"
field3 int "How much there are"
}

10.5 匿名字段和内嵌结构体

  • 字段没有名字,只有类型
  • 匿名字段可以是结构体
1
2
3
4
5
6
7
8
9
10
11
type innerS struct {
in1 int
in2 int
}
type outerS struct {
b int
c float32
int // anonymous field
innerS //anonymous field
}
  • 实际上一个结构体对每一个类型最多有一个匿名字段,通过struct.type获取

  • 内嵌结构体的概念类似于继承

  • 内外的结构体命名冲突,外层的名字会覆盖内层的名字,同级别冲突报错

10.6 方法

  • go方法是作用在接收者上的一个函数,接收者是某个类型的变量

  • 接收者可以是任何对象除了interface

  • go中类型的代码和绑定在它上面的方法的代码可以不放置在一起,只要是同一个包就可以

  • 方法集

  • 方法不允许重载

  • 定义格式 func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }

  • 接收者可以理解为Java中的this

函数和方法的区别

  • 函数将变量作为参数,方法在变量上被调用
  • 接收者需要显示命名
  • receiver_type必须和方法同包
  • 类型和方法之间的关联由接收者来建立
  • 方法没有和结构体混合,表明数据和方法是独立的

指针或值作为接收者

  • 一般来说接收者都是类型的指针,效率更高

  • 指针方法和值方法都可以在指针或非指针上被调用

  • 未导出的结构体的对象通过setter\getter来修改和获取

内嵌类型的方法和继承

  • 匿名类型被内嵌时,其可见方法也同样被内嵌,等价于外部结构继承了这些方法
1
2
3
4
5
6
7
8
9
10
11
12
13
type Engine interface {
Start()
Stop
}
type Car interface struct {
Engine
}
func (c *Car) GoToWorkIn() {
c.Start()
c.Stop()
}
  • 方法可以被外层结构体覆盖

如何在类型中嵌入功能

  • 两种方法

    • 聚合:包含一个所需要功能的具体字段
    • 内嵌:内嵌需要的功能类型
  • 聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Log struct {
msg string
}
type Customer struct {
Name string
log *Log
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) Log() *Log {
return c.log
}
  • 内嵌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Log struct {
msg string
}
type Customer struct {
Name string
Log
}
func main() {
c := &Customer{"Barak Obama", Log{"1 - Yes we can!"}}
c.Add("2 - After me the world will be a better place!")
fmt.Println(c)
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) String() string {
return c.Name + "\nLog:" + fmt.Sprintln(c.Log)
}
  • 内嵌的类型不需要指针

多重继承

  • 多重继承指的是一个类型获得多个父类型的能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Camera struct {
}
func (c *Camera) TakeAPicture() string {
return "click"
}
type Phone struct{
}
func (p *Phone) Call string {
return "ring"
}
type CameraPhone struct {
Camera
Phone
}
func main() {
cp := new(CameraPhone)
fmt.Println(cp.TakeAPicture(), cp.Call())
}

其他语言和GO的类型和方法的比较

  • go中,类型就是类,代码复用和多态更好
  • go中,代码通过组合和委托实现,多态通过接口的使用来实现

10.7 类型的String方法和格式化描述符

  • 定义了string()方法的类型,会被fmt.Println()中生成默认的输出,等同于%v产生的输出
  • 注意不要在String方法里面调用涉及String()方法的方法,放置造成无限递归

10.8 垃圾回收和SetFinalizer

  • go语言有自己的垃圾回收器
  • 通过runtime包访问GC进程,runtime.GC()
  • SetFinalizer可以在对象被移除前进行一些操作runtime.SetFinalizer(obj, func(obj *typeObj))

第11章 接口与反射

  • 接口定义了一组方法
  • go中的接口都很简短,0-3个方法
  • go语言中接口可以有值,为多字数据结构,值为nil
  • 类型来实现接口找那个的方法
  • 类型不需要显示声明它实现了哪个接口:接口被隐式的实现,多个类型可以实现同一个接口
  • 实现某个接口的类型可以有其他的方法
  • 一个类型可以实现多个接口
  • 接口类型可以包含一个实例的引用,该实例的类型实现了此接口,接口是动态类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
type Rectangle struct {
length, width float32
}
func (r Rectangle) Area() float32 {
return r.length * r.width
}
func main() {
r := Rectangle{5, 3} // Area() of Rectangle needs a value
q := &Square{5} // Area() of Square needs a pointer
shapes := []Shaper{r, q}
fmt.Println("Looping through shapes for area ...")
for n, _ := range shapes {
fmt.Println("Shape details: ", shapes[n])
fmt.Println("Area of this shape is: ", shapes[n].Area())
}
}

接口的多态实例,父类引用持有子类变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type stockPosition struct {
ticker string
sharePrice float32
count float32
}
func (s stockPosition) getValue() float32 {
return s.sharePrice * s.count
}
type car struct {
make string
model string
price float32
}
func (c Car) getValue() float32 {
return c.price
}
type valuable interface {
getValue() float32
}
func showValue(asset valuable) {
fmt.Printf("Value of the asset is %f\n", assert.getValue())
}
func main() {
var o valuable = stockPosition{"GOODS", 577.012, 4}
showValue(o)
o = car{"BMW", "M5", 56565465}
showValue(o)
}
  • 标准库中的例子-Reader
1
2
3
type Reader interface {
Read(p []byte) (n int, err error)
}

11.2接口嵌套接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lokck
Close()
}

11.3 类型断言:如何检测和转换接口变量的类型

  • 一个接口类型的变量可以包含任何类型的值,类型断言来检测某个时刻该接口变量的值
  • v := varI.(T) varI为接口变量
1
2
3
4
5
6
7
8
9
10
11
var areaIntf Shaper
sq1 := new(Square)
sq1.side = 5
areaIntf = sq1
if t, ok := areaIntf.(*Square); ok {
fmt.Println()
}
if u, ok := areaIntf.(*Circle); ok {
fmt.....
}

11.4 类型判断: type-switch

  • 不允许fallthrought关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("Param #%d is a bool\n", i)
case float64:
fmt.Printf("Param #%d is a float64\n", i)
case int, int64:
fmt.Printf("Param #%d is a int\n", i)
case nil:
fmt.Printf("Param #%d is a nil\n", i)
case string:
fmt.Printf("Param #%d is a string\n", i)
default:
fmt.Printf("Param #%d is unknown\n", i)
}
}
}

11.5 测试一个值是否实现了某个接口

1
2
3
4
5
6
7
type Stringer interface {
String() string
}
if sv, ok := v.(Stringer); ok {
fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}
  • 接口是一种契约,实现类型必须满足它,描述了类型的行为,规定类型可以做什么。
  • 使用接口使得代码更具有普适性

11.6 使用方法集与接口

  • 作用于变量上的方法实际上是不区分变量是值还是指针的,但是如果变量是接口类型时,由于接口变量中村的具体指是不可以寻址的,会编译错误
  • 在指针上调用方法时,必须有和方法定义时相同的接收者类型或者可以从具体类型P直接进行辨识的类型
    • 指针方法可以通过指针调用
    • 值方法可以通过值调用
    • 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
    • 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
  • 将一个值赋值给接口时,编译器会进行类型检查

小结

  1. 类型*T的可调用方法集合包含接受者为*T或T的所有方法集
  2. 类型T的可调用方法集包含接受者为T的所有方法

11.7 Sorter接口排序

  • Sort函数接收一个Sorter接口类型的参数,Sorter接口定义了len,less,swap方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package sort
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j, int)
}
func Sort(data Sorter) {
for pass := 1; pass < data.Len(); pass++ {
for i := 0; i < data.Len - pass; i++ {
if data.Less(i+1, j) {
data.Swap(i, i + 1)
}
}
}
}
func IsSorted(data Sorter) bool {
n := data.Len
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
type IntArray []int
func (p IntArray) Len() int{return len(p)}
func (p IntArray) Less(i, j int) bool {return p[i] < p[j]}
func (p IntArray) Swap(i, j int) {p[i], p[j] = p[j], p[i]}
type StringArray []string
func (p StringArray) Len() int { return len(p) }
func (p StringArray) Less(i, j int) bool { return p[i] < p[j] }
func (p StringArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func SortInts(a []int) { Sort(IntArray(a)) }
func SortStrings(a []string) { Sort(StringArray(a)) }
func IntsAreSorted(a []int) bool { return IsSorted(IntArray(a)) }
func StringsAreSorted(a []string) bool { return IsSorted(StringArray(a))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package main
import (
"fmt"
"./sort"
)
// sorting of slice of integers
func ints() {
data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
a := sort.IntArray(data) //conversion to type IntArray
sort.Sort(a)
if !sort.IsSorted(a) {
panic("fail")
}
fmt.Printf("The sorted array is: %v\n", a)
}
// sorting of slice of strings
func strings() {
data := []string{"monday", "friday", "tuesday", "wednesday", "sunday","thursday", "", "saturday"}
a := sort.StringArray(data)
sort.Sort(a)
if !sort.IsSorted(a) {
panic("fail")
}
fmt.Printf("The sorted array is: %v\n", a)
}
// a type which describes a day of the week
type day struct {
num int
shortName string
longName string
}
type dayArray struct {
data []*day
}
func (p *dayArray) Len() int { return len(p.data) }
func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num }
func (p *dayArray) Swap(i, j int) { p.data[i], p.data[j] = p.data[j], p.data[i] }
// sorting of custom type day
func days() {
Sunday := day{0, "SUN", "Sunday"}
Monday := day{1, "MON", "Monday"}
Tuesday := day{2, "TUE", "Tuesday"}
Wednesday := day{3, "WED", "Wednesday"}
Thursday := day{4, "THU", "Thursday"}
Friday := day{5, "FRI", "Friday"}
Saturday := day{6, "SAT", "Saturday"}
data := []*day{&Tuesday, &Thursday, &Wednesday, &Sunday, &Monday, &Friday, &Saturday}
a := dayArray{data}
sort.Sort(&a)
if !sort.IsSorted(&a) {
panic("fail")
}
for _, d := range data {
fmt.Printf("%s ", d.longName)
}
fmt.Printf("\n")
}
func main() {
ints()
strings()
days()
}

11.8 读和写

  • io包中提供了用于读和写的接口io.Readerio.Writer
1
2
3
4
5
6
7
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
  • 只要实现了读写接口,这个对象就是可读写的
  • bufio里面提供了带缓冲的读写

11.9 空接口

定义

  • 空接口不包含任何方法,对实现不做要求 type Any interface{}
  • 类似Java中的Object类
  • 空接口可以被赋予任何值
  • 每个interface变量占据两个字长,一个用来存储包含的类型,一个用来存储包含的数据或者数据指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
testFunc := func(any interface{}) {
switch v := any.(type) {
case bool:
fmt.Printf("any %v is a bool type", v)
case int:
fmt.Printf("any %v is an int type", v)
case float32:
fmt.Printf("any %v is a float32 type", v)
case string:
fmt.Printf("any %v is a string type", v)
case specialString:
fmt.Printf("any %v is a special String!", v)
default:
fmt.Println("unknown type!")
}
}

11.9.2 构建通过类型或者包含不通类型变量的数组

1
2
3
4
type Element interface{}
type Vector struct {
a []Element
}
  • Vector里面可以存放任何类型的变量
  • Vector中存储的所有元素都是Element类型,得到袁术类型需要用类型断言

11.9.3 复制数据切片到空接口切片

1
2
3
4
5
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for idx, val := range dataSlice {
interfaceSlice[i] = d
}

11.9.4 通用类型的节点数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
type Node struct {
left *Node
data interface{]
right *Node
}
func NewNode(left, right *Node) *Node {
return &Node{left, nil, right}
}
func (n *Node) SetData(data interface{}) {
n.data = data
}

11.9.5 接口到接口

  • 一个接口的值可以赋值给另一个接口,只要底层实现了必要的方法
  • 运行时才会检查两个接口是否可以进行赋值,不可以会报错

11.10 反射包

  • 反射是用程序检查其所拥有的结构,类型的一种能力
  • 反射可以在运行时检查类型和变量,比如大小,方法并动态的调用这些方法
  • 反射包中Type用来表示一个Go类型,Value为go提供了反射的接口
  • reflect.TypeOf reflect.ValueOf检查对象的类型和值
  • 反射可以从接口值反射到对象,也可以从对象反射回接口
  • 反射包中的常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
  • Kind方法总是返回底层类型

11.10.2 通过反射修改值

1
2
3
4
5
6
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settablity of v:", v.Canset())
v = reflect.ValueOf(&x)
v = v.Elem()
v.SetFloat(3.1415)
  • NumField()方法返回结构内的字段数量,通过for循环用索引取得每个字段的值Field

    11.10.3 反射结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type NotknownType struct {
s1, s2, s3 string
}
func (n NotknownType) String() string {
return n.s1 + " - " + n.s2 + " - " + n.s3
}
// variable to investigate:
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
func main() {
value := reflect.ValueOf(secret) // <main.NotknownType Value>
typ := reflect.TypeOf(secret) // main.NotknownType
// alternative:
//typ := value.Type() // main.NotknownType
fmt.Println(typ)
knd := value.Kind() // struct
fmt.Println(knd)
// iterate through the fields of the struct:
for i := 0; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value.Field(i))
// error: panic: reflect.Value.SetString using value obtained using unexported field
//value.Field(i).SetString("C#")
}
// call the first method, which is String():
results := value.Method(0).Call(nil)
fmt.Println(results) // [Ada - Go - Oberon]
}
  • 结构体中只有被导出字段(大写的字段)才是可以设置的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type T struct {
A int
B string
}
func main() {
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
}

11.11 Printf和反射

  • Printf函数声明为
1
func Printf(format string, args ... interface{}) (n int, err error)
  • ...为空接口类型,Printf可以知道每个参数的类型

11.12 接口和动态类型

11.12.1 Go的动态类型

  • go语言中数据和方法是松耦合的
  • go中的接口任何提供了接口方法实现代码的类型都隐式的实现了该接口
  • duck typing,能做什么比是什么更加重要
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type IDuck interface {
Quack()
Walk()
}
func DuckDance(duck IDuck) {
for i := 1; i <= 3; i++ {
duck.Quack()
duck.Walk()
}
}
type Bird struct {
}
func (b *Bird) Quack() {
fmt.Println("bibibi")
}
func (b *Bird) Walk() {
fmt.Println("gogogo")
}
func main() {
b := new(Bird)
DuckDance(b)
}

11.12.2 动态方法调用

  • 当一个变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数,空接口可以使用类型断言来判断
  • go提供了动态语言的优点,却没有其他动态语言在运行时随时会报错的缺点
  • Go 的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。用 Go 接口还能实现 依赖注入模式。

11.12.3 接口的提取

  • 接口的提取是非常有用的设计模式,可以减少需要的类型和方法数量
  • 一些拥有共同行为的对象可以抽象出接口
  • 整个设计可以持续演进,不用废弃之前的决定
  • 类型要实现某个接口,本身不用改变,在类型上实现新的方法就可以

11.12.4 显示的指明类型实现了某个接口

  • 结构体中添加具有描述性名字的方法
1
2
3
4
type Fooer interface {
Foo()
ImplementsFooer()
}

11.12.5 空接口和函数重载

  • go语言是不允许函数重载的
  • go中通过将最后一个参数换成…interface{}来实现函数重载,允许传递任意数量任意类型的参数给函数
1
fmt.Printf(format string, a ...interface{}) (n int, err error)

11.12.6 接口的继承

  • 当一个类型嵌套另外一个类型的指针时,这个类型就可以使用另一个类型的所有方法
  • 多态用的越多,代码就相对越少
  • 类型可以通过继承多个接口来实现多重继承
  • 添加新的接口是非常容易的,因为已有的类型不用变动,仅需要实现新的接口即可

11.13 go中面向对象总结

  • go中没有类,而是松耦合的类型,方法对接口的实现
  • 封装:go的可见层级为两个,包范围内可见和全局可见
  • 继承:用组合实现,内嵌一个或者多个想要的行为的类型,可以多重嵌套
  • 多态:用接口实现,某个类型的实例可以赋值给它所实现的任意接口类型的变量。类型和接口是松耦合的,多重继承可以通过实现多个接口来实现。

11.14 结构体、集合和高阶函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// cars.go
package main
import (
"fmt"
)
type Any interface{}
type Car struct {
Model string
Manufacturer string
BuildYear int
// ...
}
type Cars []*Car
func main() {
// make some cars:
ford := &Car{"Fiesta","Ford", 2008}
bmw := &Car{"XL 450", "BMW", 2011}
merc := &Car{"D600", "Mercedes", 2009}
bmw2 := &Car{"X 800", "BMW", 2008}
// query:
allCars := Cars([]*Car{ford, bmw, merc, bmw2})
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
fmt.Println("AllCars: ", allCars)
fmt.Println("New BMWs: ", allNewBMWs)
//
manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allCars.Process(sortedAppender)
fmt.Println("Map sortedCars: ", sortedCars)
BMWCount := len(sortedCars["BMW"])
fmt.Println("We have ", BMWCount, " BMWs")
}
// Process all cars with the given function f:
func (cs Cars) Process(f func(car *Car)) {
for _, c := range cs {
f(c)
}
}
// Find all cars matching a given criteria.
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
cars := make([]*Car, 0)
cs.Process(func(c *Car) {
if f(c) {
cars = append(cars, c)
}
})
return cars
}
// Process cars and create new data.
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, 0)
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
func MakeSortedAppender(manufacturers []string) (func(car *Car), map[string]Cars) {
// Prepare maps of sorted cars.
sortedCars := make(map[string]Cars)
for _, m := range manufacturers {
sortedCars[m] = make([]*Car, 0)
}
sortedCars["Default"] = make([]*Car, 0)
// Prepare appender function:
appender := func(c *Car) {
if _, ok := sortedCars[c.Manufacturer]; ok {
sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
} else {
sortedCars["Default"] = append(sortedCars["Default"], c)
}
}
return appender, sortedCars
}
/* Output:
AllCars: [0xf8400038a0 0xf840003bd0 0xf840003ba0 0xf840003b70]
New BMWs: [0xf840003bd0]
Map sortedCars: map[Default:[0xf840003ba0] Jaguar:[] Land Rover:[] BMW:[0xf840003bd0 0xf840003b70] Aston Martin:[] Ford:[0xf8400038a0]]
We have 2 BMWs
*/
Donate comment here