优势
- 简单的部署方式
- 可直接编译成机器码
- 不依赖其他库
- 直接运行即可部署
- 静态类型语言
- 编译的时候检查出大多数的问题
- 语言层面的并发
- 强大的标准库
- runtime系统调度机制
- 高效的GC垃圾回收
- 丰富的标准库
- 简单易学
- 25个关键字
- c语言支持
- 面向对象
- 跨平台
成就
- Docker
- Kubernetes
缺点
- 包管理,大部分都托管在github上
- 无泛化类型
- 所有的Exception都用Error来处理
- 对C的降级处理并非无缝,没有C降级到asm(汇编)那么完美
杂记
- 分号可加可不加
- 左花括号一定和方法名在同一行(同java,但强制)
变量
声明变量的四种方式
- var a int // 默认值为0
- var a int = 100 // 初始化值
- var c = 100 //不显示声明类型
- c := 100 // 最常用,省去var
注:声明全局变量,以上方法1、2、3都可以,:=
只能使用在函数体中
- 多变量声明
- 方式一
var v1, v2 = 100, "name"
- 方式二
var ( v1 int = 100 v2 bool = true )
常量
将变量中的var修改为const即可
const len int = 10
- 可以使用const来定义枚举
const(
BEIJING = 0
WUHAN = 1
SHENZHEN = 2
)
注:可以使用iota
关键字简化,只用给第一个枚举赋值为iota,其下枚举会自动赋值为上一个值+1,只能使用在const中
函数
- 如果函数名首字母大写,则对外开放(类似public),小写则只能在当前包内调用
- 函数定义
func fool(v1 string, v2 int) int {
fmt.println("v1=", v1)
c := 100 + v2
return c
}
- 支持多返回值(匿名)
func fool(v1 string, v2 int) (int, int) {
return 111, 222
}
- 支持多返回值(命名)
// v3 v4其实也是两个形参,默认值为0
func fool(v1 string, v2 int) (v3 int, v4 int) {
v3 := 100
v4 := 200
return
}
init函数与import导包

执行main函数前,先执行导包,并执行包的init函数,依次递归
- init函数优于main函数执行
- 导入的包需要写全限定限定名
- 导包但是没有调用则编译报错
- 有时候只需要执行导入包的init函数,则只需要在导包时在前面加上一个下划线。这样即使不调用该包也能编译通过并执行其init函数
import {
_ "pkg1/lib1"
}
- 导包别名
import {
mylib1 "pkg1/lib1"
// 使用 . 命名则可以直接调用其中的方法而不用 lib.fun 方式,最好不用,防止方法名冲突
. "pkg2/lib2"
}
func main() {
mylib1.func1()
lib2Func()
}
指针
函数对基本数据类型只能按引用传递,如果希望按值传递,则可以传递变量指针
使用 &
取地址,使用 *
获取地址的变量,如果是对象或map等,还是传的是地址,类似java

defer 关键字
- defer 关键字类似于java中 try…catch…finally中的finally,定义在方法中,方法声明周期结束后调用
- 多个defer使用压栈操作,执行顺序和定义顺序相反
- return执行结束完成之后才执行 defer
func main() {
defer fmt.Println("main end1")
defer fmt.Println("main end2")
fmt.Println("man run")
}
// 执行结果
/*
man run
main end2
main end1
*/
slice(动态数组)
普通数组
- 定义固定长度数组
func main() {
var arr1 [10]int
arr2 := [10]int {1,2,3,4} //会对前四个赋值
// 遍历,使用 range 关键字
for index, value := range arr2 {
fmt.Println("index:", index, "value:", value)
}
}
动态数组
- 切片定义
func main() {
arr1 := []int{1,2,3,4} // 定义之后 arr 的长度为4
arr2 := []int // 声明一个slice,但是没有分配空间
arr2 = make([]int, 3) // 给arr2手动开辟三个空间
var arr3 []int make([]int, 3) // 上面的简写
arr4 := make([]int, 3) // 上面的简写
// 判断slice是否为空
if arr4 == nil {
fmt.Pringln("is nil")
}
}
- 容量和长度
区别slice的容量和长度,长度为slice中有值的个数,容量为为该slice开辟的总空间长度,如果添加元素超过容量值,则go会再开辟一个容量的长度内存

