基础语法
Hello World
package main
import ("fmt")
func main() {
fmt.Println("Hello World")
}
包、变量、函数
包
Golang进行源代码的管理使用package的概念。对于bin项目,main函数有些仅有一个,且属于main包。同一个目录下的同级的所有go文件应该属于一个包。
通过import导入包,import路径的最后一个部分会成为包名。
Golang的设计贯彻 大道至简 的原则😆,使用首字母的大小写分别表示包成员的公共和私有。
变量
使用var或:=声明变量,使用const声明常量。var和const类型后置,格式为var|const NAME TYPE [= INIT]。:=可以自动推导类型,但只能在函数内部使用。
使用var和const一次声明多个量时,可以使用(),例如:
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或无限循环
在循环中使用break和continue分别跳出循环和跳到下次迭代。
switch
switch EXPRESSION {
case CASE1:
// ...
case CASE2, CASE3:
// ...
default:
// ...
}
条件EXPRESSION可以为变量或表达式,也可以为空,为空时,为true的case将被执行。
使用,分隔一个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错误时,可以:
-
向上传播
调用方直接返回这个
err或者返回添加上自己的上下文描述的err,例如:if err != nil { return err } // or... if err != nil { return fmt.Errorf("do sth failed: %v", err) } -
处理错误
进行重试、记录日志或忽略,中断错误传播,终止程序或恢复正常执行逻辑。
相对而言,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是一门非常独特的语言,值得关注和学习。