build-web-application-with-golang
大神astaxie制作的学习资料build-web-application-with-golang
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md
golang语言基础关键字
1.golang 关键字总共也就二十五个 你将发现,Go的世界是那么地简洁,设计是如此地美妙,编写Go将会是一件愉快的事情。等回过头来,你就会发现这二十五个关键字是多么地亲切。
var和const参考2.2Go语言基础里面的变量和常量申明
package和import已经有过短暂的接触
func 用于定义函数和方法
return 用于从函数返回
defer 用于类似析构函数
go 用于并发
select 用于选择不同类型的通讯
interface 用于定义接口,参考2.6小节
struct 用于定义抽象数据类型,参考2.5小节
break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考2.3流程介绍里面
chan用于channel通讯
type用于声明自定义类型
map用于声明map类型数据
range用于读取slice、map、channel数据
close,make,new Panic ,Recover
package 当前文件属于哪个包
package (在我们的例子中是package main)这一行告诉我们当前文件属于哪个包,而包名main则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。除了main包之外,其它的包最后都会生成*.a文件(也就是包文件)并放置在$GOPATH/pkg/$GOOS_$GOARCH中(以Mac为例就是$GOPATH/pkg/darwin_amd64)。
func 定义了一个main函数
func定义了一个main函数,函数体被放在{}(大括号)中,就像我们平时写C、C++或Java时一样。
Go使用package(和Python的模块类似)来组织代码。main.main()函数(这个函数位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符(因为UTF-8的发明者也就是Go的发明者之一),所以它天生支持多语言。
var 定义变量
var关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面:定义多个变量,定义变量并初始化值,同时初始化多个变量
const 定义常量
const定义常量,可定义为数值、布尔值或字符串等类型。Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位), 若指定給float32自动缩短为32bit,指定给float64自动缩短为64bit
内置基础类型Boolean, 数值类型, 字符串, 错误类型error类型Go的package里面还专门有一个包errors来处理错误:err:=errors.New("emit macho dwarf: elf header corrupted")iferr !=nil{fmt.Print(err)}
有些技巧:分组声明,iota枚举,大写公有,小写私有
array就是数组,它的定义方式如下:var arr[n] type [n]type中,n表示数组的长度,type表示存储元素的类型。
slice “动态数组” 并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。 var fslice []int ;ar[:n]等价于ar[0:n],ar[n:]等价于ar[n:len(ar)],ar[:]等价于ar[0:len(ar)] ;slice是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值. 几个内置函数:len 获取slice的长度;cap 获取slice的最大容量; append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice;copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数
map Python中字典的概念
map也就是Python中字典的概念,它的格式为map[keyType]valueType
//声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
varnumbersmap[string]int
几个点:
map是无序的,
map的长度是不固定的,也就是和slice一样,也是一种引用类型,len函;
它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制;通过delete删除map的元素:
make用于内建类型(map、slice 和channel)的内存分配。make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。
(关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。 此处罗列 部分类型 的 “零值”)
int0
int80
int320
int640
uint0x0
rune0//rune的实际类型是 int32
byte0x0//byte的实际类型是 uint8
float320//长度为 4 byte
float640//长度为 8 byte
boolfalse
string""
new用于各种类型的内存分配。new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针。
if 满足条件就做某事,
if也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。
Go里面if条件判断语句中不需要括号 ifx >10{
if还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了ifx:=computedValue(); x >10{
;多个条件 }elseifinteger <3{
else if 否则做另一件事
goto 跳转到当前函数内定义的标签
goto跳转到必须在当前函数内定义的标签。例如假设这样一个循环: 标签名是大小写敏感的。
funcmyFunc() {i:=0Here://这行的第一个词,以冒号结束作为标签println(i)i++gotoHere//跳转到Here去}
for 控制逻辑
for强大的一个控制逻辑,它既可以用来循环读取数据,又可以当作while(golang 没有while)来控制逻辑,还能迭代操作forexpression1; expression2; expression3 {
expression1、expression2和expression3都是表达式,其中expression1和expression3是变量声明或者函数调用返回值之类的,expression2是用来条件判断,expression1在循环开始之前调用,expression3在每轮循环结束之时调用。
break 跳出当前循环
break操作是跳出当前循环,当嵌套过深的时候,break可以配合标签使用,即跳转至标签所指定的位置
break和continue还可以跟着标号,用来跳到多重循环中的外层循环
continue 跳过本次循环
need-to-insert-img
range 配合for 读取slice,map 和channel
range for配合range可以用于读取slice和map的数据:
fork,v:=rangemap{fmt.Println("map's key:",k)fmt.Println("map's val:",v)}//通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子 for i := range c能够不断的读取channel里面的数据,直到该channel被显式的关闭。c:=make(chanint,10)gofibonacci(cap(c),c)fori:=rangec{fmt.Println(i) }
Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_来丢弃不需要的返回值 例如 for_,v:=rangemap{
switch 处理多个条件的逻辑处理
switch 处理多个条件的逻辑处理, case 匹配项, default没有匹配的默认条件
case 匹配项
default 没有匹配的默认条件
switchsExpr{caseexpr1:someinstructionscaseexpr2:someotherinstructionscaseexpr3:someotherinstructionsdefault:othercode}
sExpr和expr1、expr2、expr3的类型必须一致。Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果switch没有表达式,它会匹配true。
case2,3,4:
我们把很多值聚合在了一个case里面,同时,Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用
fallthrough强制执行后面的case代码。
fallthrough 强制执行后面的case代码。
func 声明函数
func来声明函数
funcfuncName(input1type1,input2type2) (output1type1,output2type2) {//这里是处理逻辑代码//返回多个值returnvalue1,value2}
关键字func用来声明一个函数funcName
函数可以有一个或者多个参数,每个参数后面带有类型,通过,分隔
函数可以返回多个值
上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型
如果只有一个返回值且不声明返回值变量,那么你可以省略 包括返回值 的括号
如果没有返回值,那么就直接省略最后的返回信息
如果有返回值, 那么必须在函数的外层添加return语句
return 表明函数返回值
几个特性
多个返回值
funcSumAndProduct(A,Bint) (int,int) {
returnA+B, A*B
}
变参
funcmyfunc(arg...int) {} //for_,n:=rangearg {
传值与传指针
funcadd1(aint)int{//返回一个新值//简单的一个函数,实现了参数+1的操作funcadd1(a*int)int{// 请注意,*a=*a+1// 修改了a的值return*a// 返回新值}x1:=add1(&x)// 调用 add1(&x) 传x的地址
变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有add1函数知道x变量所在的地址,才能修改x变量的值。所以我们需要将x所在地址&x传入函数,并将函数的参数的类型由int改为*int,即改为指针类型,才能在函数中修改x变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。
底传指针有什么好处:
传指针使得多个函数能操作同一个对象。
传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体
Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针
Go中函数也是一种变量,我们可以通过type来定义它,
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
defer 延迟(defer)语句
defer 延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。defer后指定的函数会在函数退出前调用。
Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panic和recover机制。你的代码中应当没有,或者很少有panic的东西。这是个强大的工具,请明智地使用它
Panic 是一个内建函数,可以中断原有的控制流程,进入一个panic状态中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。panic可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
Recover 是一个内建的函数,可以让进入panic状态的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic状态,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
///这个函数演示了如何在过程中使用panicvaruser=os.Getenv("USER")funcinit() {ifuser==""{panic("no value for $USER") }}//下面这个函数检查作为其参数的函数在执行时是否会产生panic:functhrowsPanic(ffunc()) (bbool) {deferfunc() {ifx:=recover();x!=nil{b=true} }()f()//执行函数f,如果f中出现了panic,那么就可以恢复回来return}
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。
import import这个命令用来导入包文件
相对路径
import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import
绝对路径
import “shorturl/model” //加载gopath/src/shorturl/model模块
一些特殊的import
点操作点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println("hello world")可以省略的写成Println("hello world")
别名操作 别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字. 别名操作的话调用包函数时前缀变成了我们的前缀
_操作 _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
import("database/sql"_"github.com/ziutek/mymysql/godrv")
struct 声明新的类型
struct 声明新的类型,作为其它类型的属性或字段的容器我们可以创建一个自定义类型person代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之struct。如下代码所示:
typepersonstruct{namestringageint}varPperson// P现在就是person类型的变量了P.name="Astaxie"// 赋值"Astaxie"给P的name属性.///匿名字段 最外层的优先访问typeStudentstruct{Human// 匿名字段,那么默认Student就包含了Human的所有字段specialitystring}mark:=Student{Human{"Mark",25,120},"Computer Science"}mark.age=46// 返回的是 mark.Human.age
按照顺序提供初始化值
P := person{"Tom", 25}
通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)
method 函数的另一种形态,带有接收者的函数. method是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在func后面增加了一个receiver(也就是method所依从的主体)。
func (r ReceiverType) funcName(parameters) (results)
虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
method里面可以访问接收者的字段
调用method通过.访问,就像struct里面访问字段一样
指针作为receiverGo知道receiver是指针,他自动帮你转
method继承如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。
method重写我们可以在struct上面定义一个method,重写了匿名字段的方法,go语言会根据receiver,优先调用最外层的method (最外层的优先访问)
interface 接口
interface interface是一组method签名的组合,我们通过interface来定义对象的一组行为。它让面向对象,内容组织实现非常的方便
interface类型interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口
// 定义interfacetypeMeninterface{SayHi()Sing(lyricsstring)Guzzle(beerSteinstring)}
任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
interface函数参数interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数
interface变量存储的类型
Comma-ok断言ifvalue,ok:=element.(int); ok {
switch测试 switchvalue:=element.(type) {
caseint:
嵌入interface 类似Struct时学习的匿名字段,如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
反射运用reflect包
使用reflect一般分成三步
1.首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
t:=reflect.TypeOf(i)//得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v:=reflect.ValueOf(i)//得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
2.转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
tag:=t.Elem().Field(0).Tag//获取定义在struct里面的标签
name:=v.Elem().Field(0).String()//获取存储在第一个字段里面的值
3.获取反射值能返回相应的类型和数值
varxfloat64=3.4
v:=reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
反射的话,那么反射的字段必须是可修改的 p:=reflect.ValueOf(&x)
type 申明类型 ,stuct,接口, switch测试
类型包含array,slice ,map,int..等等,也可以新建的类型
typetestIntfunc(int)bool// 声明了一个函数类型typepersonstruct{//声明struct 类型namestringageint}typeElderlyGentinterface{//申明接口类型SayHi()Sing(songstring)SpendSalary(amountfloat32)}list:=make(List,3)// switch 测试list[0]=1//an intlist[1]="Hello"//a stringlist[2]=Person{"Dennis",70}forindex,element:=rangelist{switchvalue:=element.(type) {caseint:fmt.Printf("list[%d] is an int and its value is %d\n",index,value)casestring:fmt.Printf("list[%d] is a string and its value is %s\n",index,value)casePerson:fmt.Printf("list[%d] is a Person and its value is %s\n",index,value)default:fmt.Println("list[%d] is of a different type",index) }}
go goroutine 协程
go Go的runtime管理的一个线程管理器。goroutine通过go关键字实现了,其实就是一个普通的函数.goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
不要通过共享来通信,而要通过通信来共享。
chan 通信机制
chan 通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:
ci:=make(chanint)cs:=make(chanstring)cf:=make(chaninterface{})//channel通过操作符<-来接收和发送数据ch<-v// 发送v到channel ch.v:=<-ch// 从ch中接收数据,并赋值给v
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
Buffered Channelsch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。 ch:=make(chantype, value)
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
range,像操作slice或者map一样操作缓存类型的channel, fori:=rangec {
close 关闭channel
生产者通过内置函数close关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法v, ok := <-ch测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic
另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的
select 可以监听channel上的数据流动
select 可以监听channel上的数据流动。select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
funcfibonacci(c,quitchanint) {x,y:=1,1for{select{casec<-x:x,y=y,x+ycase<-quit:fmt.Println("quit")return} }}//select来设置超时 case <- time.After(5 * time.Second): // 这个是channel啊select{casev:=<-c:println(v)case<-time.After(5*time.Second):println("timeout")o<-truebreak}
runtime goroutine //特殊强调
runtime包中有几个处理goroutine的函数:
Goexit
退出当前执行的goroutine,但是defer函数还会继续调用
Gosched
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
NumCPU
返回 CPU 核数量
NumGoroutine
返回正在执行和排队的任务总数
GOMAXPROCS
用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
https://www.yuque.com/liujunjie/pshhng/gfsb9n