PS:论坛照搬的第一篇文章,原计划是写个系列

25个关键字快速入门Go

本文适用于有一定其他语言编程基础的读者阅读

快速入门

在你对编程语言方面的概念如函数、对象、变量、常量等有了一些基本概念了解之后,快速学习一门新的语言的语法是相对比较简单的,在此之外需要关注的地方包括语言的特性、语言的工具链(包管理、编译工具链等),动手实践起来既能较好的掌握这门语言。作为Gopher以及一名撸站工,觉得有必要向大家推广这门语言,让其在安全领域(当然目前已经应用广泛了)拥有更多开发者。

Go的关键字可以在 https://golang.org/ref/spec#Keywords 找到,主要有

1
2
3
4
5
break        default      func         interface    select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

以及一些和其他语言一样常见的操作符,对于操作符本篇只记录较为常用的一些,我们对如上关键字像其他编程语言的教程一样,按使用范围对其进行一个分类:

包的引用

每个编程语言都有类似 include 或者 import 的关键字,用于导入需要引用的包或者库。Go语言也使用了import关键字来进行包的导入,在使用过程中需要注意的地方就是如果遇到重名的包可以通过 alias_name import package的方式来避免冲突,每个包都通过package关键字来标识,这有点类似Java,当导入一个包时,如果包中实现了init()函数则会被执行,你可以类比其为Java的构造函数,此时如果使用 _ import package来进行包的导入,则只会执行init()函数,而包中的其他导出函数则不会被引用。

变量与常量

在Go中是支持类型推倒的,当你需要定义一个变量时候你可以使用如下语句:

1
var_name := "pentest"

或者使用var关键字来定义其类型,当有多个变量时可以这样写:

1
2
3
4
5
6
var var_name type
var
(
var_name_1 string
var_name_2 int
)

谈到变量的类型,Go语言与其他语言的关键字相差不多,需要知道的是有几个比较独特的内建类型,如byte以及runebyte实际上是uint8的别名,也就是一个无符号整形,rune实际上是int32的别名,在Go中通常表示一个Unicode字符。

常量的关键字和其他语言一样使用const,常见的写法为const var_name type = value,这里的变量类型type可以省略。

数据结构

Go中常见的数据结构有数据(Array)、切片(Slice)、键值对(Map)、信道(Channel)等等,下面挨个举例。

对于Array和其他语言一样,我们可以如下定义后,通过下标访问,其中...表示省略了长度的数组,也可以替换为固定长度:

1
a_array :=[...] int{ 1,2,3,4,5 }

对于Slice,熟悉Python的读者应该能够快速理解,这几乎和Python中切片的用法相差无几:

1
a_slice := []int { 1,2,3 }

其中[]表示切片的类型,要访问切片中的元素可以通过a_slice[start:end]这样的形式,与Python不太相同的是Go中的切片没有步长。在Go中关于切片还有两个重要概念分别是lencap,其中Length标示切片的长度,Capacity标示切片的容量,如何理解长度与容量呢,切片本质一段变长的动态数组,长度指的是在这块动态数组中一段片段的长度,容量是指的这段动态数组中片段的最大长度,如果使用append关键字来合并两个切片时就有可能发生cap的增长默认是扩容到原来cap的两倍,以上切片我们还可以使用make函数来进行初始化:

1
a_slice := make([]int,len,cap)

对于Map我们也可以通过make函数进行初始化,如下:

1
a_map := make(map[string]int)

以上所有的数据结构我们均可以通过var关键字来声明一个nil值的相应变量,比如:

1
2
3
var a_array []int
var a_slice []int
var a_map map[string]int

chan是一个比较特殊的数据类型,将在后面讲解。

指针

是你没听错,Go是有指针的,所以在关键字之外,这里必须得讲一下!并且如果你深入学习还能学会使用unsafe包,尽管Go是一门现代语言,并且拥有GC。和C语言一样,最常见的两个操作符*&,声明一个指针很简单:

1
var int_pointer *int

是不是看起来和C一毛一样?访问一个指针志向的变量地址也是一样:

1
2
something := 1
fmt.Printf("var_addr: %x\n", &something)

条件控制

和其他语言一样,Go在条件控制中也使用了ifelse等关键字,一个典型的条件判断例子如下:

