序
Go的特别之处:
Go中没有“对象”,没有继承多态,没有范型,没有try/catch;有接口,函数式编程,CSP并发模型(goroutine+channel)。
大纲:
- 面向接口:结构体,duck typing的概念,组合的思想
- 函数式编程:闭包的概念
- 工程化:资源管理、错误处理、测试和文档、性能调优
- 并发编程:goroutine和channel,理解调度器
- 实战:分布式爬虫
基础
函数外变量,需要用var/func关键字
内建类型:
…
go指针uintptr
byte,rune(go中的字符型,32位的,相当于其他语言的char,go 中没有char只有rune)
float32/float64,complex64,complex128(原生支持复数)
go的类型转化只有强制即显示的
package main
import "fmt"
func main() {
const a = 1var c float32 //const数值可以作为各种类型使用c = afmt.Println(c)//b := 2 //:=会确定类型//c = b//fmt.Println(c)const ( //用常量,实现枚举ca = 0cb = 1)fmt.Println(ca, cb)const (cai = iota //iota自增值,可参与运算,如 1<<iotacbi)fmt.Println(cai, cbi)
}
go的switch会自动break,除非使用fallthrough,switch后可以没有表达式,在case中加入判断就行。
函数参数可以是函数,并且func name的方式,很方便使用。
Go中只有按值传递,没有引用传递,但是有引用类型,如map,chan,slice等,所谓引用类型,只是其元数据中用指针执行实体数据,这样传参进去,边可以修改实体数据
Go语言参数传递是传值还是传引用
数组也是按值传递,[3]int和[5]int是不同类型,传递时会拷贝数组;go中一般不使用数组,而是用切片。
slice本身没有数据,是对底层数组array的一个view。
slice实现:ptr,len,cap,slice是可以像后扩展的,可以超越len,但不能超越cap,而s[i],i是不能超越len的。
slice每次扩充按*2。
map不保证遍历顺序。
map使用哈希表,必须可以比较,除了slice,map,function的内建类型都可以做key,struct类型中不包括slice,map,func可以作为key
rune,是uint32的别名
//每次遍历,range中文字符,序号加3,不是逐渐增的,因为中文字符占3个
for i,ch:=range "hello你好"{
fmt.Printf("(%d %c)",i,ch)
}
//使用range遍历pos,rune对
for i,ch:=range []rune("hello你好"){
fmt.Printf("(%d %c)",i,ch)
}
utf8.RuneCountInString获得字符数量
使用len获得字节长度
使用[]byte获得字节
为结构体添加方法,为方法接收者添加按值或者按引用传递,按引用的可修改结构体内容
值接收者和指针接收者
package main
import "fmt"
type point struct {
x, y int
}
//等同于func set(p point,x,y int)...
//方法中p point按值传递,所以无法修改point的xy
func (p point) set(x, y int) {
p.x = x p.y = y
}
func (p *point) setp(x, y int) {
p.x = x p.y = y
}
func main() {
var p pointp.set(1, 2)fmt.Println(p.x, p.y)p.setp(1, 2)fmt.Println(p.x, p.y)
}
//0 0
//1 2
封装
包,文件名可以和报名不一样,但一般建议一样,一个包可以放在不同的文件。
扩展包,
如何扩展系统类型或者别人的类型:
使用组合:再定义一个结构,将原结构组合到新结构中。
使用别名:type Queue []int,扩展[]int,通过type,再添加方法,实现扩展。
接口:duck typing
go接口的实现是隐式的,只需实现接口中的方法即可。
interface中有两个内容:实现者的类型;实现者或者指向实现者的指针:
mock.Retriever {this is a fake web}
使用类型断言v=r.(type)获取变量;v.Val使用v中的变量。
知识点:
- 接口变量自带指针
- 接口变量同样采用值传递,几乎不需要使用接口的指针
- 指针接收者实现只能以指针方式使用;值接收者都可以
查看接口变量:
- 表示任何类型:interface{}
- Type Assertion
- Type Switch
接口组合,可使用类似匿名结构的方式,方法实现,或主体是值则不会修改原结构中的内容,若主体是指针,则可以修改。
func(v Readable)Read()string{
...
}
func(v* Writeble)Write(s string){
...
}
type Reader interface{
Read()string
}
type Writer interface{
Write(s string)
}
type ReadWriter interface{
ReaderWriter
}
标准接口:
Stringer:String()String// toString
Reader:Read(p []byte)(n int,err error)
Writer:Write(p []byte)(n int,err error)
函数式编程
go的函数式编程主要体现在闭包上。
函数式编程vs函数指针:
- 函数是一等公民:参数、变量、返回值都可以是函数
- 高阶函数
- 函数->闭包
正统的函数式编程:
- 不可变性:不能有状态,只能有常量和函数
- 函数只能有一个参数
闭包的概念:
维基百科:在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
参考:
- 闭包的概念、形式与应用
- 闭包 (计算机科学)
疑问,闭包的价值在哪里??
- 闭包是数据和行为的组合,这使得闭包具有较好抽象能力,甚至可以模拟面向对象编程
- 简化代码
- 有益于模块化编程
python中的闭包:
原生支持闭包;并且可以通过__closure__查看闭包中的引用。
def adder():sum=0def f(value):nonlocal sumsum+=valuereturn sumprint("the closure of func f")print(f.__closure__)return f
>>> def adder():
... sum=0
... def f(value):
... nonlocal sum
... sum+=value
... return sum
... print("the closure of func f")
... print(f.__closure__)
... return f
...
>>> adder()
the closure of func f
(<cell at 0x1098b7fd8: int object at 0x109550c30>,)
<function adder.<locals>.f at 0x1098e0d90>
>>>
c++中的闭包:
过去:stl或者boost带有类似库;c++11以后支持闭包。
#include<iostream>
using namespace std;auto adder(){
auto sum=0;//[=]表示sum是传值引用进来的,mutable表示可以改变sumreturn [=](int value)mutable{
sum+=value;return sum;};
}
int main(){
auto a=adder();for(int i=0;i<10;i++){
cout<<a(i)<<endl;}return 0;
}
liudeMacBook-Pro:test liu$ g++ -std=c++14 test.cpp
liudeMacBook-Pro:test liu$ ./a.out
0
1
3
6
10
15
21
28
36
45
java中的闭包:
v1.8以后:使用Function接口和Lambda表达式来创建函数对象。
v1.7以前可以通过匿名类或Lambda表达式均支持闭包。
Function<Integer,Integer>adder(){
final Holder<Integer> sum=new Holder<>(0);return (Integer value)->{
sum.value+=value;return sum.value;};
}
go语言闭包的应用:
- 斐波那契数列
- 为函数实现接口
- 使用函数遍历二叉树
在闭包上的优势:
- 使用起来更为自然,不需要修改如何访问自由变量
- 没有Lambda表达式,但是有匿名函数
package mainimport ("bufio""fmt""io""strings"
)//用闭包实现斐波那契额数列
func fibonacci() func()int{
a,b:=0,1return func()int{
a,b=b,a+breturn a}
}
//为函数实现接口
type intGen func()int
func(g intGen)Read(p[]byte)(n int,err error){
next:=g()if next>500{
return 0,io.EOF}s:=fmt.Sprintf("%d\n",next)//todo:incorrect if p is too smallreturn strings.NewReader(s).Read(p)
}func printFileContents(reader io.Reader){
scan :=bufio.NewScanner(reader)for scan.Scan(){
fmt.Println(scan.Text())}
}
func main(){
fmt.Println("fibonacci")f:=fibonacci()fmt.Println("release1")for i:=0;i<10;i++{
fmt.Println(f())}fmt.Println("release2")printFileContents(intGen(f))fmt.Println()
}
fibonacci
release1
1
1
2
3
5
8
13
21
34
55
release2
89
144
233
377
详见课程 imooc/Google资深工程师深度讲解Go语言