Golang语法

变量定义

变量定义语法

  • 使用var关键字,可放在函数内,也可放在包内
// var + 变量名 + 数据类型(有默认值)
var a int
var b string = "string"
// 通过赋值自动判断类型
var c = true
// 集中定义
var (
    x = 1
    y = 2
)
  • 使用:=定义变量,只能在函数内使用
// := 用于赋初值
a, b := 1, 2

内建变量类型(builtin)

  • bool, string
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
  • byte, rune
  • float32, float64, complex64, complex128

强制类型转换

Golang要求强制类型转换,无隐式转换

a, b := 3, 4
var c int
c = int(math.Sqrt(float64(a * a + b * b)))

常量

使用const关键字定义常量,const数值可作为各种类型使用。

// 不确定类型
const a = 3
var b float64
b = a
// 合并定义
const (
    c = 1
    d = 2
)

枚举

const (
    spring = 0
    summer = 1
    autumn = 2
    winter = 3
)
// iota用在取值为0的const变量,后续变量依次加1
const (
    spring = iota
    summer
    autumn
    winter
)

变量定义要点

  • 变量类型写在变量名之后
  • 编译器可以推测变量类型
  • Golang没有char,使用rune
  • 原生支持复数类型

分支

if

const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println(contents)
}

// 可以在if条件中赋值,赋值变量的作用域在if语句中
if contents, err := ioutil.ReadFile(filename); err != nil {
    fmt.Println(err)
} else {
    fmt.Println(contents)
}

switch

switch后可以没有表达式,case结束后自动break,通过fallthrough不使用自动break。

func grade(score int) {
    grade := ""
    switch {
    case score < 0:
        panic(fmt.Sprintf("Wrong score: %d\n", score))
    case score < 60:
        grade = "C"
    case score < 80:
        grade = "B"
    case score < 90:
        grade = "A"
    case score < 100:
        // 自动break,除非出现fallthrough
        fallthrough
    case score >= 100:
        grade = "S"
    }
    fmt.Println(grade)
}

循环

for条件没有括号,没有while。

func convertToBin(n int) string {
    result := ""
    for ; n > 0; n /= 2 {
        lsb := n % 2
        result = strconv.Itoa(lsb) + result
    }
    return result
}

func printFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

func forever() {
    // while true
    for  {
        fmt.Println("forever")
    }
}

函数

  • 函数声明的顺序为:关键字,函数名,参数,返回类型,函数体。
  • 函数允许有多个返回值,并能在函数体内拿到返回值。
  • 函数可以作为另一个函数的参数
  • 函数支持可变参数列表
func div(a, b int) (q, r int) {
    // q = a / b
    // r = a % b
    // return
    return a / b, a % b
}

// 使用_抛弃返回值
a, _ := div(12, 7)

// 使用另一个函数作为参数
func apply(operation func(a, b int), a, b int) {
    operation(a, b)
}
// 使用匿名函数
apply(func(a, b int) {
    fmt.Println(a, b)
}, 1, 2)

// 可变参数列表
func sum(numbers ...int) int {
    s := 0
    for i := range numbers {
        s += numbers[i]
    }
    return s
} 

指针

Golang只有值传递一种方式。

// 使用指针操作
func change(pa *int) {
    *pa++
}
a := 5
change(&a)

func swap(x, y *int) {
    *x, *y = *y, *x
}

数组

数组长度写在类型之前,[10]int和[20]int是不同的类型。数组是值复制。

var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6}
var arr4 [4][5]int

遍历数组

for i := 0; i < len(arr3); i++ {
    fmt.Println(arr3[i])
}

for i := range arr3 {
    fmt.Println(arr3[i])
}

// 按下标和值遍历
for i, v := range arr3 {
    fmt.Println(i, v)
}

切片

数组切片相当于数据的一个视图,可以通过切片改变数组的值。

func updateSlice(s []int) {
    s[0] = 100
}

func main() {
    array := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
    s1 := array[2:6]
    s2 := array[2:]
    s3 := array[:6]
    s4 := array[:]
    fmt.Println(s1, s2, s3, s4) // [2 3 4 5] [2 3 4 5 6 7] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7]

    updateSlice(s2)
    fmt.Println(s2) // [100 3 4 5 6 7]
    updateSlice(s3)
    fmt.Println(s3) // [100 1 100 3 4 5]
    
    // 切片扩展
    s5 := s3[6:8]
    fmt.Println(s5)
    
    // 添加元素
    s6 := append(s5, 8)
    fmt.Println(s6, array)
    fmt.Println(array[:8])
    
    // 通过make创建切片
    // 长度为10的切片
    x := make([]int, 10)
    // 长度为10,容量为16的切片
    y := make([]int, 10, 16)

    // 拷贝数组元素
    copy(x, y)

    // 截取数组元素
    z := append(x[:2], x[3:]...)
}

