最近公司用的语言是GO,上班期间记录一下随手的笔记。
go环境安装
go不像java这种成熟的语言有eclipse这种编译器,但是go提供了很多插件
首先先选择一个编译器,我这边使用的vscode,接着在vscode中配置go的开发环境
配置开发环境之后,接着需要安装一些go的插件方便开发,首先先了解每种插件的作用,像gocode的作用就是代码补全,具体可参考https://blog.csdn.net/langzi7...
安装插件有几种方式,可以通过vscode自动安装,也可以通过命令行安装,但因为有些插件需要vpn,故可先从github上clone插件包,然后再进行安装,过程中可能会相关的问题.....
可跟着一下博文安装https://blog.csdn.net/Yo_oYgo...
go build go install go getValue
- go build:在当前目录下生成可执行文件,注意:go build指令会调用所有引用包的源码,重新编译,而不是直接使用pkg里的编译后的文件,如果在
$GOROOT
或者$GOPATH
下没有找到import引入的项目源码,就会报错。 - go install:编译源代码,如果为可执行文件(package "main"且包含main方法),则会编译生成可执行文件到
$GOPATH\bin
目录下,可执行文件import引入其他包,就会被编译到$GOPATH/pkg/$GOOS_$GOARCH
目录下。 - go get:git clone到
$GOPATH\src
+ go install
method和function的关系
method是特殊的function,定义在某一特定的类型上,通过类型的实例来进行调用,这个实例被叫receivermethod belongs to instance
fuction is a global function belongs to package
使用method的时候注意几点:
- 虽然method的名字一模一样,但是如果接受者不一样,那么method就不一样
- method里面可以访问接收者的字段
- 调用method通过.访问,就像struct里面访问字段一样
指针
类似于java的引用,但是也保证的内存安全,类似C语言内存泄漏而程序崩溃的指针运算(所谓的指针算法,如:pointer+2,移动指针指向字符串的字节数或数组的某个位置)是不允许的。
指针的传递很廉价,只占用4或8个字节。
但传递一个变量的引用(函数的参数),这样不会传递变量的拷贝。
s := "good bye"
var p *string = &s
*p = "hello"
fmt.Printf("Here is the pointer p:%p\n", p)
fmt.Printf("Here is the string *p:%s\n", *p)
fmt.Printf("Here is the string s:%s\n", s)
map的value值可以为任何类型
interface的实现通过结构体实现相同的方式
package main
import (
"fmt"
)
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
}
func getValue() float32 {
return 0
}
type valueable interface {
getValue() float32
}
func showValue(asset valueable) {
fmt.Printf("Value of the asset is %f\n", asset.getValue())
}
func main() {
s := getValue()
fmt.Println(s)
//接口实现
// var o valueable = stockPosition{"GOOG", 577.20, 4}
// showValue(o)
// o = car{"BMW", "M3", 66500}
// showValue(o)
//map的value可以为任何类型
// mf := map[int]func() int{
// 1: func() int { return 10 },
// 2: func() int { return 20 },
// 3: func() int { return 50 },
// }
// fmt.Println(mf)
//map的value为interface时
//当value为interface,即map中的value值为interface的实现
//跟java的面向对象有点绕,go的struct在java完全可以用class来实现
//具有接口相同的方法就可以算是实现了接口
var c car = car{"BMW", "M3", 66500}
var v valueable = stockPosition{"GOOG", 577.20, 4}
mf := map[string]interface{ getValue() float32 }{
"account": c,
"password": v,
}
for _, value := range mf {
fmt.Println(value)
fmt.Println(value.getValue())
}
}
interface万能模型
测试
测试文件的后缀为_test.go,并且应该跟被测试文件放在同一个目录下
测试数据放在一个特殊的testdata目录中,使用go build时,testdata目录和_test.go文件都会被编译器忽略
单元测试gomock
new跟make
func new(Type) *Type
分配空间,参数一个类型,返回值指向这个新分配的零值的指针
func make(Type,size IntegerType)
分配并且初始化一个slice,或者map或者chan对象,并且只能是这三种对象。
第一个参数为类型,第二个参数为长度
返回值是这个类型.
int类型跟string类型的切换
go主要通过strings包来完成对字符串的主要操作
strings.HasPrefix(s,prefix string) bool等
多返回值函数的错误
Go中函数经常使用两个返回值表示执行是否成功,返回某个值以及true表示成功,返回零值(或nil)和false表示失败。
当不使用true或false的时候,也可以使用一个error类型的变量来替代第二个返回值。
习惯用法:
value,err := pack1.Function(param1)
if err != nil{
fmt.Printf("An error occured in pack1.Function1 with parameter %v",param1)
return err
}
//未发生错误,继续执行
测试用例需要包括
- 正常的用例
- 反面的用例
- 边界检查用例
go中的实现泛型
go中并没有泛型的概念,但可通过interface来实现
例如下面的冒泡排序
package main
import "fmt"
type Sortable interface {
Len() int
Less(int, int) bool
Swap(int, int)
}
func bubbleSort(array Sortable) {
for i := 0; i < array.Len(); i++ {
for j := 0; j < array.Len()-1-i; j++ {
if array.Less(j+1, j) {
array.Swap(j, j+1)
}
}
}
}
//实现接口的整形切片
type IntArr []int
func (array IntArr) Len() int {
return len(array)
}
func (array IntArr) Less(i int, j int) bool {
return array[i] < array[j]
}
func (array IntArr) Swap(i int, j int) {
array[i], array[j] = array[j], array[i]
}
func main() {
intArr1 := IntArr{3, 5, 1, 2, 3, 7, 88}
bubbleSort(intArr1)
fmt.Println(intArr1)
}
参考:https://studygolang.com/artic...
闭包
当不希望给函数起名字的时候,可以使用匿名函数,例如:func(x, y int) int {return x + y }
这样一个函数不能够独立存在,但可以被赋值为某个变量,例如 fplus := func(x, y int) int {return x + y },然后通过变量名对函数进行调用 fplus(3,4)
当然可以直接对匿名函数进行直接调用:func(x, y int) int {return x + y}(3,4)
表示参数列表的第一对括号必须紧着着关键字func,因为匿名函数没有名称,花括号{}涵盖着函数体,最后一堆括号表示对匿名函数的直接调用
linq
异常
defer
defer相当于java的finally
panic
panic是用来表示非常严重的不可恢复的错误,在Go语言是一个内置函数,接收一个interface{}类型的值作为参数。panic的作用就像我们平常接触的异常,不过Go没有try...catch,所以panic一般会导致程序挂掉(除非recover),
所以在go语言中的异常就是真的异常了。
关键的一点,函数执行的时候panic了,将先到defer中执行完,panic再想传递
recover
recover用来捕获panic,被recover捕获到的panic将不会向上传递
recover之后,逻辑并不会恢复到panic那个点去,函数还在defer之后返回
导包下划线"_"的作用
引入该包,但是并非真的需要使用使用这些包,同时会执行它的init()函数
构建http服务
func IndexHander(w http.ResponseWriter,r *http.Request){
fmt.Fprintln("hello wrold")
}
func main(){
http.HandleFunc("/",IndexHander)
http.ListenAndServer("127.0.0.1:8080",nil)
}
接收request的过程,最重要的莫过于路由(router),即实现一个Multiplexer器,Go中既可以使用内置的multiplexer--DefaultServerMux,也可以自定义。
Multiplexer路由的目的就是为了找到处理器函数(handle),后者讲对request进行处理,同时构建response
函数作为参数的优势?
Go语言跟JS一样同样都支持用函数作为参数传递,那么这种方式的优势体现点在 哪里?
//TODO
init函数
不能够有任何参数,虽然每个package里面可以写任意多个init函数,但这无论是可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件中只写一个init函数
Struct
声明方式
type person struct{
name string
age int
}
//1、
var P person
p.name = "dack"
p.age = 21
//2、
p := person{"dack",21}
//3、
p := person{
age : 21
name : "dack"
}
//4、可以通过new函数分配一个指针,此处P的类型为 *person
匿名字段
当一个匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入当前定义的这个struct
type Human struct{
name string
age int
weight int
}
type Student struct{
Human //匿名字段,那么默认Student就包含了Human的所有字段
speciality string
}
func main(){
mark := Student{Human{"dacl",21,65},"Go Science"}
mark.speciality = "AI"
mark.age = 25
mark.weight = 70
}
Student可以访问name和age就像自己用自己的字段一样
匿名对象能够实现字段的继承
Student还能访问Human
mark.Human = Human{"tony",22,55}
mark.Human.age = 1
不仅struct可以作为匿名字段,自定义类型、内置类型也可以作为匿名字段
如果遇到human里面有一个字段phone,student里面的字段phone,那么优先访问最外最外层
判断为nil
go中struct结构默认字段都会有零值,故不能用nil来判断struct是否为空,可通过额外的字段来判断struct是否填充或为空
type Demo struct{
ready bool
name string
//其他字段
}
在初始化的时候必须将ready设置为true
var d Demo
if !d.ready{
//do stuff
}
golang API json ,struct结构中标签(Tag)的使用
在golang中,命名都是推荐用驼峰方式,并且在首字母大小写有特殊含义:包外无法引用
但由于经常需要和其他系统进行数据交互,例如转换成json格式,存储到Redis等。这个时候如果直接使用属性名来作为键值会不符合项目要求
于是就有了tag,也就是有了json:name
并发
goroutine是GO并行设计的核心,goroutine说到底其实就是协程,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。
goroutine是通过Go的runtime管理的一个线程管理器,goroutine通过go关键字实现了,其实就是一个普通的函数
func say(s string){
for i := 0;i<5;i++{
runtime.Gosched() //让cpu把时间片让给别人,下次某个时候继续恢复该goroutine
fmt.Println(s)
}
}
func main(){
go say("world") //开一个新的Goroutine执行
say("hello") //当前的Goroutine执行
}
在Go 1.5之前调度器仅使用单线程,也就是说只实现了并发,想要发挥多核处理器的秉性,需要在我们的程序中显示调用runtime.GOMAXPROCS(n)告诉调度器同时使用多个线程。
channels
类似于Java的BlockingQueue
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,Go提供了一个很好的通信机制channel,channel可以与unix shell中的双向管道做类比:可以通过它发送或者接受值。这些值只能是特定的类型,channel类型。
定义一个人channal时,也需要定义发送到channel的值的类型。
注意:必须使用make创建channel:
ci := make(chan int)
cf := make(chan interface{})
channel通过操作符<-
来接受和发送数据
ch <- v //发送v到channel ch
v := <- ch //从ch中接收数据,并赋值给v
func sum(a []int,c chan int){
total := 0
for _,v : range a {
total += v
}
c <- total //send total to c
}
func main(){
a := []int{1,2,8,6,3,4,1}
c := make(chan int)
go sum(a[:len(a)/2],c)
go sum(a[len(a)/2:],c)
x,y := <- c,<- c //receive from c
fmt.Println(x,y,x+y)
}
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变得更加简单,而不需要显示的lock,所谓阻塞,也就是要读取(value <- ch)它将会被阻塞,知道有数据接收。其次,任何发送(ch <- 5)将会被阻塞,知道数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
Buffered Channels
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
ch := make(chan type, value)
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值
package main
import "fmt"
func main() {
c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
//修改为1报如下的错误:
//fatal error: all goroutines are asleep - deadlock!
Range和Close
在Go中也可以通过Range来操作Channel
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
func main(){
c := make(chan int ,10)
go fibonacci(cap(c),c)
for i := range c{
fmt.Println(i)
}
}
for-range能够不断的读取channel里面的数据,直到该channel被显示的关闭。关闭channel之后就无法发送数据了,可通过v,ok := <- ch测试channel是否被关闭,如果ok返回false,说明channel已经没有数据并且被关闭。
Select
当存在多个channel的时候,Go提供了关键字select,通过select可以监听多个channel上的数据流动。
select默认时阻塞的,只有监听的channel可以进行时才会运行,当多个channel都准备好的时候,随机选择一个执行。
func fibonacci(c,quit chan int){
x ,y := 1, 1
for{
select{
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不在阻塞等待channel)。
select{
case i := <-c :
// use i
default:
//当c阻塞的时候执行这里
}
超时
可通过select来设置超时避免整个程序进入阻塞
select{
case v := <- c:
//
case <- time.After(t * time.Second):
println("timeout")
//
break
}
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.7.md
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。