1
2
3
4
5
6
7
if ture  {
...
} else if false {
...
} else {
...
}

同时Go也实现了switchcasedefault这些经典的关键字,一个多重条件判断例子如下:

1
2
3
4
5
6
7
8
9
10
switch something {
case 0:
...
case 1:
...
case 2:
...
default:
...
}

需要明确的是,Go中并需要声明break关键字,如something的值匹配了1,则隐式包含了break在其中,case 2:下的语句块则不会被执行。需要注意的是,Go中在实现多重条件判断时,引入了一个比较特别的关键字fallthrough,其主要的作用是在紧跟fallthrough后的一个CASE表达式中,不去判断其条件,而是直接执行CASE代码块中的语句,这个关键字着实用得也比较少。

最后Go也实现了goto关键字,既无条件跳转,通常我们的编程老师都会说不要使用这个关键字,很大程度上会破坏代码的美感和可阅读性。

循环控制

Go中没有while关键字,要实现常见的while true操作则使用for {},一个典型的循环遍历如下:

1
2
3
for x := 0; x < 10; x++ { 
... do something with x ...
}

而对于数据、切片等数据结构,则可以使用range关键字来遍历其元素:

1
2
3
4
5
a_array := []int{1,2,3,4,5}

for key, value := range a_array {
... do something with key or value ...
}

和其他语言一样,跳出本次循环使用continue,结束循环使用break

函数

Go的函数关键字比较简略,直接使用func,下面结合指针以一个C语言中最经典的例子举例函数所会用到的关键字:

1
2
3
4
5
6
func swap(a *int, b *int) {
var tmp int
tmp = *a
*a = *b
*b = tmp
}

为了说明返回值再举一个例子:

1
2
3
4
func function_name(a int,b int) (int) {
sum := a + b
return
}

Go的函数是可以多值返回的,我们在实际编程里经常会返回error类型来判断函数执行过程中是否返回了错误。在Go函数当中常用的还有另外一个关键字defer,这也是与其他语言有所差异的地方,如defer close()一般是用于在返回前延迟执行某个函数,这也是在错误处理中,我们要恢复或清理相关现场时经常用到的。

对象与接口

Go拥有很轻量级的OOP,常见的关于类的操作几乎都能实现。如果你和我一样经常使用C就会知道,在没有C++以前,我们通过结构体,也就是type(C中是typedef)和struct的关键字来体现面向对象的思想了。

我们在Go中可以如下定义一个结构体:

1
2
3
4
type Pentester struct {
web_hack_level int
reverse_engineering_level int
}

定义完成后可以初始化他,比如一个超级黑客的能力肯定是很牛逼的:

1
pentester_struct := Pentester { web_hack_level: 9999, reverse_engineering_level: 9999 }

那么如何体现OOP呢?我们分别从封装、继承、多态这三个面向对象的常见概念出发。

对于封装性,这里必须表扬一下Go把语言的简洁性体现得淋漓尽致,对于一个对象对外的可见性,很多语言如Java等都使用public之类的关键字,而在Go中是通过对象的成员函数或者变量的首字母大写来标识的,比如拿前面的结构体做例子:

1
2
3
4
5
6
7
8
9
10
11
type Pentester struct {
web_hack_level int
reverse_engineering_level int
}

func (p *Pentester) GetWebHackLevel() int {
return p.web_hack_level
}
func (p *Pentester) GetRELevel() int {
return p.reverse_engineering_level
}

在Pentester结构体中我们的成员变量都是使用的小写字母开头,故没办法通过实例化后的pentester_strucet.web_hack_level调用,只能使用如GetRELevel以大写字母开头的成员函数来取值。这样就体现了面向对象中的封装性,既封装提供了数据的安全性, 将变化保持在自己的实现中,对于外界来说不用关去心内部的处理过程, 只需要拿到对用方法调用即可。

对于继承性,Go通过匿名属性来实现,还是以上面的结构体为基准,举一个例子如下:

1
2
3
4
5
6
7
8
9
type SuperHacker struct {
Pentester
bluff_level int
}

func (s *SuperHacker) GetRELevel() int {
s.bluff_level = 999999
return s.bluff_level
}

