前言

记录一些Go语言入门学习的基础知识

安装(Mac)

1
2
brew install go // 安装
brew upgrade go // 升级

Hello World

如在hello_world.go中添加如下代码

1
2
3
4
5
6
7
8
// 要生成 Go 可执行程序,必须建立一个名为 main 的 package
package main
// 引入需要的包,这里引入fmt,应该是和格式化相关的包,main函数中使用到了输出方法
import "fmt"
// 在该 package 中必须包含一个名为 main() 的函数
func main() {
fmt.Println("Hello, World!")
}

在终端执行go run hello_world.go ,则会输出Hello, World!

执行流程,使用内置包(GoRoot)

去GoRoot中的src目录下去查找内置的包和函数,此处为fmt,然后就可以调用其中的方法了。其他的内置函数可以在GoRoot中src目录下进行查看,包含了crypto、errors、math、net等很多。

补充

GoRoot是go的安装目录,GoPath是Go的工作目录,可以通过命令go env查看go的环境信息,我自己的如下:

1
2
3
4
...
GOPATH="/Users/smiacter/go"
GOROOT="/usr/local/Cellar/go/1.14.1/libexec"
...

使用github三方开源库(GoPath)

如果需要使用github三方库,则需要将包下载并安装到GoPath,获取三方开源库命令如下(go get xxx):

1
go get github.com/gomodule/redigo/redis    // 获取redis包

上述命令会将包下载到GoPath下的src中,生成目录结构src/github.com/gomodule/redigo

补充

GoPath约定了三个目录,其作用分别如下:

src:存放源代码。按照 Go 语言约定,go run,go install 等命令默认会在此路径下执行

pkg:存放编译时生成的中间文件( * .a )

bin: 存放编译后生成的可执行文件

引用刚刚下载的三方库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
// 引入多个包的使用方式
import (
"fmt" // 引入内置包,从GoRoot的src目录中查找引入
"github.com/gomodule/redigo/redis" // 引入三方包,从GoPath的src目录中引入
)
func main() {
// 使用redis方法
c, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
fmt.Println("Connect to redis error", err)
return
}
// 使用fmt方法
fmt.Println("redis connect succ")
defer c.Close()
}

语法

基础数据类型(均是小写)

