Golang基础语法和常用特性解析 | 青训营

135 阅读6分钟

基础语法

Hello World

package main

import ("fmt")

func main() {
    fmt.Println("Hello World")
}

包、变量、函数

Golang进行源代码的管理使用package的概念。对于bin项目,main函数有些仅有一个,且属于main包。同一个目录下的同级的所有go文件应该属于一个包。

通过import导入包,import路径的最后一个部分会成为包名。

Golang的设计贯彻 大道至简 的原则😆,使用首字母的大小写分别表示包成员的公共和私有。

变量

使用var:=声明变量,使用const声明常量。varconst类型后置,格式为var|const NAME TYPE [= INIT]:=可以自动推导类型,但只能在函数内部使用。

使用varconst一次声明多个量时,可以使用(),例如:

var (
    lastName string = "Li"
    firstName string = "Hua"
)

使用:==进行声明和赋值时,可以使用,一次性赋多个值:var1, var2, var3 := 1, "A", true

不同于大多语言,Golang编译器对声明后未使用的变量抛出Error而非Warning。

函数

func NAME(PARAM1 TYPE1, PARAM2 TYPE2, ...) (RES2 RES_TYPE2,RES2 RES_TYPE2,... ) { 
    // ...
}

可选地,为返回值设置名字RES后,可以在函数中将值赋值给RES设置返回值。这样在返回时,直接return即可。

控制流

if-else

if CONDITION {
} else {
}

需要注意的是else必须紧随}之后。

不支持?:if else三目运算,所谓 less is more 😆。

惯用写法是在if条件中声明变量,变量在整个if-else块中可见。例如:

if err := doSth(); err != nil {
    return err
}

for

for INIT; CONDITION; STEP {
}

允许空的预处理INIT;和后处理语句STEP;,此时表现为其他语言的while循环,如果条件语句CONDITION也为空,则表现为loop或无限循环

在循环中使用breakcontinue分别跳出循环和跳到下次迭代。

switch

switch EXPRESSION {
case CASE1:
// ...
case CASE2, CASE3:
// ...
default:
// ...
}

条件EXPRESSION可以为变量或表达式,也可以为空,为空时,为truecase将被执行。

使用,分隔一个case语句的多个case。case也可以是表达式。

不同于其他语言默认的fallthrough,golang默认在一个case语句执行后break,需要fallthrough时,需加上指令fallthrough。例如:

switch num { 
case num < 50: 
    fmt.Printf("%d 小于 50\n", num)
    fallthrough
case num > 100:
    fmt.Printf("%d 大于 100\n", num) 
}

结构、数组、切片

数组

var arr1 [3]int
arr2 := [3]int{1,2,3}

切片

var slice1 []int

从数组切片

slice1 := arr[2:5]

数组长度在创建后不可变,而切片在容量不足时,会自动扩充。

结构

type STRUCT_NAME struct {
    FIELD_NAME1 TYPE1
    FIELD_NAME2 TYPE2
}

可以使用组合的方式将某结构嵌入其他结构,例如:

type Person struct {
    Name string
}
type Student struct {
    Person
    ID int
}

可以直接通过.Name访问Student实例上的组合自Person的字段Name

由于Golang使用首字母大小写区分可见性,一般将字段名首字母设为大写,但使用JSON序列化和反序列化时常用小写。可以为该字段打上tag:

type Person struct {
    Name string `json:"name"`
}

常用特性

错误处理

Golang需要手动显示地进行错误处理。通常,将可能遇到错误的函数的最后一个返回值设置为error类型进行错误传播。

res, err := doSth()
if err != nil {
    // 错误处理
}

函数的调用方遇到返回非nil错误时,可以:

  1. 向上传播

    调用方直接返回这个err或者返回添加上自己的上下文描述的err,例如:

    if err != nil {
        return err
    }
    // or...
    if err != nil {
        return fmt.Errorf("do sth failed: %v", err)
    }
    
  2. 处理错误

    进行重试、记录日志或忽略,中断错误传播,终止程序或恢复正常执行逻辑。

相对而言,Golang缺少对错误的默认的隐性传播,一方面确保了潜在的错误都能得到处理,但一方面增加了代码的冗余。less is more

可以将error返回结果赋给_忽略掉错误。

goroutine、channel

Golang提供了一套较为方便的并发特性:协程goroutine和通道channel。

goroutine

Goroutine是Go语言中的轻量级执行线程,是一个独立的执行单元。相比于传统的线程,Goroutine更加轻量,因此可以创建很多个线程而不引起明显的性能损耗。Goroutine由Go运行时进行调度,实现不同Goroutine间的切换执行,实现并发。

启动一个Goroutine只需要在调用前加上go即可:

func main() {
    go doSthInGoroutine()
}

channel

新兴的编程语言往往抛弃了传统的共享内存方式,而采用消息的方式实现线程/协程间通信。避免了共享数据时发生冲突的问题。Golang使用channel机制,实现在不同的Goroutine之间安全地传递数据。

Channel分为有缓冲channel和无缓冲channel。无缓冲channel会阻塞发送方和接收方,只有有对应的接收方和发送方时,才能发送并继续执行,适用于同步场景。

// 创建一个通信内容为`string`的有5个缓冲区的channel。
ch := make(chan string, 5)
// 创建一个通信内容为`int`的无缓冲channel。
ch := make(chan int)

Channel支持两种基本操作:发送数据和接收数据。发送数据和接收数据都使用<-操作符,只不过位置不同。

ch := make(chan string, 5)
// routine 1: 发送数据"some message"
ch <- "some message"
// routine 2: 接收数据,赋给data
data := <-ch

使用close函数关闭channel。

defer关键字

defer是Go语言的关键字之一,用于延迟执行函数。使用defer关键字标记的语句会在defer所在函数执行行结束之后,返回之前被执行。无论函数是通过正常返回还是发生了异常,defer语句都会执行。defer语句通常用于在函数结束前执行一些清理工作,例如关闭文件、释放资源等。

func doSth() {
    defer cleanup() // 延迟执行cleanup函数
    // 其他操作...
}

延迟的函数遵循LIFO,最先被延迟的语句最后会被执行。

总结

Golang的语法给人留下深刻印象,其中一些语法可以称为独具特色。首先,Golang的语法相对简洁,摒弃了其他语言中繁琐的符号和特性,从而降低了入门门槛,使学习曲线相对平缓。另外,通过协程模式,Golang提供了高效处理高并发编程的能力,使其在后端服务开发方面表现出色。但另一方面,Golang在一些重要的语言特性上存在一定的缺陷,如泛型等。相较于某些灵活的语言,Golang的语法限制较为严格,甚至涉及代码风格的规范,如括号位置和大小写等。综上所述,Golang是一门非常独特的语言,值得关注和学习。