以上例子中你要成为一名超级黑客,你装逼的本事就要牛逼,所以当别人问你逆向牛逼不牛逼的时候,你可以通过你吹牛逼等级重载掉作为一名撸站工时方法,不知道这样说大家有没有理解 ;)

对于多态性,其实就是统一事物的不同形态,在很多语言中的体现就是接口,Go也使用了与大多数语言相同的关键字interface。还是拿以上的例子说明,比如不管是初级的Pentester还是超级黑客SuperHacker他们都要会撸站,他们的统一形态都是打工仔Worker,用代码来说明就是如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
package main
import "fmt"

type Worker interface {
luzhan()
}

type Pentester struct {
attack_method string
}

type SuperHacker struct {
attack_method string
}

func (p Pentester) luzhan() {
fmt.Println("菜鸡", p.attack_method, "撸站")
}

func (s SuperHacker) luzhan() {
fmt.Println("超级黑客", s.attack_method, "撸站")
}


func main() {
var worker Worker

// 你是初级菜鸡时撸站
pentester := Pentester{"只会用工具"}
worker = pentester
worker.luzhan()

// 你是超级黑客时撸站
fakepro := SuperHacker{"脑波"}
worker = fakepro
worker.luzhan()
}

所以当他是超级黑客的时候,尽管大家都是通过接口实现了撸站,但是他可以用脑波去攻击目标,快速获取权限。

并发

Go语言最迷人的地方,莫过于超级高效且简明的并发处理,如果你接触过网络编程,你会发现在不同的操作系统下可能有不能网络通信模型来处理并发,这其中还涉及到诸如同步异步、阻塞非阻塞等复杂问题。Go的并发使用了比线程更轻量级的goroutine来实现,而需要把你的函数变成并发的,你只需要在函数前加一个go关键字即可就是这么简单,如下:

1
go func fucking_cool_goroutine_func() { }

甚至,Go也支持使用匿名函数(闭包)来快速插入go

1
2
3
go func(x int) {
x++
}

初学者使用闭包的时候经常会碰到逃逸问题和变量作用域问题,不是特别推荐。

谈到并发的同时不能不说数据的同步问题,在Go中使用了一个特殊的数据类型chan来进行并发中的同步处理,这其中还包括一个看起来比较新奇的操作符<-,下面逐个例子来感受一下Channel。

Channel的定义:

1
ch := make(chan int)

Channel发送与接收:

1
2
ch <- v    // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v

如果有多个消息,需要使用select来响应Channel的发送或者接收操作:

1
2
3
4
5
6
7
8
for {
select {
case c <- ch:
....
case <-quit:
return
}
}

这里笼统的说道Channel感受不会太深,后面需要结合,实际的例子来理解。

至此,以Go的25个关键字为索引,基本上已经能够了解了Go的常见语法了,后续在实践中可能还需要了解错误处理(关键字panicrecover)、IO操作(io.Xxxx、重载readerwriter)等,才能用起来感觉很爽。

开发环境

在最近发布的Go版本中,已经没有烦人的GOPATH环境变量问题了,同时也加入了诸如Go Mod或者Go Vendor等特性,默认安装完Go,会在默认的用户目录的下生成 go/srcgo/bin、以及go/pkg

当你打开 https://golang.google.cn/ 就可以看到下载链接,直接安装了,如果你使用MacOS并且安装了Homebrew作为包管理的话,一句brew install go就解决了所有问题。

如果你仅仅只想体验一番,打开浏览器直奔 https://play.golang.org/ 即可。

对于IDE,伟大的Jetbrains公司已经提供了Goland,如果你想使用Vscode来进行折腾也没有关系,可以参考 https://code.visualstudio.com/docs/languages/go 快速配置。

学习资源

你可以从一个本地 go-tour https://github.com/Go-zh/tour 开始体验,按照例子一步一步玩耍下来应该就能慢慢熟悉Go语言了。

想要快速熟悉语法同样可以通过 Learn X in Y minute 项目 https://learnxinyminutes.com/docs/zh-cn/go-cn/

针对Go的每一个的常用用法等也可以学习 Go By Example 项目 https://gobyexample.com/

其他一些与安全相关的Go资源:

行文仓促,学习语言做的笔记如有不足之处还请读者指出,十昏感谢 :)