本文并不是一篇面向新手的入门文章,而是面向于有一定编程经验而且希望快速上手 Go 语言的人
阅读本文章最好了解 C、C++、Java 等语言之一的语法
本文中难免会有错误,接受一切纠错以及志同道合的人来交流,可以在网页最下方找到联系方式邮件联系我
变量
变量需要显式的声明,完整的语法如下。
1
var a int = 8
在提供初始化的值的时候,类型可以省略,不提供时则需要显式的写出类型。利用 :=
可以省略 var
。const
替代 var
以表示常量。
1
2
3
4
5
6
var a = 8 // 省略类型
var a int // 无初始值
a := 8 // 简略写法,与第一行语义相同
const a = 8 // 常量
a, b := 1,2 // 多变量声明
_ := 8 // 可用 _ 表示一个匿名变量
Go 语言中不存在未初始化的说法,声明时即使未指定初始值,也会用默认值进行初始化。
数据类型
基础类型
Go 是一门强类型的静态语言,但是提供了自动的类型推导,内建的基础类型如下。
1
2
3
4
5
6
7
8
int // 有符号整数,32位为 int32, 64位为 int64
int8 int16 int32 int64
uint // 无符号整数
uint8 uint16 uint32 uint64 uintptr
float32 float64 // 浮点数
byte // 字节,与 uint8 相同
bool // 布尔类型,true/false
string
内置类型
Array
Array 指具有指定固定长度的序列对象,即数组,类型为 [N]type
,表明是长度为 N
元素类型为 type
的数组。
1
2
3
4
5
var arr [10]int // 长度为 10 类型为 int 的数组
arr[0] = 10 // 数组元素赋值,下标从 0 开始
arr := [...]int{0, 1} // 初始化,长度可省略交由编译器计算
Slice
Slice 是没有固定长度的序列对象,称为切片,类型为 []type
,表明是元素类型为 type
的切片。
1
2
3
4
5
6
7
8
9
// 初始化
slice := make([]int, length, capacity) // make 为内置函数, capacity可省略
slice := []int{}
// 切片,语法与 Python 一致
slice[3:10]
slice[:8]
slice = append(slice, 4) //添加元素,append 为内置函数,返回新的切片对象
Map
Map 为映射类型,类型为 map[type1]type2
,表明是从 type1
映射到 type2
的 map
。
1
2
3
4
5
6
7
8
9
10
m := make(map[string]int) // 初始化
m := map[string]int{
"k1": 1,
"k2": 2,
}
m["key"] = 0 // 赋值
m["new_key"] // 如果是个不存在的 key, 那么会返回默认值
ele, exist := m["key"] // 可以用这种方式检查是否存在,不存在 ele 就会是默认值
结构体
Go 中不存在类,只有结构体。
1
2
3
4
5
6
7
8
9
10
// 结构体声明,内部元素大写则外部可见,小写则不可见
type Complex struct{
Real, Imag float32
}
complex = Complex{ // 这种方式未赋值的元素会以默认值初始化
Real: 3, Imag: 4,
}
complex = Complex{3, 4} // 这种赋值必须为所有元素赋值
complex.Real = 10 // 更新元素
Go 中没有继承的概念,通过组合的方式实现嵌套复用等目标。为结构体编写方法见 函数 一章。
接口
接口指只写明需要的方法,如下
1
2
3
4
5
6
7
8
9
// 声明接口
type Number interface{
add() int
}
// 使用接口
func add(n1 Number, n2 Number) int{
return n1.add(n2)
}
如果接口为空,那么可以匹配任意的结构体。
运算符
Go 中的运算符如下。
1
2
3
4
5
6
7
8
9
10
11
12
// 算术运算符
= - * / % ++ --
// 关系运算符
== != > < >= <=
// 逻辑运算符
&& || !
// 位运算符
& | ^ >> <<
// 赋值运算符
+= -= *= //等等
// 指针相关运算符
& *
上述列出的所有运算符与 C 语言的运算符含义一致。值得注意的是自增自减操作符 ++
与 --
不再像 C 语言中能够在语句中使用,只能单独使用,避免了歧义。
控制流
if
判断即 if
语法如下
1
2
3
4
5
if a % 2 == 0 {
return true
}else{
return false
}
除了 if 后不再需要括号之外,与 C 语言一致。此外,if
还可以在判断前加一句定义,如下
1
2
3
if a:=10; a % 2==0{
return true
}
switch
switch
语法与 C 语言一致,不过每一个 case
默认 break,也就是说不会连续运行多个 case
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 同样可以先定义变量
switch a := getVal(); a {
case 1:
return a*2
// 多条件匹配
case 2, 4:
return a*3
default:
return a*5
}
// 还可以进行判断
switch{
case a>10:
return a / 2
default:
return a / 3
}
循环
Go 中将 C 语言的三种循环简化了一种,均使用 for
关键词。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 完整形式
for i:=0; i<10; i++ {
// ...
}
// 三个部分可以省略
for ; i<10; {
// ...
}
// 上述情况可以省略分号,成为 while 形式
for i<10 {
// ...
}
// 三个部分都省略后就成了 while(true)
for {
}
// 使用 range 简化遍历数组、切片等
for i, ele := range arr {
// i 为下标, ele 为数组内元素 arr[i]
// _, ele := range arr 这样用 _ 就能忽略其中一个
}
函数
函数的形式如下。
1
2
3
4
5
6
7
8
9
// 定义
func fn_name(val type, val2 type2) (ret_type, ret_type2){
// 函数体...
// 支持多返回值
return 0, "return"
}
// 调用
fn_name(v1, v2)
可以为结构体编写函数,类似于成员函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过在该位置指明元素类型即可
func (c Complex) add(c2 Complex) Complex{
return ...
}
// 也可以用指针,用来区分不同的传送参数的方式
func (c *Complex) add(c2 Complex) Complex{
return ...
}
// 以上两种方法,前者会传递一个拷贝,不影响原值但是拷贝成本通常较高,
// 后者传递一个指针,可以直接操作原值,而且仅传输一个指针成本很低
// 调用,不区分函数接收的是不是指针
c.add(c2)
Go 还提供了函数类型,即闭包。闭包可以直接获得外部的变量,但是会成为一个新的变量,也就是说闭包内对外部变量的修改仅在闭包内有效,并不会真正影响到外部的变量。
1
2
3
4
5
6
7
f := func(v int) int{
return v+10
}
func test(f func() int){
return f()+10
}
defer
defer
关键词的含义是将该句话推迟到函数返回时执行,多个 defer
按照后进先出的顺序执行。
1
2
3
4
5
6
func test(){
mutex.Lock()
defer mutex.Unlock()
// 这里仍在保护区内
return // 这句话之前才会解锁
}
利用该语句实现了类似于 RAII 的效果,避免了因为函数体过长,文件忘记释放、互斥量忘记解锁这种情况的发生。
指针
Go 依旧保留了指针这一概念,整体与 C 语言一致,但是少了很多比较 tricky 的用法。主要是 &
与 *
两个操作符,分别对应了取地址和取地址上值的功能。
1
2
3
4
5
6
c := Complex{3,4}
ptr = &c // 指向 c 的指针
c. Real // 3
(*ptr).Real // 3, * 用来获得指针指向的内容
ptr.Real // 3, 这里依旧不区分是否为指针
包管理
通过 import
关键词可以导入其他的包,例如
1
2
3
4
5
import (
"fmt"
"math/rand"
alias "another/rand" // 冲突可以用这种方法换个名字
)
而每个源文件也必须用 package
指明自己被导入时的标识符。
1
package main
导入之后就可以用该包名使用内部的函数了。
1
2
import "fmt"
fmt.Println("yeah!")
协程
协程 (Goroutine) 是 Go 语言的一个关键特性,可以以极为方便的方式运行一个新的协程,这里协程的表现类似于线程。
创建
1
2
3
4
// 正常调用一个函数,会等待运行结束
do_something()
// 用协程运行该函数,表现相当于新建一个线程运行该函数,不等待结果
go do_something()
同步
这里采用一个 Channel 的对象在不同协程间传递消息。
1
2
3
4
5
6
7
ch := make(chan int) // 消息类型为 int 的 Channel
ch <- 10 // 向 channel 中发送值 10,如果没读取就会阻塞
<- ch // 从 channel 中读取值,没有值就会阻塞
v, ok := <- ch // 同时检查 channel 是否关闭
close(ch) // 关闭 channel
func f(ch chan<- int) // 只可以输入的 channel
ch := make(chan int,10) // 带缓冲的channel