语言教程
计算机软件经历了数十年的发展,形成了多种学术流派,有面向过程编程、面向对象编程、函数式编程、面向消息编程等,这些思想究竟孰优孰劣,众说纷纭。
除了 OOP 外,近年出现了一些小众的编程哲学,Go 语言对这些思想亦有所吸收。例如,Go 语言接受了函数式编程的一些想法,支持匿名函数与闭包。再如,Go语言接受了以 Erlang 语言为代表的面向消息编程思想,支持 goroutine 和通道,并推荐使用消息而不是共享内存来进行并发编程。总体来说,Go 语言是一个非常现代化的语言,精小但非常强大。
Go 语言最主要的特性:
- 自动垃圾回收
- 更丰富的内置类型
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性
语法基础
仅仅列举一些值得注意的语法。
数据类型
使用 fmt.Sprintf
格式化字符串并赋值给新串:
1
| fmt.Sprintf("Code=%d&endDate=%s", 123, "2020-12-31");
|
Go 中按类别大约有以下几种数据类型:
- ”布尔型“、“数字型”、“字符串型”、“派生类型”;
- 其中“派生类型”有以下几种:指针类型、数组类型、struct 类型、channel 类型、函数类型、切片类型、interface 类型、Map 类型;
变量
变量声明有以下几种方式:
1
2
3
4
5
6
7
8
| // 一次声明多个变量
var identifier type
// 根据值自行判定变量类型。
var v_name = value
// 省略 var,使用 := 定义。左侧如果没有声明新的变量,就产生编译错误
v_name := value
|
声明多变量的方式:
1
2
3
4
5
6
7
| var vname1, vname2, vname3 type
var (
vname1 v_type1
vname2 v_type2
)
var vname1, vname2, vname3 = v1, v2, v3
vname1, vname2, vname3 := v1, v2, v3
|
指定变量类型,如果没有初始化,则变量默认为零值:
值类型、引用类型与 C++ 类似。
常量
常量定义关键词是 const
,与变量类似,它的定义格式如下:
1
| const identifier [type] = value
|
在 golang 常量中有一个特殊的语法 iota
,下面是使用实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
|
条件语句
if
条件语句:
1
2
3
4
5
| /* 使用 if 语句判断布尔表达式 */
if a < 20 {
/* 如果条件为 true 则执行以下语句 */
fmt.Printf("a 小于 20\n" )
}
|
switch
条件语句(默认 break
,使用 fallthrough
语句执行下一个 case
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| func main() {
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
}
|
select
语句来自于 Linux 中的 select
这一 IO 模式(现在常用的是 epoll
),它是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select
随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
基本格式:
1
2
3
4
5
6
7
8
9
| select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
|
循环
go
中只使用 for
这一个关键字进行循环,但是却可以执行 C 中所有关键字的功能:
1
2
3
4
5
6
7
8
9
| for init; condition; post { }
for condition { }
for { }
for key, value := range oldMap {
newMap[key] = value
}
|
其中 range
循环可以对字符串、数组、切片等进行迭代输出元素,下面举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| package main
import "fmt"
func main() {
strings := []string{"google", "runoob"}
for i, s := range strings {
fmt.Println(i, s)
}
numbers := [6]int{1, 2, 3, 5}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}
|
在循环语句中可以使用 break
/continue
/goto
进行控制。
函数
Go 语言函数定义格式如下:
1
2
3
| func function_name( [parameter list] ) [return_types] {
// ...函数体
}
|
Go 语言的函数可以返回多个值:
1
2
3
4
5
6
7
8
9
10
11
| package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}
|
Go 语言可以定义引用传值:
1
2
3
4
5
6
| func swap(x *int, y *int) {
var temp int
temp = *x /* 保持 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
|
数据类型
数组
声明数组语法:
1
2
3
| var variable_name [SIZE] variable_type
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type // 多维数组
|
初始化数组的方式:
1
2
3
4
5
6
7
8
9
| var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} // 自动推断数组长度
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}
|
指针
声明指针的语法:
空指针的值为 nil
。
定义指针数组(存储着指针的数组)通过以下的方式:
结构
与 C 语言中类似,声明的语法:
1
2
3
4
5
6
| type struct_variable_type struct {
member definition
member definition
...
member definition
}
|
切片
对数组的抽象,因为数组的长度是固定的,使用范围有限,与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片:
1
2
3
| var identifier []type
var slice1 []type = make([]type, len, capacity) // capacity 是可选参数
|
范围
集合
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。Map
的底层是通过哈希表实现的。
可以使用内建函数 make
也可以使用 map
关键字来定义 Map:
1
2
3
4
5
| /* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
|
接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| /* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
|
并发
go 语言的核心优势在于并发的实现,所以 go 在工程意义上是一个更适合做“分布式系统”的后端语言,而不适合做科学计算。
goroutine
我们可以用 go
关键词来开启一个 goroutine
:
goroutine
是轻量级线程,goroutine
的调度是由 Golang 运行时进行管理的。- 同一个程序中的所有
goroutine
共享同一个地址空间。
语法格式如下:
channel
goroutine
是 go 语言中用于声明新的线程的方式,channel
则是 go 中用于进行线程间通信的方式。
- 关键字
channel
: 可用于两个 goroutine
之间通过传递一个指定类型的值来同步运行和通讯。 - 声明
make
:声明一个变量使用 make
函数。比如 make(chan int)
声明了一个,可以用于传递 int
类型数据的管道。 - 操作符
<-
: 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。 - 遍历通道,使用关键字
range
:像遍历切片一样的 for 循环; - 关闭管道,使用函数
close
:如果有某个 goroutine
正在进行 range
遍历,它只有在管道关闭时才会结束,否则会一直阻塞。
下面是一个声明并且使用的例子:
1
2
3
4
| ch := make(chan int) // 指定第二个参数可以指定缓冲区大小
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据,并把值赋给 v
|
上面的 make
函数如果指定了第二个参数则指定了缓冲区大小。如果通道不带缓冲,在通道中有内容时,发送方会阻塞直到接收方从通道中接收了值。