开始微服务,那就先温习下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 = `\r\n`

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)

}

内容太多:参考文章,写的挺详细的

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 个数据\n", 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\n", 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 个协程准备开始... \n", i)

lock.RLock()

fmt.Printf("第 %d 个协程获得读锁, sleep 1s 后,释放锁\n", 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\n", 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