// 定义切片长度为3,且容量为5
var arr1 = make([]int, 3, 5)
fmt.Println("len:", len(arr1), "cap:", cap(arr1))
// 向slice追加一个元素,此时其长度为4,容量还是5
arr1.append(1)
// 可以使用类似python的方式进行截取,但其实他们都指向同一个元素
arr2 := arr1[0:2]
// 可以使用copy函数复制slice
var arr3 = make([]int, 3)
copy(arr2, arr3)
map
类似于java的Map,多种声明方式同slice
// key为string类型,value为int类型,此时只是声明,需开辟空间后使用
var map1 map[string]int
// 分配空间
make(map[string]int, 10)
// 赋值
map1["one"] = 1
// 声明2,其他同slice
map2 := map[string]int{
"one": 1,
"two": 2
}
// 遍历
for key, value:= range map1{
// do some thing
}
// 删除
delete(map1, "one")
面向对象特征
结构体
使用 type 关键字声明一种新的数据类型
// 类似于c的宏定义
type myint int
// 定义一个结构体
type Book struct {
title string
auth string
}
// 创建结构体对象
var book Book
book.title = "go"
类
类就是 结构体 + 方法,只不过这里的方法名前有特殊标记
// 访问限制使用大小写开头控制
type Person struct {
Name string
Age int
}
// Person指该方法绑定到哪个类中
// this指当前调用对象的拷贝,也就是说,可以读取调用对象的属性,但是无法修改
func (this Person) Show() {
fmt.Println("name:", this.Name)
}
// 使用指针即可修改对象值
func (this *Person) SetName(name string) {
this.Name = name
}
func main() {
p1 := Person{Name: "hunt", Age: 12}
p1.show
}
继承
// 定义子类
type Man struct {
// 像属性一样声明即表示继承
Person
sex string
}
// 定义字类对象
man1 := Man{Person{"hunt", 12}, "m"}
// 或者
var man2 Man
man2.name = "hunt"
man2.sex = "m"
多态
go中的多态一定要通过接口实现接口的本质就是一个实现类的指针
type Animal interface {
Sleep()
GetColor()
}
// 任何类只要实现接口的所有方法即可,无需显式定义
type Cat struct {
color string
}
func (this *Cat) Sleep() {
fmt.Println("sleep")
}
func (this *Cat) GetColor() {
return this.Color
}
var animal1 Animal
animal1 = &Cat{"red"}
注:事实上,上述代码中,Cat类并不是Animal接口的实现,这是因为 Sleep 和 GetColor 两个方法都是 *Cat 的方法,即Cat指针才是Animal的实现类(指针是有类型的),所以上述代码若想使用Animal接收Cat对象,就需要使用&
(最后一行代码)
如果要做类型断言,应该断言为指针
// 正确写法
cat, ok := animal1.(*Cat)
// 错误写法
cat, ok := animal1.(Cat)
interface 用作通用万能接口
- 使用
interface{}
类似java中的Object - 使用类型断言机制判断具体类型
func myFunc(arg interface{}) {
fmt.Println(arg)
// 区分具体类型,只有 interface{} 才能这样判断
value, ok := arg.(string)
if ok {
fmt.Println("is str, value: ", value)
}
}
myFunc(100)
myFunc("hunt")
反射
Go中的每个变量内部都有两个指针:type和value,他们合起来成为pair对,其中type又分为静态类型(int string等)和具体类型(类似于引用类型),value则存储具体的值
当定义一个 interface{} 类型的变量并将一个string类型的变量赋给它时,string变量中的type和value就会传递给interface{}变量,就可以通过类型断言获取其具体类型
- reflect包
需看文档
import (
"fmt"
"reflect"
)
func reflectProperty(arg interface{}) {
// 获取类型
argType := reflect.TypeOf(arg)
fmt.Println("type: ", argType.Name())
argValue := reflect.ValueOf(arg)
fmt.Println("value: ", argValue)
// 获取类型中的字段,使用NumField 获取所有方法
for i := 0; i < argType.NumField(); i++ {
field := argType.Field(i)
value := argValue.Field(i).Interface()
}
}
结构体标签
为了判断该属性在这个包中的作用,用法类似于java中的注解。可以用反射获取
type Man struct {
Name string `info:"name" doc:"名字"`
}
例:对象转json,其中的json标签表示转换为json的key

goroutine
可以简单理解为一个线程
需要重温 co-routine、协程
使用 go 关键字创建一个go(即gorontine)
func show() {
fmt.Println("hi")
}
// main是一个主goroutine
func main() {
// 他们会并发执行,两个子goroutine
go show()
go show()
go func() {
fmt.Println("hello")
// 退出当前goroutine
runtime.Goexit()
}
}
goroutine间的通信

使用管道(channel)进行通信
使用 close(myChannel) 关闭管道,带缓冲的channel可以继续获取数据直到缓冲获取完毕
无缓冲channel
管道会发生阻塞,见以下注释
func main() {
//定义一个channel,chan int合起来是一个整体
c := make(chan int)
go func() {
defer fmt.Println("goroutine 结束")
fmt.Println("goroutine 正在运行")
c <- 666 // 将666发送到管道,如果执行到这里,但是main的goroutine还没有执行到接收数据,则这里会发生阻塞
}
num := <-c // 冲管道中接收数据,获取到数据之前会阻塞
data, ok := <-c // data为channel中得到的数据,ok判断channel是否关闭
}
有缓冲管道
缓冲用完之前不会发生阻塞,但是缓冲用完之后向里面存数据就会发生阻塞,当缓冲为空时,取数据就会发生阻塞,每次只能读写一个缓冲长度
func main() {
c := make(chan int, 3) // 3为缓冲长度
}
// 可以使用简单for循环或者range关键字读取c中的数据
for data := range c {
fmt.Println(data)
}
- 使用
select
监控多个channel
c1 := make(chan int)
c2 := make(chan int)
for {
select {
case <-c1:
fmt.Println("c1")
case d2<-c2:
fmt.Println(d2)
}
}
Go Modules
类似于 python的pip
- 解决go的依赖管理问题
- 淘汰了现有的 GOPATH模式(无依赖版本控制)
必要的环境变量

- GO11MODULE
go语言使用该环境变量作为Go modules的开关,可用值- auto:只要项目中包含了go.mode 文件就启用
- on:启用
- off:禁用
可以使用go env -w GO11MODULE=on
设置
- GOPROXY
设置拉取模块的代理位置(类似于模块仓库位置) - GOSUMDB
设置一个专门用于校验下载模块的网址
使用
- 开启go module
- 任意文件夹创建go项目文件夹
- cd进文件夹,使用
go mod init github.com/abc/moudle_test
后面的参数意味着,如果别人需要导入你的这个包,则需要这样写,相当于这个模块的名称。执行之后,当前目录下会自动创建一个go.mod
文件,记录了上面写的模块名称和go的版本 - 写项目代码
go.mod
目录下执行go get github.com/pkg_name/module_name
, 后面的参数是你项目中 import 的远程仓库和包的名称。然后使用 go mod replace your_module 修改依赖版本(非必须)