Golang基础知识

优势

  1. 简单的部署方式
    1. 可直接编译成机器码
    2. 不依赖其他库
    3. 直接运行即可部署
  2. 静态类型语言
    1. 编译的时候检查出大多数的问题
  3. 语言层面的并发
  4. 强大的标准库
    1. runtime系统调度机制
    2. 高效的GC垃圾回收
    3. 丰富的标准库
  5. 简单易学
    1. 25个关键字
    2. c语言支持
    3. 面向对象
    4. 跨平台

成就

  1. Docker
  2. Kubernetes

缺点

  1. 包管理,大部分都托管在github上
  2. 无泛化类型
  3. 所有的Exception都用Error来处理
  4. 对C的降级处理并非无缝,没有C降级到asm(汇编)那么完美

杂记

  • 分号可加可不加
  • 左花括号一定和方法名在同一行(同java,但强制)

变量

声明变量的四种方式

  1. var a int // 默认值为0
  2. var a int = 100 // 初始化值
  3. var c = 100 //不显示声明类型
  4. c := 100 // 最常用,省去var

注:声明全局变量,以上方法1、2、3都可以,:= 只能使用在函数体中

  • 多变量声明
  1. 方式一
var v1, v2 = 100, "name"
  1. 方式二
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模式(无依赖版本控制)

必要的环境变量

  1. GO11MODULE
    go语言使用该环境变量作为Go modules的开关,可用值
    1. auto:只要项目中包含了go.mode 文件就启用
    2. on:启用
    3. off:禁用
      可以使用 go env -w GO11MODULE=on 设置
  2. GOPROXY
    设置拉取模块的代理位置(类似于模块仓库位置)
  3. GOSUMDB
    设置一个专门用于校验下载模块的网址

使用

  1. 开启go module
  2. 任意文件夹创建go项目文件夹
  3. cd进文件夹,使用 go mod init github.com/abc/moudle_test 后面的参数意味着,如果别人需要导入你的这个包,则需要这样写,相当于这个模块的名称。执行之后,当前目录下会自动创建一个 go.mod 文件,记录了上面写的模块名称和go的版本
  4. 写项目代码
  5. go.mod 目录下执行 go get github.com/pkg_name/module_name, 后面的参数是你项目中 import 的远程仓库和包的名称。然后使用 go mod replace your_module 修改依赖版本(非必须)

Leave a Comment