开始微服务,那就先温习下golang语法吧;
golang变量类型
1. 整形
Go %b 表示为二进制 %c 该值对应的unicode码值 %d 表示为十进制 %o 表示为八进制 %q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 %x 表示为十六进制,使用a-f %X 表示为十六进制,使用A-F %U 表示为Unicode格式:U+1234,等价于"U+%04X" %E 用科学计数法表示 %f 用浮点数表示 |
2. 浮点型
- • float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度
- • float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度
3. Byte 和 rune
Go var a byte = 65 byte,占用1个节字 var b rune = 'B' rune,占用4个字节 |
4. String
转义:
Go var mystr02 string = ` ` var mystr01 string = "\r\n" 换行: var mystr01 string = `你好呀! |
我的公众号是: Go欢迎大家关注`
5. Array
Go // 第一种方法 var arr [3]int = [3]int{1,2,3} // 第二种方法 arr := [3]int{1,2,3} arr := [...]int{1,2,3} |
6. 切片
区间: 左闭右开
Apache arr[0:2] --> 1,2 sliTest := make([]int, 2, 3) sliTest[1] = 5 sliTest[0] = 1 sliTest = append(sliTest, 6) |
7. Map
Go mapA := map[int]string{1:"one",2:"two"} mapA[6] = "six" //进行判断key是否存在: if math, ok := mapA[6]; ok { } //循环遍历:key+value for key,value := range mapA{ fmt.Println(key,value) } //循环遍历:key for key := range mapA{ fmt.Println(key) } //循环遍历:value for _, value := range mapA{ fmt.Println(value) } |
8. Ptr
指针
Go ptr := new(string) *ptr = "string ptr" str := *ptr ptr2 := &str in1t := 1 ptr3 := &in1t ptr4 := &(*ptr3) println("name:",sliTest) |
9. Switch case
Go func getResult(args ...int) bool { return true } //接函数 switch getResult(chinese, english, math) { // case 后也必须是布尔类型 case true: fmt.Println("该同学所有成绩都合格") case false: fmt.Println("该同学有挂科记录") } //这种 可以替代if else score := 30 switch { case score >= 95 && score <= 100: fmt.Println("优秀") case score >= 80: fmt.Println("良好") case score >= 60: fmt.Println("合格") case score >= 0: fmt.Println("不合格") default: fmt.Println("输入有误...") } //穿透性 s := "hello" switch { case s == "hello": fmt.Println("hello") fallthrough //直接不比较就穿透到下面的case,不进行break case s != "world": fmt.Println("world") } output: hello world |
10. For
for [condition | ( init; condition; increment ) | Range]{
statement(s);
}
- • condition: 接一个条件表达式
Go a := 1 for a <= 5 { fmt.Println(a) a ++ } |
- • ( init; condition; increment ): 接三个表达式
Go for i := 1; i <= 5; i++ { fmt.Println(i) } |
- • Range: 接一个 range 表达式
Go for key, value := range myarr { fmt.Println(key,value) } |
- • 不接表达式
Go for { fmt.Println("无限") } // 等价于 for ;; { fmt.Println("无限") } |
11. Goto
Go if name == "lern" { goto flagEnd } goto flag //var i = 0 这个会报错,flag间不允许定义 flag: fmt.Println("flag") fmt.Println("flag2") fmt.Println("flag3") flagEnd: fmt.Println("flagEnd") |
12. Defer
Go func myfunc() { fmt.Println("B") } func main() { defer myfunc() fmt.Println("A") } 输出: A B //先后顺序: 遵循栈的方式 func main() { name := "go" defer fmt.Println(name) // 输出: go name = "python" defer fmt.Println(name) // 输出: python name = "java" fmt.Println(name) } 输出: java python go |
13. interface{}
Go print("string") func print(param interface{}){ if value, ok := param.(string); ok{ fmt.Println(value) } } |
14. Select case
- • select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
- • select 的 case 是随机的,而 switch 里的 case 是顺序执行;
- • select 要注意避免出现死锁,同时也可以自行实现超时机制;
- • select 里没有类似 switch 里的 fallthrough 的用法;
- • select 不能像 switch 一样接函数或其他表达式。
Go func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) c2 <- "hello" select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) default: fmt.Println("No data received.") } } |
15. panic
异常,可以采用recover来进行恢复
Go func set_data(x int) { defer func() { // recover() 可以将捕获到的panic信息打印 if err := recover(); err != nil { fmt.Println(err) } }() // 故意制造数组越界,触发 panic var arr [10]int arr[x] = 88 } func main() { set_data(20) // 如果能执行到这句,说明panic被捕获了 // 后续的程序能继续运行 fmt.Println("everything is ok") } 输出: runtime error: index out of range [20] with length 10 everything is ok |
golang面向对象
1. Struct
Go //基类 type base struct { name string } type userinfo struct { name string age int base //组合 } func(self *userinfo) dump() { fmt.Println(self.name) //输出的是qingfeng fmt.Println(self.age) } func main() { bb := base{name: "qqq"} user := userinfo{ name: "qingfeng", age: 19, base: bb, } user.dump() fmt.Println(user.base.name) //这个才输出 qqq } |
2. Interface
Go type caller interface { test1() test2() } type son struct { userid int username string } type mun struct { userid int username string } func (self son) test1() { fmt.Println("son_test1",self.userid) } func (self son) test2() { fmt.Println("son_test2",self.username) } func (self mun) test1() { fmt.Println("mun_test1",self.userid) } func (self mun) test2() { fmt.Println("mun_test2",self.username) } m_son := son{ userid: 1001, username: "1001", } m_mun := mun{ userid: 1005, username: "1005", } m_calls := []caller{m_son, m_mun} for _,v := range m_calls{ v.test1() v.test2() } |
3. x.()
Go func findType(i interface{}) { switch x := i.(type) { case int: fmt.Println(x, "is int") case string: fmt.Println(x, "is string") case nil: fmt.Println(x, "is nil") default: fmt.Println(x, "not type matched") } } var k interface{} // nil t3, ok := k.(interface{}) fmt.Println(t3, "-", ok) var k int // nil t3, ok := k.(int) fmt.Println(t3, "-", ok) |
4. reflect
Go func main() { var name string = "Go编程时光" fmt.Println("真实世界里 name 的原始值为:", name) v1 := reflect.ValueOf(&name) v2 := v1.Elem() v2.SetString("Python编程时光") fmt.Println("通过反射对象进行更新后,真实世界里 name 变为:", name) } |
内容太多:参考文章,写的挺详细的
http://golang.iswbm.com/en/latest/c02/c02_08.html
5. Func
Go func double(a int) (b int) { // 不能使用 := ,因为在返回值哪里已经声明了为int b = a * 2 // 不需要指明写回哪个变量,在返回值类型那里已经指定了 return }
func main() { fmt.Println(double(2)) } output: 4 |
6. chan
Go func main() { pipline := make(chan int, 10) fmt.Printf("信道可缓冲 %d 个数据 ", cap(pipline)) pipline<- 1 fmt.Printf("信道中当前有 %d 个数据", len(pipline)) } |
定义只读信道
Go var pipline = make(chan int) type Receiver = <-chan int // 关键代码:定义别名类型 var receiver Receiver = pipline |
定义只写信道
Go var pipline = make(chan int) type Sender = chan<- int // 关键代码:定义别名类型 var sender Sender = pipline |
仔细观察,区别在于 <- 符号在关键字 chan 的左边还是右边。
- • <-chan 表示这个信道,只能从里发出数据,对于程序来说就是只读
- • chan<- 表示这个信道,只能从外面接收数据,对于程序来说就是只写
chan用来做锁
Go // 由于 x=x+1 不是原子操作// 所以应避免多个协程对x进行操作// 使用容量为1的信道可以达到锁的效果 func increment(ch chan bool, x *int) { ch <- true *x = *x + 1 <- ch } func main() { // 注意要设置容量为 1 的缓冲信道 pipline := make(chan bool, 1) var x int for i:=0;i<1000;i++{ go increment(pipline, &x) } // 确保所有的协程都已完成 // 以后会介绍一种更合适的方法(Mutex),这里暂时使用sleep time.Sleep(3) fmt.Println("x 的值:", x) }
|
7. WaitGroup
等待协程结束
- • Add:初始值为0,你传入的值会往计数器上加,这里直接传入你子协程的数量
- • Done:当某个子协程完成后,可调用此方法,会从计数器上减一,通常可以使用 defer 来调用。
- • Wait:阻塞当前协程,直到实例里的计数器归零。
Go func worker(x int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < 5; i++ { fmt.Printf("worker %d: %d ", x, i) } } func main() { var wg sync.WaitGroup wg.Add(2) go worker(1, &wg) go worker(2, &wg) wg.Wait() } |
8. Mutex
互斥锁:
Go func add(count *int, wg *sync.WaitGroup, lock *sync.Mutex) { for i := 0; i < 1000; i++ { lock.Lock() //加锁 *count = *count + 1 lock.Unlock() //解锁 } wg.Done() } func main() { var wg sync.WaitGroup lock := &sync.Mutex{} count := 0 wg.Add(3) go add(&count, &wg, lock) go add(&count, &wg, lock) go add(&count, &wg, lock) wg.Wait() fmt.Println("count 的值为:", count) } |
读写锁:
Go func main() { lock := &sync.RWMutex{} lock.Lock() for i := 0; i < 4; i++ { go func(i int) { fmt.Printf("第 %d 个协程准备开始... ", i) lock.RLock() fmt.Printf("第 %d 个协程获得读锁, sleep 1s 后,释放锁 ", i) time.Sleep(time.Second) lock.RUnlock() }(i) } time.Sleep(time.Second * 2) fmt.Println("准备释放写锁,读锁不再阻塞") // 写锁一释放,读锁就自由了 lock.Unlock() // 由于会等到读锁全部释放,才能获得写锁 // 因为这里一定会在上面 4 个协程全部完成才能往下走 lock.Lock() fmt.Println("程序退出...") lock.Unlock() } |
9. goroutine
go的协程,理解成语言级别的
Go func mygo(name string) { for i := 0; i < 10; i++ { fmt.Printf("In goroutine %s ", name) // 为了避免第一个协程执行过快,观察不到并发的效果,加个休眠 time.Sleep(10 * time.Millisecond) } } func main() { go mygo("协程1号") // 第一个协程 go mygo("协程2号") // 第二个协程 time.Sleep(time.Second) } |
golang环境:
1. Go mod
- • go mod init:初始化go mod, 生成go.mod文件,后可接参数指定 module 名,上面已经演示过。
- • go mod download:手动触发下载依赖包到本地cache(默认为$GOPATH/pkg/mod目录)
- • go mod graph: 打印项目的模块依赖结构
- • go mod tidy :添加缺少的包,且删除无用的包
- • go mod verify :校验模块是否被篡改过
- • go mod why: 查看为什么需要依赖
- • go mod vendor :导出项目所有依赖到vendor下
- • go mod edit :编辑go.mod文件,接 -fmt 参数格式化 go.mod 文件,接 -require=golang.org/x/text 添加依赖,接 -droprequire=golang.org/x/text 删除依赖,详情可参考 go help mod edit
- • go list -m -json all:以 json 的方式打印依赖详情
golang编码规范:
1. 文件命名
- • 由于 Windows平台文件名不区分大小写,所以文件名应一律使用小写
- • 不同单词之间用下划线分词,不要使用驼峰式命名
- • 如果是测试文件,可以以 _test.go 结尾
- • 文件若具有平台特性,应以 文件名_平台.go 命名,比如 utils_ windows.go,utils_linux.go,可用的平台有:windows, unix, posix, plan9, darwin, bsd, linux, freebsd, nacl, netbsd, openbsd, solaris, dragonfly, bsd, notbsd, android,stubs
- • 一般情况下应用的主入口应为 main.go,或者以应用的全小写形式命名。比如MyBlog 的入口可以为 myblog.go
2. 常量命名
目前在网络上可以看到主要有两种风格的写法
- • 第一种是驼峰命名法,比如 appVersion
- • 第二种使用全大写且用下划线分词,比如 APP_VERSION
这两种风格,没有孰好孰弱,可自由选取,我个人更倾向于使用第二种,主要是能一眼与变量区分开来。
如果要定义多个变量,请使用 括号 来组织。
Go const ( APP_VERSION = "0.1.0" CONF_PATH = "/etc/xx.conf") |
3. 变量命名
和常量不同,变量的命名,开发者们的喜好就比较一致了,统一使用 驼峰命名法
- • 在相对简单的环境(对象数量少、针对性强)中,可以将完整单词简写为单个字母,例如:user写为u
- • 若该变量为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头。例如:isExist ,hasConflict 。
- • 其他一般情况下首单词全小写,其后各单词首字母大写。例如:numShips 和 startDate 。
- • 若变量中有特有名词(以下列出),且变量为私有,则首单词还是使用全小写,如 apiClient。
- • 若变量中有特有名词(以下列出),但变量不是私有,那首单词就要变成全大写。例如:APIClient,URLString
这里列举了一些常见的特有名词:
Go // A GonicMapper that contains a list of common initialisms taken from golang/lintvar LintGonicMapper = GonicMapper{ "API": true, "ASCII": true, "CPU": true, "CSS": true, "DNS": true, "EOF": true, "GUID": true, "HTML": true, "HTTP": true, "HTTPS": true, "ID": true, "IP": true, "JSON": true, "LHS": true, "QPS": true, "RAM": true, "RHS": true, "RPC": true, "SLA": true, "SMTP": true, "SSH": true, "TLS": true, "TTL": true, "UI": true, "UID": true, "UUID": true, "URI": true, "URL": true, "UTF8": true, "VM": true, "XML": true, "XSRF": true, "XSS": true,} |
4. 函数命名
- • 函数名还是使用 驼峰命名法
- • 但是有一点需要注意,在 Golang 中是用大小写来控制函数的可见性,因此当你需要在包外访问,请使用 大写字母开头
- • 当你不需要在包外访问,请使用小写字母开头
- • 另外,函数内部的参数的排列顺序也有几点原则
- • 参数的重要程度越高,应排在越前面
- • 简单的类型应优先复杂类型
- • 尽可能将同种类型的参数放在相邻位置,则只需写一次类型
5. 接口命名
使用驼峰命名法,可以用 type alias 来定义大写开头的 type 给包外访问。
Go type helloWorld interface { func Hello(); } type SayHello helloWorld 当你的接口只有一个函数时,接口名通常会以 er 为后缀 type Reader interface { Read(p []byte) (n int, err error) } |
6. 注释规范
注释分为
6.1 代码注释
用于解释代码逻辑,可以有两种写法
单行注释使用 // ,多行注释使用 /* comment */
Go // 单行注释 /*多行注释*/ |
另外,对于代码注释还有一些更加苛刻的要求,这个看个人了,摘自网络:
- • 所有导出对象都需要注释说明其用途;非导出对象根据情况进行注释。
- • 如果对象可数且无明确指定数量的情况下,一律使用单数形式和一般进行时描述;否则使用复数形式。
- • 包、函数、方法和类型的注释说明都是一个完整的句子。
- • 句子类型的注释首字母均需大写;短语类型的注释首字母需小写。
- • 注释的单行长度不能超过 80 个字符。
- • 类型的定义一般都以单数形式描述:
Go // Request represents a request to run a command. type Request struct { ... |
- • 如果为接口,则一般以以下形式描述:
Go // FileInfo is the interface that describes a file and is returned by Stat and Lstat.type FileInfo interface { ... |
- • 函数与方法的注释需以函数或方法的名称作为开头:
Go // Post returns *BeegoHttpRequest with POST method. |
- • 如果一句话不足以说明全部问题,则可换行继续进行更加细致的描述:
Go // Copy copies file from source to target path.// It returns false and error when error occurs in underlying function calls. |
- • 若函数或方法为判断类型(返回值主要为 bool 类型),则以 <name> returns true if 开头:
Go // HasPrefix returns true if name has any string in given slice as prefix.func HasPrefix(name string, prefixes []string) bool { ... |
6.2 特别注释
- • TODO:提醒维护人员此部分代码待完成
- • FIXME:提醒维护人员此处有BUG待修复
- • NOTE:维护人员要关注的一些问题说明
7. 包的导入
单行的包导入
Go import "fmt" 多个包导入,请使用 {} 来组织 import { "fmt" "os"} |
另外根据包的来源,对排版还有一定的要求
- • 标准库排最前面,第三方包次之、项目内的其它包和当前包的子包排最后,每种分类以一空行分隔。
- • 尽量不要使用相对路径来导入包。
Go import ( "fmt" "html/template" "net/http" "os" "github.com/codegangsta/cli" "gopkg.in/macaron.v1" "github.com/gogits/git"
"github.com/gogits/gfm" "github.com/gogits/gogs/routers" "github.com/gogits/gogs/routers/repo" "github.com/gogits/gogs/routers/user" ) |
8. gofmt
有空的可以用gofmt来进行格式化;
参考:https://jingyan.baidu.com/article/c45ad29c64cfe7051653e245.html