1
2
3
4
布尔值 - bool
整型 - int8 int16 int32 int64 (无符号整型 - uint8 uint 16 uint32 uint64
浮点型 - float32 float64
字符串 - string

变量

go语言和Swift一样,是不用写 ; 的,巴适

1
2
3
// 先声明,后赋值【和Swift很像,均使用var, 区别是少了冒号: 直接使用空格,类型均是小写】
var name string
name = "smiacter"
1
2
3
4
// 声明并立即赋值,和Swift一样,会自动判断变量类型
var name = "smiacter"
// go还可以通过 := 符号进行赋值,var关键字也可以省略
name := "smiacter"

条件语句

1
2
3
4
// 常规用法,和Swift一样,if不需要括号
if name == "smiacter" {
fmt.Println("yeah, it's me")
}
1
2
3
4
5
6
7
8
// 可选statement,在判断条件前执行
if statement ; condition {

}
// 如:
if a := 1 ; a < 10 {
fmt.Println("a < 10")
}

循环语句(只有for语句)

1
2
3
4
// 常规用法
for i := 0 ; i < 10 ; i++ {
...
}
1
2
3
4
5
// 仿while循环
for isTrue {
...
// some time make isTrue to false to break
}
1
2
3
4
// 死循环
for {
server.Listen(8080) // 服务器监听8080端口
}

结构体【相当于class,go中没有class?】

1
2
3
4
5
6
// 声明结构体 [type 结构体名 struct]
type Person struct {
name string // 没有使用var
age int32
height float32
}
1
2
3
4
5
6
// 赋值 - 使用零值
person := new (Person) // 通过new创建一个默认零值的对象

// 赋值 - 混合字面量语法,底层仍然调用的是new,顺序必须和声明顺序一样
// 使用&结构体名{},感觉和Swift中字典语法很像
person := &Person {name: "smiacter", age: 30, height: 173.1}
1
2
// 访问结构体成员,使用点.操作符
person.name

数据【感觉是指固定长度数组】

1
2
3
4
5
6
7
// 先声明再赋值
var arr [100] string
arr[0] = "smiacter"
arr[1] = "yin"

// 声明并赋值 数据的语法感觉变化还是挺大的,需要适应
var arr = [100]string{"smiacter", "yin"}
1
2
3
4
5
6
7
8
9
10
// 数组遍历 - 常规用法
for i := 0; i < 10; i++ {
fmt.Println(arr[i])
}

// 数组遍历 - 使用range,可拿到index和value【类似Swift中的enumerate】
// 和Swift一样,当不需要index或value时,可以使用 _ 占位
for index, value := range arr {
fmt.Println("index: %d, value: %s", index, value)
}

切片【动态数组】

1
2
3
4
// 先声明 - 一个字符串类型的切片类型
var mArr []string
// 初始化 - 使用make
mArr = make([]string, "default_value")
1
2
// 声明并初始化 - 涉及可变多个初始化都和字典相像
otherArr := []string{"111", "222", "333"}

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 添加新元素 - append
mArr = append(mArr, "new_string")

// 截取 mArr[lower-bound:upper-bound]
mArr[1:5] // 数组1~5的元素,包含1和不包含5
mArr[:3] // 数组0~3的元素,左边省略,0开始,同样包含左边不包含右边
mArr[3:] // 数组3~最后一个元素,右边省略,代表到数组默认,左右两边都包含

// 获取切片长度
len(mArr)

// 遍历方法 - 使用和数组一样的range遍历方法
for i, value := range mArr {
...
}

map

1
2
3
4
5
6
7
8
// 初始化 - 使用make - instance := make(map[keyType]valueType)
// 例:初始化一个key为string,value为int32的map,必须初始化,如果不初始化不能用来存放键值对,为nil map
mMap := make(map[string]int32)

// 遍历方法 - 和切片一样,使用range
for key, value := range mMap {
...
}

常用操作方法

1
2
3
4
5
6
7
8
// 设置修改元素
mMap[key] = value
// 获取元素
value := mMap[key]
// 删除元素
delete(mMap, key)
// 判断元素是否存在,如果存在,则ok为true,赋值给val,如果不错在,则ok为false,val为零值
val, ok = mMap[key]

函数

1
2
3
4
5
6
7
8
// 函数定义方式
func func_name(p1 type, p2 type, ...) (return_type1, return_type2, ...) {
}

// 举例 - 和Swift几乎一样,只是参数冒号: 改为了空格
func max(a int32, b int32) int32 {
return a > b ? a : b
}

方法【感觉是和Swift中接收指针的函数一样,go里面多了种叫法】

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方法定义方式
func (p Pointer) func_name() return_type {
}

// 举例 - 参数传入Person结构体的指针,方法名字为GetName,返回值为string类型
func (p *Person) GetName() string {
return p.name
}

// 举例 - 传入的指针是引用类型,可以持有修改
func (p *Person) SetName(name string) {
p.name = name
}

异常处理

使用error接口【抛出预期内的错误】

1
2
3
4
5
6
7
8
9
10
11
// error类型定义如下:
type error interface {
Error() string
}

// 使用errors.New()抛出错误异常
func check(isvalid bool) error {
if !isvalid {
return errors.New("不合法的参数") // 效果和exception类似
}
}

使用defer语句

1
2
// defer语句是在main函数退出后,它的代码才调用,不管程序是否有异常都会执行
// 一般如数据库、文件、锁操作等,会使用defer语句进行数据库的释放,文件句柄的释放,锁的释放

使用panic-recover 机制

1
2
// 代码运行时崩溃,go会自动panic
// panic () 是一个内建函数,可以中断原有的控制流程,执行函数中的延迟函数defer(该defer必须在panic之前被加载)。调用recover可以捕获panic中传入的值
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
// panic-recover 示例
package main

import "fmt"

func main() {
var a int
var b int
fmt.Println("请输入分子分母,以空格隔开,确定按回车键")
fmt.Scanf("%d %d", &a, &b)

//! defer延时函数必须在可能发生panic之前定义
defer func() {
if r := recover(); r != nil { //! 声明recover继续执行panic后的代码,可接收panic传入的值
fmt.Printf("panic的内容为:%v\n", r)
}
}()

rs := divide(a, b)
fmt.Println("除法调用的结果为:", rs) //! 没发生panic正常流程

fmt.Println("发生了panic,这里还会执行吗") //! 不会!
}

func divide(a int, b int) int {
if b == 0 {
panic("分母不能为0") //! 产生了panic,且抛出原因,这时会在延时函数defer中recover函数中接收,继续执行其中的代码
} else {
return a / b
}
}

面向对象

封装 【go中通过约定来实现权限控制】

  • public - 变量和方法的首字母大写
  • private - 变量和方法首字母小写
  • go中没有继承,则无protected权限

继承

  • go语言没有继承,可以通过在struct中包含其他struct,继承该包含struct的方法和变量,或者重写

多态

  • 在go中,只要某个struct实现了某个interface的所有方法,那么我们认为这个struct实现了这个类

并发

go是一门天然支持高并发的语言

协程

go中的并发是以协程为基本粒度,类似其他语言中的线程。协程与线程是存在差别的,线程是内核态纬度的,利用CPU的中断机制进行线程切换。而协程是用户态纬度的,是一个特殊的函数,也可以中断执行。

并发

go中的并发正是通过协程实现并发的,并发中的每一个协程叫做goroutine, 其语法如下:

1
2
// 在函数前加个go即可
go f(params)

多个goroutine之间的消息通信方式,推荐使用channel,可以使用操作符<-进行接收与发送值

1
2
3
4
5
6
// channel在使用前必须初始化,和map,slice方式一样
ch := make(chan int)
// 将v值传给channel ch
ch <- v
// 从channel ch中接收值,赋值给v
v := <-ch
1
2
// 可以设置缓冲区,第二个参数为缓冲长度
ch := make(chan int, 100)
1
2
// 关闭一个channel
close(ch)

select,使得一个 goroutine 在多个通讯操作上等待。select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。select 中的其他条件分支都没有准备好的时候,default 分支会被执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select { // 类似switch-case,不同的是操作协程
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}

其他

  • go中字符串使用双引号包裹,和Swift一样
  • 变量和方法名遵守驼峰式命名