Golang
Go语言设计初衷
- 针对其他语言的痛点进行设计
- 并加入并发编程
- 为大数据,微服务,并发而生的通用编程语言
Go语言与转型
- 项目转型首选语言
- 软件工程师转型、添加技术栈的首选语言
- 这是一门为转型量身定制的课程
课程内容
- 基本语法
- 变量
- 选择、循环
- 指针、数组、容器
- 面向接口
- 结构体
- duck typing的概念
- 组合的思想
- 函数式编程
- 闭包的概念
- 多样的例题
- 工程化
- 资源管理,错误处理
- 测试和文档
- 性能调优
- 并发编程
- gorutine和channel
- 理解调度器
- 多样的例题
实战项目的实现步骤
单任务版 ——> 并发版 ——> 分布式
2 Go语言基本语法
2-1 变量定义
- 变量类型写在变量名之后
- 编译器可推测变量类型
- 没有char,只有rune(32位)
- 原生支持复数类型
使用var 关键字
- var a, b, c bool
- var s1, s2 string = "Hello", "world"
- 可放在函数内,或直接放在包内
- 使用var()集中定义变量
package main
import "fmt"
// 变量名在变量类型之前,且有初始值
func variableZeroValue() {
var a int
var s string
fmt.Printf("%d %q\n",a, s)
}
// 变量可以同时定义多个,定义的变量必须使用
func variableInitialValue() {
var a, b int = 3, 4
var s string = "abv"
fmt.Println( a, b, s)
}
// 可以省略type,进行自动识别
func variableTypeDeduction() {
var a, b, c, d = 3, 4, true, "def"
fmt.Println(a, b, c, d)
}
// := 为定义并赋值,只能在函数内使用
func variableShorter() {
a, b, c, d := 3, 4, true, "Def"
b = 5
fmt.Println(a, b, c, d)
}
func main() {
fmt.Println("Hello world")
variableZeroValue()
variableInitialValue()
variableTypeDeduction()
variableShorter()
}
2-2 内建变量类型
- bool, string
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr(指针)
- byte, rune(Go语言的字符型)
- float32, float64, complex64, complex128(复数)
// 复数计算;验证欧拉公式
func euler(){
c := 3 + 4i
fmt.Println(cmplx.Abs(c)) // 5
fmt.Println(cmplx.Pow(math.E, 1i * math.Pi) + 1) //(0+1.2246467991473515e-16i) 因为浮点数存在误差,所以不等于0
fmt.Printf("%3f", cmplx.Pow(math.E, 1i * math.Pi) + 1) //(0.000000+0.000000i)
}
// 强制类型转换
func triangle() {
var a, b int = 3, 4
var c int
// math.Sqrt要求float类型,返回值也是float类型
c = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println(c) // 5
}
常量与枚举
常量的定义
func consts() {
const filename = "abc.txt" // 定义在方法体外也可以
const a, b = 3, 4 // const数值可作为各种类型使用
var c int
c = int(math.Sqrt(a*a + b*b))
fmt.Println(filename, c) //abc.txt 5
}
枚举类型
func enums() {
// 用const块来定义枚举, iota为自增关键字
const (
cpp = iota
_
python
golang
javascript
)
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
fmt.Println(cpp, javascript, python, golang) // 0 4 2 3
fmt.Println(b, kb, mb, gb, tb, pb) // 1 1024 1048576 1073741824 1099511627776 1125899906842624
}
2-4 条件语句
if
- if 的条件里可以赋值
- if 的条件里赋值的变量作用域就在这个if语句里
const filename = "abc.txt"
//contents, err := ioutil.ReadFile(filename)
//if err != nil {
// fmt.Println(err)
//} else {
// fmt.Printf("%s\n", contents)
//}
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
swith
- swith 会自动break, 除非使用fallthrough
func eval(a, b int, op string) int {
var result int
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
result = a / b
default:
panic("unsupported operation: " + op)
}
return result
}
func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf(
"Wrong score: %d", score))
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score <= 100:
g = "A"
}
return g
}
2-5 循环
for
- for的条件里不需要括号
- for的条件里可以省略初始条件,结束条件,递增表达式
sum := 0
for i := 1; i <= 100; i++ {
sum +=
}
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
// 整数转成二进制,省略初始条件,相当于while
func convertToBin(n int) string {
result := ""
for ; n >0; n /=2 {
lsb := n % 2
result = strconv.Itoa(lsb) + result
}
return result
}
// 省略初始条件和递增条件,相当于while
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
// 条件都省略,相当于死循环
func forever() {
for {
fmt.Println("abc")
}
}
func main() {
fmt.Println(
convertToBin(5), // 101
convertToBin(13), // 1101
)
printFile("abc.txt")
forever()
}
2-6 函数
只返回一个值
package main
import "fmt"
func eval(a, b int, op string) int {
switch op {
case "+":
return a + b
case "-":
return a - b
case "*":
return a * b
case "/":
return a / b
default:
panic("unsupported operation: " + op)
}
}
func main() {
fmt.Println(eval(3, 4, "*")) // 12
}
返回多个值
// 13 / 3 = 4 ... 1
func div(a, b int) (int, int) {
return a / b, a % b
}
函数的参数也可以是函数
func apply(op func(int, int) int, a, b int) int{
fmt.Printf("Calling %s with %d, %d\n", runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(), a, b)
return op(a, b)
}
2-7 指针
指针不能运算
var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)
值传递和引用传递
- Go 语言只有值传递一种方式
- var a int --> func f(a int) 直接将a拷贝过去,函数体内对a 的改变不会影响变量a
- var a int --> func f(pa *int)将&a 的地址进行拷贝
-
var cache Cache --> func f(cache Cache)
func swap(a, b *int) {
*b, *a = *a, *b
}
// a, b := 3, 4
// swap(&a, &b) //4, 3
3 内建容器
3-1 数组
- 数量写在类型的前面
- 数组的遍历
- 可通过_省略变量
- 不仅range,任何地方都可以通过_省略变量
package main
import "fmt"
func main() {
// 数组的定义
var arr1 [5]int // [0 0 0 0 0]
arr2 := [3]int{1, 3, 5} // [1 3 5]
arr3 := [...]int{2, 4, 6, 8, 10} // [2 4 6 8 10]
var grid [4][5]int // [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)
// 遍历 方法1
for i := 0; i<len(arr3); i++ {
fmt.Println(arr3[i])
}
// 遍历 方法2
for i := range arr3 {
fmt.Println(arr3[i])
}
// 遍历 方法3 能同时返回下标
for i, v := range arr3 {
fmt.Println(i, v)
}
}
数组是值类型
- [10]int 和 [20]int是不同类型
- 调用func f(arr [10]int),会拷贝数组
- Go语言中一般不直接使用数组(切片)
3-2 slice(切片)的概念
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s := arr[2:6] // s的值为[2 3 4 5]
s[0] = 10 //[0 1 10 3 4 5 6 7]
- slice本身没有数据,是对底层array的一个view
3-3 切片的操作
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] // [2 3 4 5]
s2 := s1[3:5] // [5 6]
s3 := append(s2, 10)
// s4 and s5 no longer view arr.
s4 := append(s3, 11)
s5 := append(s4, 12) // [5 6 10 11 12]
// arr = [0 1 2 3 4 5 6 10]
- slice可以向后扩展,不可以向前扩展
- s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)
- 添加元素时如果超越cap,系统会重新分配更大的底层数组
- 由于值传递的关系,必须接收append的返回值
- s = append(s, val)
package main
import "fmt"
// slice 的一些操作
func printSlice(s []int) {
fmt.Printf("%v, len=%d, cap=%d\n", s, len(s), cap(s))
}
func main() {
fmt.Println("Creating slice")
var s []int // Zero value for slice is nil
for i := 0; i < 100; i++ {
printSlice(s)
s = append(s, 2*I+1)
}
//fmt.Println(s)
s1 := []int{2, 4, 6, 8}
printSlice(s1) // [2 4 6 8], len=4, cap=4
s2 := make([]int, 16)
s3 := make([]int, 10, 32)
printSlice(s2) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
printSlice(s3) //[0 0 0 0 0 0 0 0 0 0], len=10, cap=32
fmt.Println("Copying slice")
copy(s2, s1)
printSlice(s2) // [2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2) //[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=15, cap=16
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println(front) // 2
fmt.Println("Popping from back")
tail := s2[len(s2) - 1]
s2 = s2[:len(s2) - 1]
fmt.Println(tail) // 0
printSlice(s2) // [4 6 0 0 0 0 0 0 0 0 0 0 0], len=13, cap=15
}
3-4 Map
- map[K]V, map[K1]map[K2]V
m := map[string]string {
"name": "ccmouse",
"course": "golang",
"site": "imooc",
"quality": "notbad",
}
Map 的操作
- 创建: make(map[string]int)
- 获取元素:m[key]
- key不存在时,获取Value类型的初始值
- 用value, ok := m[key]来判断是否存在key
- 用delete删除一个key
package main
import "fmt"
func main() {
//map 定义
m := map[string]string {
"name": "ccmouse",
"course": "golang",
"site": "imooc",
"quality": "notbad",
}
// 定义空map
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println(m) // map[course:golang name:ccmouse quality:notbad site:imooc]
fmt.Println(m2) // map[]
fmt.Println(m3) // map[]
fmt.Println("Traversing map")
for k, v := range m {
fmt.Println(k, v)
}
fmt.Println("Getting values")
courseName, ok := m["course"]
fmt.Println(courseName, ok)
if causeName, ok := m["cause"]; ok {
fmt.Println(causeName)
} else {
fmt.Println("Key does not exist")
}
fmt.Println("Deleting values")
name, ok := m["name"]
fmt.Println(name, ok) // ccmouse true
delete(m, "name")
name, ok = m["name"]
fmt.Println(name, ok) // false
}
Map的遍历
- 使用range遍历key,或者遍历key,value对
- 不保证遍历顺序,如需顺序,需手动对key排序
Map的key
- Map使用哈希表,必须可以比较相等
- 除了slice,map,function的内建类型都可以作为key
- Struct类型不包括上述字段,也可以作为key
3-5 Map的例题
例:寻找最长不含有重复字符的字串
- abcabcbb -> abc
- bbbbb -> b
- pwwkew -> wke
对于每一个字母x
- lastOccurred[x]不存在,或者< start -> 无需操作
- lastOccurred[x] >= start -> 更新start
- 更新lastOccurred[x],更新maxLength
package main
import "fmt"
func lengthOfNonRepeatingSubStr (s string) int {
lastOccurred := make(map[byte]int)
start := 0
maxLength := 0
for i, ch := range []byte(s) {
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}
if i - start + 1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = I
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("abcabcbb"))
fmt.Println(lengthOfNonRepeatingSubStr("bbbb"))
fmt.Println(lengthOfNonRepeatingSubStr("pwwkew"))
}
3-6 字符和字符串处理
rune相当于go的char
- 使用range遍历pos,rune对
- 使用utf8.RuneCountInString获得字符数量
- 使用len获得字节长度
- 使用[]byte获得字节
例:寻找最长不含有重复字符的字串
package main
import "fmt"
func lengthOfNonRepeatingSubStr (s string) int {
lastOccurred := make(map[rune]int)
start := 0
maxLength := 0
for i, ch := range []rune(s) {
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}
if i - start + 1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = I
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("abcabcbb")) //3
fmt.Println(lengthOfNonRepeatingSubStr("bbbb")) // 1
fmt.Println(lengthOfNonRepeatingSubStr("pwwkew")) // 3
fmt.Println(lengthOfNonRepeatingSubStr("这里是慕课网")) // 6
fmt.Println(lengthOfNonRepeatingSubStr("一二三二一")) // 3
}
其他字符串操作
- fields, split, join
- contains, index
- toLower, toUpper
- trim, trimRight, trimLeft
4 面向对象
- go语言仅支持封装,不支持继承和多态
- go语言没有class,只用struct
4-1 结构体和方法
结构的定义
type TreeNode struct {
Left, Right *TreeNode
Value int
}
结构的创建
package main
import "fmt"
type treeNode struct {
value int
left, right *treeNode
}
// 工厂函数
func createNode(value int) *treeNode {
return &treeNode{value: value}
}
func main() {
var root treeNode
fmt.Println(root) // {0 <nil> <nil>}
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)
nodes := []treeNode {
{value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]
}
- 使用自定义工厂函数
- 注意返回了局部变量的地址!
结构创建在堆上还是栈上
由go语言的运行环境和编译器决定,不需要了解
为结构定义方法
func (node treeNode) print() {
fmt.Println(node.value)
}
- 显示定义和命名方法接收者
func (node *treeNode) setValue(value int) {
node.value = value
}
- 只有使用指针才可以改变结构内容
- nil指针也可以调用方法!
树的遍历
package main
import (
"fmt"
)
type treeNode struct {
value int
left, right *treeNode
}
func (node treeNode) print() {
fmt.Println(node.value)
}
func (node *treeNode) setValue(value int) {
if node == nil {
fmt.Println("Setting value to nil " + "node. Ignored.")
return
}
node.value = value
}
// 中序遍历
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
// 工厂函数
func createNode(value int) *treeNode {
return &treeNode{value: value}
}
func main() {
var root treeNode
fmt.Println(root) // {0 <nil> <nil>}
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)
root.right.left.setValue(4)
nodes := []treeNode {
{value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]
root.traverse() // 0 2 3 4 5
}
值接收者 vs 指针接收者
- 要改变内容必须使用指针接收者
- 结构过大也考虑使用指针接收者
- 一致性:如果有指针接收者,最好都是指针接收者
- 值接收者是go语言特有
- 值/指针接收者均可接受值/指针
4-2 包和封装
封装
- 名字一般使用CamelCase
- 首字母大写:public
- 首字母小写:private
包
- 每个目标一个包
- main包包含可执行入口
- 为结构定义的方法必须放在同一个包内
- 可以是不同的文件
4-3 扩展已有类型
如何扩充系统类型或者别人的类型
- 定义别名
- 使用组合
package main
// 自己写后序遍历
import (
"fmt"
"learngo/tree"
)
type myTreeNode struct {
node *tree.TreeNode
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left}
right := myTreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
myNode.node.Print()
}
func main() {
var root tree.TreeNode
fmt.Println(root) // {0 <nil> <nil>}
root = tree.TreeNode{Value: 3}
root.Left = &tree.TreeNode{}
root.Right = &tree.TreeNode{5, nil, nil}
root.Right.Left = new(tree.TreeNode)
root.Left.Right = tree.CreateNode(2)
root.Right.Left.SetValue(4)
nodes := []tree.TreeNode{
{Value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]
root.Traverse() // 0 2 3 4 5
fmt.Println()
myRoot := myTreeNode{&root}
myRoot.postOrder()
fmt.Println() // 2 0 4 5 3
}
package queue
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
package main
import (
"fmt"
"learngo/queue"
)
func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop()) // 1
fmt.Println(q.Pop()) // 2
fmt.Println(q.IsEmpty()) // false
fmt.Println(q.Pop()) // 3
fmt.Println(q.IsEmpty()) // true
}
4-4 GOPATH 环境变量
5 接口
Go语言的面向对象只支持封装,Go语言使用接口完成继承和多态
type Traversal interface {
Traverse()
}
func main() {
traversal := getTraversal()
traversal.Traverse()
}
5-1 duck typing
- “像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子”
- 描述事物的外部行为而非内部结构
- 严格说GO属于结构化类型系统,类似duck typing
许多语言都支持Ducking Typing,通常Duck Typing是动态编程语言用来实现多态的一种方式。
补充:多态
python中的Duck Typing
def download(fetcher):
return fetcher.get("http://xxx");
有一个download函数,传过来一个fetcher参数,fetcher是可以获取URL链接的资源的。这个fetcher就是一个Ducking
Typing的对象,使用者约定好这个fetcher会有一个get函数就可以了。
只有运行时才知道传入的fetcher有没有get函数。
C++中的Duck Typing
C++不是动态语言,但是它能支持Ducking
Typing,通过模板的方法来实现。 在编译时,才知道传入的fetcher有没有get方法。
template <class F>
string download(const F& fetcher) {
return fetcher.get("http://xxxx")
}
Java中的类似代码
Java 没有Ducking Typing,它只有类似的代码。Java的ducking typing:
<F extends FetcherInterface>
String download(F fetcher) {
return fetcher.get("http://xxxx")
}
如果download函数只依赖fetcher的get方法,而FetcherInterface接口必须实现除get方法以外,还有其他方法,那么也要一一实现,非常不灵活。
Go中的Duck Typing
在Java的Duck Typing类似代码中,如果fetcher参数需要同时实现两个或两个以上的接口方法时,Java是没有办法做到的。但Go语言可以做到。
type Fetcher interface {
Get(url string) string
}
type Saver interface {
Save(content string)
}
type FetcherAndSaver interface {
Fetcher
Saver
}
func download(f fetcher) string {
return f.Get("http://xxxx")
}
func save(f saver) {
f.Save("some thing")
}
func downloadAndSave(f FetcherAndSaver) {
content := f.Get("http://xxxx")
f.Save(content)
}
// 实现者
type MyFetcherAndSaver struct {
}
func (f MyFetcherAndSaver) Get(url string) string {
...
}
func (f MyFetcherAndSaver) Save(content string) {
...
}
func main() {
f := MyFetcherAndSaver{}
download(f)
save(f)
downloadAndSave(f)
}
这里定义了三个接口,只要有Get方法的就是Fetcher,只要有Save方法的就是Saver,同时有Get方法和Save方法的就是FetcherAndSaver。
实现者MyFetcher并不需要声明它实现了哪些接口,只要它有相关接口的所定义的方法,那么它的实例就能作为Fetcher接口来使用,又能作为Saver接口来使用,也能作为FetcherAndSaver接口来使用。
Go的实现方法相对比较灵活,又不失类型检查。总的来说,有以下特点:
- 即能同时实现多个接口
- 又具有python,c++的Duck Typing灵活性
- 又具有java的类型检查
5-2 接口的定义与实现
接口的定义
download(使用者)——> retriever(实现者)
- 接口由使用者定义
// retriever/mooc/moocRetriver.go
package mooc
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}
package real
import (
"net/http"
"net/http/httputil"
"time"
)
type Retriever struct {
UserAgent string
TimeOut time.Duration
}
func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(
resp, true)
resp.Body.Close()
return string(result)
}
package main
import (
"fmt"
"learngo/retriever/real"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("http://www.imooc.com")
}
func main() {
var r Retriever
//r = mooc.Retriever{"this is a fake imooc.com"}
r = real.Retriever{}
fmt.Println(download(r))
}
接口的实现
- 接口的实现是隐式的
- 只需要实现里面的方法
5-3 接口的值类型
func main() {
var r Retriever
r = mooc.Retriever{"this is a fake imooc.com"}
fmt.Printf("%T %v\n",r,r) // mooc.Retriever {this is a fake imooc.com}
r = real.Retriever{}
fmt.Printf("%T %v\n", r, r) // real.Retriever { 0s}
//fmt.Println(download(r))
}
接口变量里面有什么
接口变量中包括:
- 实现者的类型
- 实现者的值,或者说是实现者的指针,指向实现者
查看接口变量
- 表示任何类型:interface{}
- type assertion
- type switch
var r Retriever
r 是什么,r 的类型判断
func main() {
var r Retriever
r = mooc.Retriever{"this is a fake imooc.com"}
fmt.Printf("%T %v\n",r,r) // mooc.Retriever {this is a fake imooc.com}
r = real.Retriever{}
fmt.Printf("%T %v\n", r, r) // real.Retriever { 0s}
//fmt.Println(download(r))
}
将Retriever定义为指针接收者
func (r *Retriever) Get(url string) string
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
fmt.Printf("%T %v\n", r, r) // *real.Retriever &{Mozilla/5.0 1m0s}
如何知道r的类型
方法一:switch
package main
import (
"fmt"
"learngo/retriever/mooc"
"learngo/retriever/real"
"time"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("http://www.imooc.com")
}
func inspect(r Retriever) {
fmt.Printf("%T %v\n",r,r)
switch v := r.(type) {
case mooc.Retriever:
fmt.Println("Contents:", v.Contents)
case *real.Retriever:
fmt.Println("UserAgent:", v.UserAgent)
}
}
func main() {
var r Retriever
r = mooc.Retriever{"this is a fake imooc.com"}
inspect(r) //mooc.Retriever {this is a fake imooc.com} Contents: this is a fake imooc.com
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
inspect(r) // *real.Retriever &{Mozilla/5.0 1m0s} UserAgent: Mozilla/5.0
//fmt.Println(download(r))
}
方法二:type assertion
realRetriever := r.(*real.Retriever)
fmt.Println(realRetriever.TimeOut)
或是
if realRetriever, ok := r.(*real.Retriever); ok {
fmt.Println(realRetriever.TimeOut)
} else {
fmt.Println(ok)
}
接口变量里面有什么
- 接口变量自带指针
- 接口变量同样采用值传递,几乎不需要使用接口的指针
- 指针接收者只能以指针方式使用;值接收者都可
5-4 接口的组合
package main
import (
"fmt"
"learngo/retriever/mooc"
"learngo/retriever/real"
"time"
)
type Retriever interface {
Get(url string) string
}
type Poster interface {
Post(url string, form map[string]string) string
}
const url = "http://www.imooc.com"
func download(r Retriever) string {
return r.Get(url)
}
func post(poster Poster) {
poster.Post(url,
map[string] string {
"name": "ccmouse",
"course": "golang",
})
}
type RetrieverPoster interface {
Poster
Retriever
}
// 接口的组合
func session(s RetrieverPoster) string {
s.Post(url, map[string]string{
"contents": "another fake imooc.com",
})
return s.Get(url)
}
func inspect(r Retriever) {
fmt.Printf("%T %v\n",r,r)
switch v := r.(type) {
case mooc.Retriever:
fmt.Println("Contents:", v.Contents)
case *real.Retriever:
fmt.Println("UserAgent:", v.UserAgent)
}
}
func main() {
var r Retriever
retriever := &mooc.Retriever{"this is a fake imooc.com"}
inspect(r) //mooc.Retriever {this is a fake imooc.com} Contents: this is a fake imooc.com
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
inspect(r) // *real.Retriever &{Mozilla/5.0 1m0s} UserAgent: Mozilla/5.0
//fmt.Println(download(r))
// type assertion
if realRetriever, ok := r.(*real.Retriever); ok {
fmt.Println(realRetriever.TimeOut)
} else {
fmt.Println(ok)
}
fmt.Println("Try a session")
fmt.Println(session(retriever)) // this is a fake imooc.com
}
package mooc
type Retriever struct {
Contents string
}
func (r Retriever) Get(url string) string {
return r.Contents
}
func (r Retriever) Post(url string, form map[string]string) string {
r.Contents = form["contents"]
return "ok"
}
session(retriever) 的输出为this is a fake imooc.com,并没有被post所改变,因为Retriever是值接收者,改为指针接收者
5-5 常用系统接口
stringer相当于toString功能,还有reader writer等接口
func (r *Retriever) String() string {
return fmt.Sprintf("Retriever: {Contents=%s}", r.Contents)
}
package main
import (
"fmt"
"learngo/retriever/mooc"
"learngo/retriever/real"
"time"
)
type Retriever interface {
Get(url string) string
}
type Poster interface {
Post(url string, form map[string]string) string
}
const url = "http://www.imooc.com"
func download(r Retriever) string {
return r.Get(url)
}
func post(poster Poster) {
poster.Post(url,
map[string] string {
"name": "ccmouse",
"course": "golang",
})
}
type RetrieverPoster interface {
Poster
Retriever
}
// 接口的组合
func session(s RetrieverPoster) string {
s.Post(url, map[string]string{
"contents": "another fake imooc.com",
})
return s.Get(url)
}
func inspect(r Retriever) {
fmt.Println("Inspecting", r)
fmt.Printf(" > %T %v\n",r,r)
fmt.Print(" > Type switch:")
switch v := r.(type) {
case *mooc.Retriever:
fmt.Println("Contents:", v.Contents)
case *real.Retriever:
fmt.Println("UserAgent:", v.UserAgent)
}
fmt.Println()
}
func main() {
var r Retriever
retriever := mooc.Retriever{"this is a fake imooc.com"}
r = &retriever
inspect(r) //mooc.Retriever {this is a fake imooc.com} Contents: this is a fake imooc.com
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
inspect(r) // *real.Retriever &{Mozilla/5.0 1m0s} UserAgent: Mozilla/5.0
//fmt.Println(download(r))
// type assertion
if realRetriever, ok := r.(*real.Retriever); ok {
fmt.Println(realRetriever.TimeOut)
} else {
fmt.Println(ok)
}
fmt.Println("Try a session")
fmt.Println(session(&retriever)) // this is a fake imooc.com
}
// 输出
Inspecting Retriever: {Contents=this is a fake imooc.com}
> *mooc.Retriever Retriever: {Contents=this is a fake imooc.com}
> Type switch:Contents: this is a fake imooc.com
Inspecting &{Mozilla/5.0 1m0s}
> *real.Retriever &{Mozilla/5.0 1m0s}
> Type switch:UserAgent: Mozilla/5.0
1m0s
Try a session
another fake imooc.com
6 函数与闭包
6-1 函数式编程
package main
import "fmt"
// 累加器
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}
func main() {
a := adder()
for i := 0; i < 10; i++ {
fmt.Print(a(i), " ") // 0 1 3 6 10 15 21 28 36 45
}
}
函数式编程 VS 函数指针
- 函数是一等公民:参数,变量,返回值都可以是函数
- 高阶函数
- 函数 -> 闭包
变量的作用域有两种:全局变量和局部变量;函数内部可以直接读取全局变量;在函数外部无法读取函数内的局部变量。
能够读取其他函数内部变量的函数,就是闭包。本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的用途:
- 从外部读取函数内部的变量
- 将创建的变量的值始终保持在内存中
- 封装对象的私有属性和私有方法
python 中的闭包
- python 原生支持闭包
- 使用closure来查看闭包内容
def adder():
sum = 0
def f(value):
nonlocal sum
sum += value
return sum
return f
Java中的闭包
Function<Integer, Integer> adder() {
final Holder<Integer> sum = new Holder<>(0);
return (Integer value) -> {
sum.value += value;
return sum.value;
}
}
6-2 函数式编程例一
斐波那
package main
import "fmt"
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := fibonacci()
fmt.Println(f()) // 1
fmt.Println(f()) // 1
fmt.Println(f()) // 2
fmt.Println(f()) // 3
fmt.Println(f()) // 5
fmt.Println(f()) // 8
fmt.Println(f()) // 13
fmt.Println(f()) // 21
}
为斐波那实现reader
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
func fibonacci() intGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
type intGen func() int
func (g intGen) Read(p []byte) (n int, err error) {
next := g()
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)
// TODO: incorrect if p is too small!
return strings.NewReader(s).Read(p)
}
func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
func main() {
f := fibonacci()
printFileContents(f)
//fmt.Println(f()) // 1
//fmt.Println(f()) // 1
//fmt.Println(f()) // 2
//fmt.Println(f()) // 3
//fmt.Println(f()) // 5
//fmt.Println(f()) // 8
//fmt.Println(f()) // 13
//fmt.Println(f()) // 21
}
6-3 函数式编程例二
使用函数来遍历二叉树
package tree
import (
"fmt"
)
type TreeNode struct {
Value int
Left, Right *TreeNode
}
func (node TreeNode) Print() {
fmt.Print(node.Value, " ")
}
func (node *TreeNode) SetValue(value int) {
if node == nil {
fmt.Println("Setting value to nil " + "node. Ignored.")
return
}
node.Value = value
}
// 中序遍历
func (node *TreeNode) Traverse() {
node.TraverseFunc(func(node *TreeNode){
node.Print()
})
fmt.Println()
}
func (node *TreeNode) TraverseFunc(f func(*TreeNode)) {
if node == nil{
return
}
node.Left.TraverseFunc(f)
f(node) // 可以定义任何操作
node.Right.TraverseFunc(f)
}
// 工厂函数
func CreateNode(value int) *TreeNode {
return &TreeNode{Value: value}
}
// 还可以做其他操作
nodeCount := 0
root.TraverseFunc(func(node *tree.TreeNode) {
nodeCount++
})
fmt.Println("nodeCount: ", nodeCount)
7 资源管理与出错处理(catch all the errors)
7-1 defer调用
- 确保调用在函数结束时发生
- 参数在defer语句时计算
- defer列表为后进先出
package main
import "fmt"
func tryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}
func main() {
tryDefer() // 输出为3 2 1 可以将defer定义为栈,先进后出,用defer定义不担心提前return或者出现Panic
}
7-2 错误处理概念
// 进行错误处理
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Println(pathError.Op, pathError.Path, pathError.Err) // open fib.txt file exists
}
return
}
7-3 服务器统一出错处理
package main
import (
"io/ioutil"
"net/http"
"os"
)
// 文件列表服务器
func main() {
http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
// 文件不存在出现Panic错误不会关闭服务器
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
writer.Write(all)
})
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
实现统一的错误
func HandleFileList (writer http.ResponseWriter, request *http.Request) error{
path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
// 文件不存在出现Panic错误不会关闭服务器
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
type appHandler func(writer http.ResponseWriter, request *http.Request) error
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handler(writer, request)
if err != nil {
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
default:
code = http.StatusInternalServerError
}
http.Error(
writer,
http.StatusText(code),
code)
}
}
}
7-4 Panic和recover
Go语言不支持传统的try...catch...finally这种异常,在Go语言中,使用多值返回来返回错误,引入Exception处理:defer,panic,recover
panic
- 停止当前函数执行
- 一直向上返回,执行每一层的defer
- 如果没有遇见recover,程序退出、
recover
- 仅在defer调用中使用
- 获取panic的值
- 如果无法处理,可重新panic
7-5 服务器统一出错处理
如果直接访问http://localhost:8888/fib.txt,则服务器直接出错,但因为系统有保护,服务器不会关闭
自己定义保护
package filelisting
import (
"io/ioutil"
"net/http"
"os"
"strings"
)
const prefix = "/list/"
type userError string
func (e userError) Error() string {
return e.Message()
}
func (e userError) Message() string {
return string(e)
}
func HandleFileList (writer http.ResponseWriter, request *http.Request) error{
strings.Index(request.URL.Path, prefix)
if strings.Index(
request.URL.Path, prefix) != 0 {
return userError("path must start with " + prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
// 文件不存在出现Panic错误不会关闭服务器
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
package main
import (
"learngo/errhanding/filelisteningserver/filelisting"
"log"
"net/http"
"os"
)
type appHandler func(writer http.ResponseWriter, request *http.Request) error
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
defer func(){
if r := recover(); r != nil {
log.Printf("panic: %v", r)
http.Error(writer,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()
err := handler(writer, request)
if err != nil {
log.Printf("Error occurred handing request: %s", err.Error())
if userErr, ok := err.(userError); ok {
http.Error(writer,
userErr.Message(),
http.StatusBadRequest)
return
}
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(
writer,
http.StatusText(code),
code)
}
}
}
// 可以展示给用户的error
type userError interface {
error
Message() string
}
// 文件列表服务器
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
有疑问加站长微信联系(非本文作者)