切片底层维护了一个数组,ptr指向切片的首个元素,len表示切片的长度,cap表示底层数组的从ptr到最末的元素数量。切片可以向后扩展,不可向前扩展。切片添加元素如果会超过底层数组的cap,Golang会分配更大的底层数组(原容量*2进行扩展),由于值传递的关系,必须接收append方法的返回值。

map

通过map[K]v的形式定义map。除了slice、map、function的内建类型和struct类型都可以作为map的key。

m := map[string]string{
        "name": "wch",
        "age":  "23",
    }
    
m2 := make(map[string]int)

// 无序遍历map
for k, v := range m {
    fmt.Println(k, v)
}

// map读值
name := m["name"]


// 判断key是否存在
grade, exist := m["grade"]

// 删除
delete(m, "name")

rune

Golang的rune相当于java的char,中文字符在Golang中占3个字节,使用 utf8.RuneCountInString 获取字符数,用 len 获取字节长度,使用 []byte 获取字节。

s := "学习Golang!"
fmt.Println(s)

// ch是rune类型,中文字符占3个字节
for i, ch := range s {
    fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()

fmt.Println("rune count in string:", utf8.RuneCountInString(s))

bytes := []byte(s)
for len(bytes) > 0 {
    ch, size := utf8.DecodeRune(bytes)
    bytes = bytes[size:]
    fmt.Printf("size= %d, rune=%c\n", size, ch)
}

for i, ch := range []rune(s) {
    fmt.Printf("(%d %c)", i, ch)
}
fmt.Println()

面向对象

Golang仅支持封装,不支持继承和多态,没有class,只有struct,通过struct来定义对象。

type treeNode struct {
    value       int
    left, right *treeNode
}

func createNode(value int) *treeNode {
    return &treeNode{value: value}
}

func main() {
    // 通过多种方式声明对象
    var root treeNode
    root = treeNode{value: 0}
    root.left = &treeNode{}
    root.right = &treeNode{1, nil, nil}
    root.right.left = new(treeNode)
    root.right.right = createNode(3)
}

Golang支持显式定义方法接收者,值/指针接收者均可接收值/指针。当需要改变内容或结构过大时需要使用指针接收者,值接收者是Golang特有的

// 指定方法接收者
func (node treeNode) print() {
    fmt.Println(node.value)
}

func (node *treeNode) setValue(value int) {
    node.value = value
}

func main() {
    node := treeNode{}
    node.setValue(100)
    node.print()
    
    pNode := &node
    pNode.setValue(101)
    pNode.print()
}

封装

Golang的可见性(针对包)使用首字母来控制,首字母大写表示public,首字母小写表示private。每个目录都是一个包,main包包含可执行入口。为结构定义的方法必须放在同一个包内,可以是不同的文件。Golang提供了两种扩展系统类型和已封装类型的方法:

  • 定义别名:通过type为已有类型定义一个别名,针对新的结构体进行扩展。
type Queue []int

func (q *Queue) Push(v int) {
    *q = append(*q, v)
}

func (q *Queue) Pop() int {
    head := (*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool {
    return len(*q) == 0
}
  • 使用组合:定义新的结构体,其中一个成员是需要扩展的类型的指针。
type EnhanceNode struct {
    node *Node
}

func (enhanceNode *EnhanceNode) PostOrder() {
    if nil == enhanceNode || nil == enhanceNode.node {
        return
    }

    left := EnhanceNode{enhanceNode.node.Left}
    right := EnhanceNode{enhanceNode.node.Right}

    left.PostOrder()
    right.PostOrder()
    enhanceNode.PostOrder()
}

GOROOT、GOPATH、go get

GOROOT一般为下载的sdk路径,GOPATH为用户可定义的路径,用户源码和通过go get命令下载的第三方库在GOPATH的src目录下。
gopm是go get的镜像工具,通过 go get -g -v github.com/gpmgo/gopm 下载gopm。

// 下载
gopm get -g -v Golang.org/x/tools/cmd/goimports
// 更新
gopm get -g -v -u Golang.org/x/tools/cmd/goimports
// 安装到GOPATH的bin目录
go install Golang.org/x/tools/cmd/goimports

接口

接口的实现是隐式的,只要实现接口的方法。

duck typing

“像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子”,意为描述事物的外部行为而非内部结构。严格说go属于结构化类型系统,类似duck typing。

接口变量里有什么

  • 接口自带指针
  • 接口变量同样采用值传递,几乎不需要使用接口的指针
  • 指针接收者只能以指针的方式使用,值接收者都可以
  • interface{}可以代表任何类型
  • 定义接口
type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return r.Get("http://www.baidu.com")
}
  • 实现接口
type Retriever struct {
    UserAgent string
    Timeout   time.Duration
}

func (r *Retriever) Get(url string) string {
    resp, err := http.Get(url)
    if nil != err {
        panic(err)
    }

    result, err := httputil.DumpResponse(resp, true)
    resp.Body.Close()

    if nil != err {
        panic(err)
    }

    return string(result)
}
  • 调用接口
var r Retriever
r = &real.Retriever{
    UserAgent: "Mozilla/5.0",
    Timeout:   time.Minute,
}
fmt.Println(download(r))

// Type Assertion
if retriever, ok := r.(*real.Retriever); ok {
    fmt.Printf("%T %v\n", retriever, retriever)
}
  • 接口组合
type HttpExecute interface {
    Retriever
    Poster
}

函数式编程

  • 函数是一等公民:参数,变量,返回值都可以是函数。
  • 高阶函数:函数的参数可以是函数
  • 函数->闭包
func adder() func(int) int {
    sum := 0
    return func(i int) int {
        sum += i
        return sum
    }
}

func main() {
    // f中不仅是一个函数,还有对变量sum的引用
    f := adder()
    for i := 0; i < 10; i++ {
        fmt.Println(f(i))
    }
}

错误处理和资源管理

defer

用于指定在函数结束之前执行,可以用于关闭文件、释放锁等。

func tryDefer() {
    // 在函数借结束之前执行
    defer fmt.Println("execute last...")
    defer fmt.Println("execute before last...")
    fmt.Println("execute")
}

panic

  • 停止当前函数执行
  • 一直向上返回,执行每一层的defer
  • 如果没有遇见recover,程序退出

recover

  • 仅在defer调用中使用
  • 获取panic的值
  • 如果无法处理,重新panic
func tryRecover() {
    defer func() {
        r := recover()
        if err, ok := r.(error); ok {
            fmt.Println("error:", err)
        } else {
            panic(fmt.Sprintf("unknown error: %v", r))
        }
    }()

    panic(errors.New("this is an error"))
}

测试

测试格式

  • 测试文件与待测试代码放在同一目录下
  • 测试文件名以 _test.go 结尾
  • 测试函数名为 TestXxxTest_xxx 的格式,使用其它类型测试,如性能测试则函数名为BenchmarkXxx 或 ```Benchmark_xxx`` 的格式

传统测试

  • 传统测试数据与逻辑混在一起
  • 传统测试的出错信息不明确
  • 传统测试一旦出错测试即全部结束

表格驱动测试

  • 测试代码
func calcTriangle(a, b int) int {
    return int(math.Sqrt(float64(a*a + b*b)))
}
  • 表格驱动测试
    分离了测试数据和测试逻辑,明确了出错信息,可以部分失败。
func TestCalcTriangle(t *testing.T) {
    tests := []struct{ a, b, c int }{
        {3, 4, 5},
        {5, 12, 13},
        {8, 15, 18},
    }

    for _, tt := range tests {
        if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
            t.Errorf("calcTriangle(%d %d); got %d; expected %d", tt.a, tt.b, actual, tt.c)
        }
    }
}

性能测试

执行b.N次,由Golang决定具体次数,测试完成后在控制台打印执行次数和平均执行时间。

func BenchmarkTriangle(b *testing.B) {
    x, y, z := 8, 15, 17
    for i := 0; i < b.N; i++ {
        actual := calcTriangle(x, y)
        if actual != z {
            b.Errorf("calcTriangle(%d %d); got %d; expected %d", x, y, actual, z)
        }
    }
}

测试命令行工具

# 测试命令
go test
# 代码覆盖率测试
go test -cover
# 生成代码测试覆盖率文件
go test -coverprofile c.out
# 代码覆盖率测试html
go tool cover -html=c.out
# 性能测试
go test -bench .
# 生成性能报告
go test -bench . -cpuprofile cpu.out

文档

Golang提供 go doc 命令查看由注释组成的文档,通过 godoc -http :6060 命令在本地6060端口生成html文档。在test文件中建立名为 ExampleXxx_xxx 的函数,通过特定格式生成代码实例文档。

func ExampleQueue_Pop() {
    queue := Queue{}
    queue.Push(1)
    fmt.Println(queue.Pop())

    // Output:
    // 1
}

go routine

协程Coroutine

  • 轻量级“线程”
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟机层面的多任务
  • 多个协程可能在一个或多个线程上运行

goroutine定义

  • 调用任何函数前只需加上 go 关键字就可以交给调度器执行
  • 不需要在定义时区分是否为异步函数
  • 调度器会在合适的点进行切换(I/O、select、channel、等待锁、函数调用等),或手动调用 runtime.Gosched() 进行切换
  • 使用race来检测数据访问冲突, go run -race xxx.go

channel

相对于通过共享内存来通信,channel通过通信来共享内存。

  • chanel可以作为参数传递
  • 可以设置channel的buffer
  • 发送方负责关闭channel,接收方通过可以通过 range 判断channel是否传递完毕

使用select进行调度

select可以用来监听IO操作,可以同时监听多个channel的消息状态。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343