Golang | 一日冲刺

2019 12 6, Fri

今年开始我一点点的开始在生产里面用go语言,但是跟别人聊了以后发现自己还是太过于naive了。所以计划按照费曼学习法把自己学习的过程记录一下。

有人说过其实世界上没有语言,只有特性的集合。go语言基础我打算用一篇博客大概的写一下有什么东西有什么特点,用来给其他博客做铺垫。

有其他语言基础的大神可以快速过一下语法。小白还是乖乖去看书比较好。

基础语法

package

模块划分

go语言主要是由一个个包构成,每个go语言的源文件都以package开始

package xxx

// ......

所有代码按照包来划分功能,一个目录里面的所有代码都属于同一个包。

概念上用法上都类似于c++的namespace或者是Java的package。

go语言入口

和Java类似,go的入口是main函数,但是不是每个包的main函数都可以作为入口,只有main包的main函数(main.main)才是入口。

类似于c runtime,main函数也是被调用的。go的入口其实在runtime包,其中一个goroutine中调用了main.main()

go语言的main函数不带参数,没有返回值。

import

当我们需要用到别人的包的时候,就需要import

pakage main

import "fmt"

如果我们要import多个包,那么我们既可以写多个import,也可以简单一些,用圆括号

package main

import (
   "fmt"
   "strconv"
)

// ......

函数

定义和调用

go语言里面的函数定义是func <name>(<arglist...>) { <body> },调用的话[<package>.]<name>(<arglist...>)。需要注意,go语言中类型在形参的后面,行末没有分号。

比如说

package main

func Greeting(name string) {
    fmt.Printf("Hello, %v", name)
}

func main() {
   Greeting("guochao")
}

// Hello, guochao

返回值

go语言支持多返回值,在接受返回值时,变量数量需要和函数返回值数量一致。

pacakge main

func OneRval() int {
    return 1
}

func WithError() (int, error) {
    return 1, nil
}

func main() {
    r1 := OneRval()
    r2, err := WithError()
    
    // ...
}

不定参数

go语言允许最后一个参数数量不定,形参写法是

package main

func VAArgsFunc(arg...string) {
    // ......
}

类型和变量

go的类型比其他语言略微严格和复杂一些。

大概来分也是primitive types和struct/interface

基础类型

go语言里面不带修饰的类型只有bool byte rune int和float,以及ptr类和channel。

bool跟c++差不多。

对于int和float来说,如果对长度有要求,在后面加上长度,比如说int8 int16 int32 int64 float32 float64。int默认都是有符号的,如果需要无符号,在前面加上u,比如说uint8 uint16。

rune本意符文,玩游戏的大概都知道。在这里主要是指代Unicode中的一个字符。而相对的byte就是单纯的一个字节。后面再具体说。

变量/常量

go语言中类似于func,变量定义可以通过var来定义。相对的常量就是const。语法如下

package main

var aBoolean bool
var initedBoolean = false
var anotherBoolean bool = false

var (
    tick = 0
)

const PI = 3.1415926535

:= 和 =

用=赋值一般都没啥问题。赋值的时候类型不一致需要自己进行显式类型转换。

go除了=以外还有一个:=,其实不是赋值,而是在函数中定义本地变量并初始化的另一种写法。 比如说

package main

func main() {
    var (
        tick = 1
    )
    // literately equal to
    tick := 1
}

需要明确的限制就是,只能在函数中定义并初始化在这个scope中,没有定义过的变量。

type和struct

type类似于c语言的typedef,给类型定义别名的。在go里面定义别名除了好看以外,还有一个用处是作为函数的receiver。这个之后再说。

定义的方法类似与这样

package main

type MyString string

type Item struct {
    ID   int
    name []byte
}

receiver

go语言没有类,但是自己定义的类型可以有receiver,可以让语义更清楚一些,同时也可以用来满足interface的约束

package main

import (
    "fmt"
)

type MyString string

func (s MyString) Length() int {
    return len(s)
}

func main() {
    fmt.Print(MyString("test").Length())
}

// 4

interface

这是一个go语言受争议的特性之一。

go语言里面没有类的概念,取而代之的是interface。更类似于typescript约束参数类型时定义的object interface一些,不过对go来说定义一个接口并且满足它就可以了,不需要明确说我满足了什么interface。更类似于是一种约束,可以在定义函数参数的时候约束传入的实参。

如果需要确认一个类型满足了interface的约束,可以用一个小技巧

大概的语法和struct类似,只不过成员不是变量,而是函数。

pacakge io

type Writer interface{
    Write([]byte) (int, error)
}

// 然后实现这个接口

type SringBuilder struct {
    // ...
}
func (w *SringBuilder) Write(data []byte) (int, error) {
    // ...
}

// 保证这个类型实现了这个接口
var (
    _ Writer = &SringBuilder{}
)

匿名函数和函数类型的变量

在go语言中,函数也是一种类型。可以用来赋值或者传参。类似于传递了函数指针

内置函数

go语言像很多其他语言一样,除了标准库,还有一些常用的builtin函数。比如说len、make等等。

选择循环

if

go语言的选择循环都不带括号,比如说if的语法是

if 条件 {
    语句
}
pacakge main

func Greeting(name string) {
    if len(name) == 0 {
        name = "world"
    }
    fmt.Print("Hello, ", name)
}

func main() {
    Greeting("") // Hello, world
    Greeting("Chao") // Hello, Chao
}

switch

和其他语言类似的用法

用一个表达式可能的值来进入不同的分支

switch <expr> {
    case <possible value>:
        ...
}

不大一样的地方是

  • 不需要break,会自动在下一个case停止
  • 如果需要继续执行下一个case,需要fallthrough。
  • 如果有多个值进入完全一样的分支,可以在case后面用逗号分隔罗列出所有可能的值

比如说

package main

import (
    "os"
)

func main() {
    switch len(os.Argv) {
         case 1:
            //......
            fallthrough
         case 2:
            // ....
         case 3, 4:
            //......
    }
}

用作多个if/else if的简化形式

switch {
    case <condition>:
        // ......
}

在类型不明时按照类型执行不同的分支

go里面变量可以暂时只标注是某种interface类型,比如说参数接收一个interface的时候,在这个scope内也只能用这个interface的接口。

用goland等IDE看的时候可以看到会有标注。比如说error | github.com/go-errors/errors.Error,实际上这个变量是errors包的Error类型,但是在这个scope内只是个error

这种情况为了确认具体的类型可以用type switch:

variable := <expr>
switch variable.(type) {
    case <type>:
        v := variable.<type>
        // ......
}

或者等价形式

switch v := <expr>.(type) {
     case <type>:
          //......
}

如果只是区分类型就不需要给v赋值的部分

for

go里面循环只有一个关键字,但是有多个形式

正常的

类似于c++的普通的for

for <initialization>; <condition>; <statement> {
    // ......
}

range for

for v := range <expr> {
    // ....
}

代替while

for <condition> {
    // .....
}