Go入门教程

作者: 宏势 | 来源:发表于2022-04-12 20:20 被阅读0次

    GO简介

    Go语言(也叫 Golang)是Google开发的开源编程语言。
    Logo: 囊地鼠(Gopher)
    源码地址:https://github.com/golang/go

    起源与发展

    2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,旨在提高多核联网机器和大型代码库时代的编程效率,2009 年 11 月首次向公众发布 Go,2012年发布go稳定版。截至目前2022年,全球知名 TIOBE 编程语言社区统一语言排名第十名左右。目前主流的云原生技术很大一部分都是Go实现的,比如:Docker容器,Kubernetes,Istio,ETCD,InfluxDB等等。


    作者.png
    • Robert Griesemer,参与开发 Java HotSpot 虚拟机;
    • Rob Pike,Go 语言项目总负责人,贝尔实验室 Unix 团队成员,参与的项目包括 Plan 9,Inferno 操作系统和 Limbo 编程语言;
    • Ken Thompson,贝尔实验室 Unix 团队成员,C 语言、Unix 和 Plan 9 的创始人之一,与 Rob Pike 共同开发了 UTF-8 字符集规范

    语言特性

    Go 语法简洁,上手容易,快速编译,支持跨平台开发,自动垃圾回收机制,天生的并发特性,更好地利用大量的分布式和多核的计算机。简单来说:Go语言实现了开发效率与执行效率的完美结合,具有媲美C语言的运行速度,又具有与Python,Java语言相近开发效率。

    语法特点

    Go 语言支持指针,引入协程(goroutine)实现并发,通过Channel实现协程间通讯,函数方法支持多个返回值,通过 recover 和 panic 来替代异常机制,从1.18版本开始支持泛型;

    没有类概念,通过结构体和interface实现面向对象,不支持函数重载,不支持隐式转换,不支持三元运算符,不支持静态变量,
    不支持动态链接库和动态加载代码;

    安装配置

    官网下载:


    下载.png

    window开发环境

    默认情况下.msi 文件会安装在 c:\Go 目录下,可选择自定义路径进行安装,设置为GOROOT的值,即Go的SDK路径。具体SDK库使用见官网标准库文档,三方中文文档:https://studygolang.com/pkgdoc

    #查看版本
    go version
    
    #查看Go环境变量
    go env 
    

    查看所有命令:

    go help 
    

    helloworld.go 源文件如下:

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Hello, 世界")
    }
    
    #编译 输出可执行文件.exe
    go build helloworld.go 
    #编译并运行
    go run helloworld.go
    

    跨平台编译

    #修改环境变量
    SET CGO_ENABLED=0
    SET GOOS=linux
    #编译输出linux下可执行文件
    go build helloworld.go 
    

    单元测试

    命名文件时需要让文件必须以_test结尾,测试用例函数需要以Test为前缀,例如func TestXXX(t*testing.T)
    helloworld_test.go 文件

    import "testing"
    func TestA(t *testing.T) {
        t.Log("A")
    }
    func TestB(t *testing.T) {
        t.Log("B")
    }
    
    # 执行所有测试函数
    go test helloworld_test.go 
    # 执行指定测试函数
    go test -run TestA helloworld_test.go
    

    工程化

    Go 官方在1.11开始推出了Go Modules的功能,配置GO111MODULE=auto 启用,1.13版本默认启动,用于go项目得依赖管理

    设置代理:

    go env -w GOPROXY=https://goproxy.cn
    

    常用代理:阿里云(https://mirrors.aliyun.com/goproxy/ )七牛云(https://goproxy.cn),https://goproxy.io

    #创建目录
    mkdir hello
    cd hello
    #初始化工程
    go mod init example/hello
    
    # 下载依赖包
    go get github.com/robfig/cron@v1.2.0
    go get github.com/google/uuid@v1.3.0  #下载$GOPATH/pkg/mod目录下
    

    输出:
    go.mod 文件标记每个依赖包的版本

    module example/hello  
    go 1.15
    
    require (
        github.com/google/uuid v1.3.0
        github.com/robfig/cron v1.2.0
    )
    

    go.sum 文件记录每个依赖包的哈希值

    github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
    github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
    github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
    github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
    

    正常情况下,每个依赖包版本会包含两条记录:

    • 第一条记录为该依赖包版本整体(所有文件)的哈希值
    • 第二条记录仅表示该依赖包版本中go.mod文件的哈希值

    go.mod只需要记录直接依赖的依赖包版本,只在依赖包版本不包含go.mod文件时候才会记录间接依赖包版本; go.sum则是要记录构建用到的所有依赖包版本, go.sum文件中记录的依赖包版本数量往往比go.mod文件中要多

    GOSUMDB 环境变量标识checksum database,用于依赖包版本写入go.sum 文件之前进行二次校验

    go不允许循环依赖(包A>包B>包C>包A)

    早期GOPATH工作区方式,GOPATH包含三个子目录:

    • src 存放源代码的位置
    • pkg 存储预编译目标文件的地方,以加速程序的后续编译
    • bin 编译后的二进制文件的位置
      引入三方套件,先查找目录GOPATH/src没有再查找目录GOROOT/src,如果没找到就报错了,虽然有一些第三方的包管理工具比如:Vendor、Dep,但是并不好用

    引入本地包

    module example/hello  
    go 1.15
    
    require (
        github.com/google/uuid v1.3.0
        github.com/robfig/cron v1.2.0
            example/greeting v1.0.0 //indirect  
    )
    //replace 将远程包替换为本地包服  模块名=>模块路径
    replace example/greeting => ../greeting
    

    //indirect 不能省略

    包管理

    • 文件夹名与包名没有直接关系,并非需要一致,但建议一致
    • 同一个文件夹下的文件只能有一个包名(即一个目录内只能有一个包名)
    • import 两个同名包时,可以设置别名区分
    import (
        "example/hello/com/greeting"  //   模块名+包路径
            _ "example/hello/com/math"  //匿名(可以不使用),促发包内init()方法
            myMath "example/hello/com/math"  //设置别名
        "fmt"
    )
    func main() {
        greeting.Say()  //包名.方法名
        fmt.Println("hello world")
    }
    

    访问控制

    以首字母大小写进行变量、方法、函数得访问控制

    • 首字母大写,公开的
    • 首字母小写,包级私有的,只能包内访问
    • internal代码包内,首字母大写的,模块级私有(go 1.4版本)
      只能该代码包得 直接父包及其子包中的代码引用

    基础语法

    变量

    var a = 100 // 支持类型推导
    var b int = 200   //声明并初始化
    var (
        x,y,z string   //只声明
    )
    func main() {
        var a int 
        a = 150
        var b = true
        c := 300 //短变量声明并初始化,只能在函数体内使用
        fmt.Println(a,b, c)
        fmt.Println(x,y, z)
        x = "hello world"  //全局变量赋值
        fmt.Println(x, y, z)
    }
    

    函数体内的变量一旦声明就一定要使用,不然会报编译出错,变量读取优先读取函数体内的,没找到再读取函数体外的

    _特殊只写变量,例如 值 5 在:_, b = 5, 7 中被抛弃,常用于不需要从一个函数得到所有返回值

    常量

    常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型,值不可更改,表达式只支持内置函数

    const num int = 10
    const str = "Hello world" //支持按值进行类型推导
    const LENGTH = len(str) //表达式只支持内置函数
    //定义枚举值
    const (
        Unknown = 0
        Female = 1
        Male = 2
    )
    

    基础数据类型

    go不支持隐式类型转换,需显示转换,要转换的类型(变量)

    • 整型
      有符号按长度分为:int8、int16、int32、int64 ,对应的无符号整型:uint8、uint16、uint32、uint64,默认值为0
    var x int8 = 88
    var y int32 = 555
    y = int32(x) //低位转高位
    fmt.Println(x, y) //输出88,88
    var a int8 = 99
    var b int32 = 9999
    a = int8(b) //高位转低位 转成二进制截取
    fmt.Println(a,b) //输出15,9999
    
    • 特殊整型
      int/uint 32位操作系统上就是int32/uint32,64位操作系统上就是int64/uint64
      uintptr 无符号整型,用于存放一个指针
      byte 实际是一个uint8,代表了ASCII码的一个字符
      rune 实际是一个int32,代表一个UTF-8字符

    • 布尔型
      true和false,默认值为false,无法参与数值运算,不允许将整型强制转换为布尔型

    • 浮点型
      支持两种浮点型数:float32和float64,默认类型是float64

    • 复数
      complex64和complex128

    字符串

    go语言字符串是一个任意字节的常量序列,底层是byte字节数组,len()方法计算的是字节总数,修改字符串必须先转成byte或者rune,多行字符串可以使用反引号``

    func main(){
        var str = "Go入门教程!"
        fmt.Println(str[0:],str[0:5]) //按索引截取字符串
        fmt.Println(len(str)) //输出15, 一个中文三个字节
        fmt.Println(utf8.RuneCountInString(str))//输出7,代表7个字符
        var charArray = []rune(str) //转成字节切片
        fmt.Println(len(charArray))
        //range 遍历所有字符
        for _,s:= range str {
            fmt.Printf("%v\t",string(s))
        }
        //字符串比较==
        var str1= "Go入门教程" + "!"
        fmt.Println(str==str1)  
    
        str += "welcome" //字符串连接
        fmt.Println(str)
    }
    

    常用工具包strings

    strings.ToUpper(str),strings.ToLower(str)
    strings.Join([]string{"java", "go", "c++"},";")
    strings.ContainsAny(str, "GW")
    strings.Contains(str,"Go")
    strings.Split(str,"")
    strings.EqualFold(str,str1) //不区分大小比较
    strings.Compare(str1,str) //区分大小写比较
    

    字符串连接采用strings.Builder或者 bytes.Buffer,推荐strings.Builder 性能最高

    func StringAppend(num int) {
        var now = time.Now()
        var str = ""
        for i := 0; i < num; i++ {
            str += "append" + strconv.Itoa(i)
        }
        fmt.Println(time.Since(now))
        fmt.Println(len(str))
    
        now = time.Now()
        var stringBuilder = strings.Builder{}
        for i := 0; i < num; i++ {
            stringBuilder.WriteString("append" + strconv.Itoa(i))
        }
        fmt.Println(time.Since(now))
        fmt.Println(len(stringBuilder.String()))
    
        now = time.Now()
        var bytebuffer bytes.Buffer
        for i := 0; i < num; i++ {
            bytebuffer.WriteString("append" + strconv.Itoa(i))
        }
        fmt.Println(time.Since(now))
        fmt.Println(len(bytebuffer.String()))
    }
    

    类型转换

    基础数据类型转string,采用strconv包 或者fmt.Sprintf()

    var x int = 99
    var y float32= 88.88
    fmt.Sprintf("%d,%.2f",x,y)
    

    string转基础数据类型,利用strconv包

    var x = "99";
    var y = "88.88"
    
    

    运算符

    不支持三元运算符,支持赋值交换,比如a,b=b,a

    控制语句

    支持 if,for,case,表达式不需要(),支持goto 语言但不推荐使用

    func main() {
        if x,y:=5,3; x+y > 10 {   //判断表达式前支持一个赋值表达式分号分隔
            fmt.Printf("%d + %d > 10",x, y)
        }else if x+y < 10{
            fmt.Printf("%d + %d < 10",x, y)
        }else{
            fmt.Printf("%d + %d = 10",x, y)
        }
    }
    
    // 冒泡算法
    func BubbleSort(array []int){
        length := len(array)
        for i:= 0; i < length-1; i++ {
            for j := 0; j < length-i-1; j++{
                if array[j] > array[j+1]{
                    array[j], array[j+1] = array[j+1],array[j]
                }
            }
        }
    }
    // 等同于while
    for i < 100{  
    }
    //死循环
    for{
    }
    

    表达式不需要(), { 不能单独放在一行, 编译报错 ,代码行之间不需要分号也不能用分号

    数据结构

    数组

    支持多维数组,属于值类型,支持range遍历
    例子:随机生成长度为10整数数组

    func RandomArray(){
        var array[10]int    //声明
        var max int
        for i := 0; i < 10; i++{
            array[i] = rand.Intn(100)  //赋值 随机获取100以内的整数
            if array[i] > max {
                max = array[i]
            }
    
        }
        fmt.Printf("数组内容:%v,长度为:%d, 最大值:%d\n",array, len(array),max) //获取数组长度len(array)
    }
    

    切片

    切片(slice) 也叫动态数组 ,是对数组的一个连续片段的引用。底层实现是数组,是引用类型,支持range遍历

    var slice  = []int{10, 20, 30}
    var slice1[]int
    fmt.Println(slice1==nil) //true
    slice1 = make([]int,5,10) //length=5,cap=10
    fmt.Println(slice,slice1) //[10 20 30] [0 0 0 0 0]
    
    //数组转切片
    var array = [6]int{10, 20, 30, 40, 50, 60}
    var slice = array[0:3]
    fmt.Println(slice,len(slice),cap(slice)) //输出[10 20 30] 3 6
    
    //append操作 
    slice1 := append(slice,99, 999)
    sliceX = array[:]
    i:= 3
    sliceY := append(sliceX[:i],sliceY[i+1:]...) //删除sliceX[i] 元素
    
    //copy操作  copy(dst, src []Type) 
    slice2 := []int{1, 2, 3}
    copy(slice1, slice2) //复制slice2到slice1的前三个位置
    fmt.Println(slice1)  //[1 2 3 99 999] 
    
    slice3 := []int{11, 22, 33, 44, 55, 66, 77, 88, 99}
    copy(slice2,slice3) //只复制slice3前三个元素到slice2的前三个位置
    fmt.Println(slice2) //[11,22,33]
    
    

    slice底层实现

    slice.png
    var slice = []int{10, 20, 30, 40, 50, 60}
    var newSlice = slice[1:3]
    fmt.Println(len(slice),cap(slice),len(newSlice),cap(newSlice)) //6 6 2 5
    newSlice[1] += 100
    fmt.Println(slice) //[10 20 130 40 50 60]
    newSlice = append(newSlice, 999)
    fmt.Println(slice,newSlice,len(newSlice),cap(newSlice)) //[10 20 130 999 50 60] [20 130 999] 3 5
    

    当切片Append()操作,如果容量(CAP)足够的时候,会直接在原数组上操作,共享同一个底层数据,当容量不够的时候才会进行扩容,开辟一个新的内存区域(新数组),拷贝原来值,再执行append操作。扩容策略是这样,当切片的容量小于 1024 个元素,翻倍扩容,当大于1024,每次增加原来容量的四分之一(增长因子为:1.25)

    指针

    引用类型


    point.png
    var a  = 100
    b:= &a  // 取地址 赋值给指针变量
    c:= *b // 指针变量中指向地址的值
    var d *int //定义字符指针
    d = b
    fmt.Println(a,b,c,*d) //100 0xc0000120d0 100 100
    *b = 666
    fmt.Println(a,b,c,*d) //666 0xc0000120d0 100 666
    a = 999
    fmt.Println(a,b,c,*d)  //999 0xc0000120d0 100 999
    
    

    make 与 new, make 只能用于初始化slice,map,chan类型;new 可以用于任何类型,nil 代表空指针

    var slice1 = make([]int,5)
    var map1 = make(map[int]string)
    var chan1 = make(chan int,3)
    var x *int = new(int)
    var y *int
    var z * map[int]string = new(map[int]string)
    fmt.Println(x==nil,y==nil,z==nil) //输出false,true,false
    

    Map

    引用类型,支持range遍历

    var color = map[string]int{"red": 1, "blue": 2, "green": 3}
    var city map[string]int // city等于nil,只声明
    var red = color["red"]
    var pink = color["pink"]  //不存在返回对应类型的零值,这里是int类型所以是0
    fmt.Println(red,pink,city==nil) //输出1,0,true
    if blue,exist := color["blue"];exist{
        fmt.Println("exist blue value: ",blue)
    }
    delete(color,"blue") //删除指定key的key-value对
    

    函数

    函数支持多个返回值,支持闭包函数,函数参数支持函数类型,不定长参数用 ...,不支持函数重载

    • 返回多个值
    func Swap(x int , y int) (int, int){
        return  y,x
    }
    
    • 函数参数实现回调函数
    type FuncType func(x int,y int) int //声明函数类型
    
    func Minus(x int,y int) int{
        return x - y
    }
    func CalFun(x int,y int, cal FuncType) int{
        return cal(x, y)
    }
    func main() {
        var result= CalFun(100,200, func(x int, y int) int{  //匿名回调函数
            return x + y
        })
        fmt.Println(result)
        fmt.Println(CalFun(200,100, Minus))
    
    }
    
    • 递归函数实现N的阶乘
    func Factorial(n int64) int64 {
        if n <= 1 {
            return 1
        }
        return n * Factorial(n-1)
    }
    
    
    • 闭包实现斐波那契数列
      F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
    func Fibonacci() func() int{
        a := 0  //代表F(n-2)
        b := 1  //代表F(n-1)
        var add  = func() int {
            result := a + b  //代表F(n)
            a,b =b,result 
            return  result
        }
        return add
    }
    
    func main() {
        var nextNum = Fibonacci() //函数变量
        for i:=0; i < 10; i++ {
            fmt.Print(nextNum(),"  ")
        }
    }
    

    面向对象

    go 语言没有类的概念,用自己的一套方式实现面向对象,通过结构体实现封装,通过结构体绑定函数实现方法,以组合的方式实现继承,(java之父也曾透露过他最想改的就是继承,觉得java继承有点重)更加解耦和轻量,利用interface{}实现多态,需要要继承和实现关键字

    结构体

    结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,是值类型,通过将函数与结构体变量绑定实现方法(当然函数可以与命令类型绑定实现方法)

    type Person struct {
        Name string
        Age uint
        Sex bool //true 代表男, false 代表女
    }
    //直接绑定结构体(传递是值类型)
    func (p Person) Say(content string){
            p.Age=100 //修改拷贝的结构体的值
        fmt.Println(p.Name, content)
    }
    //指针方式绑定结构体(传递是引用类型)
    func (p *Person) SetName(name string) {
        p.Name = name
    }
    // 采用匿名组合实现继承
    type Student struct {
        Person
        class string
    }
    
    
    func TestInit(t *testing.T) {
        per := Person{"张三", 34, true}
        per.Say("welcome !")
        per.SetName("李四")
        stu:= Student{per, "一年一班"}
        stu.Name = "王五"
        stu.Say("hello!")
    }
    

    实际项目中,大部分都会绑定指针类型;既节省了资源,又同步了对象的数据

    interface

    即接口,它把具有共性的方法定义在一起,任何类型实现了这些方法就是实现了这个接口。

    • 接口嵌套接口实现接口继承
    • 接口也可以嵌入到结构中
    • 指针接收者实现接口的方式只能支持赋值结构体指针给接口变量,而值接收者实现接口的方式都可以,因为Go语言中有对指针类型变量求值的语法糖
    type Downloader interface {
        Download()
    }
    
    type Implement interface {
        download(url string)
        save()
    }
    type Template struct {
        url string
        Implement
    }
    //模板方法
    func (t Template)Download()  {
        fmt.Println("prepare download...")
        t.download(t.url)
        fmt.Println("finish download...")
        t.save()
    }
    func (t Template)save(){
        fmt.Println("save ...")
    }
    
    type HttpDownloader struct {
        Template
    }
    func (HttpDownloader) download(url string)  {
        fmt.Println("http download "+url+"....")
    }
    func NewHttpDownloader(url string) Downloader{
        downloader := &HttpDownloader{Template{url:url}}
        downloader.Template.Implement = downloader
        return downloader
    }
    
    type FtpDownloader struct {
        Template
    }
    func (FtpDownloader) download(url string)  {
        fmt.Println("ftp download  "+url+"....")
    }
    func NewFtpDownloader(url string) Downloader{
        downloader := &FtpDownloader{Template{url:url}}
        downloader.Template.Implement = downloader
        return downloader
    }
    

    结构可以嵌套结构体或者接口,接口只能嵌套接口

    interface{}

    空接口。没有方法的接口,所有类型都至少实现了0个方法,所以所有类型都实现了空接口。interface{} 类型常用于函数参数,表示可以传递任意类型,GO运行时会执行类型转换(如果需要)。内置fmt包打印方法用到这个参数

    func main() {
        PrintType("string",56, &oop.Person{Name: "张三", Age: 22, Sex: true})
    }
    // 不定长interface{} 参数
    func PrintType(v ...interface{}){
        for _,valType := range v{
            switch valType.(type) {  //类型断言
            case int:
                fmt.Println("int")
            case bool:
                fmt.Println("bool")
            case string:
                fmt.Println("string")
            case *oop.Person:
                fmt.Println("*Person")
            default:
                fmt.Println("unknow")
            }
        }
    }
    
    

    异常处理

    go 的异常处理简单轻巧高效,推荐采用将异常返回的方式代替捕获异常,可以采用panic+recover模拟类似try...catch

    defer

    defer语句预设延迟函数调用,无论函数怎么返回,都会执行,被延期的函数先进后出的顺序执行,常用于资源释放

    func CopyFile(dstFileName string,srcFileName string) (written int64,err error) {
        srcFile, err := os.Open(srcFileName)
        if err != nil {
            return
        }
        defer srcFile.Close() //声明延迟调用
        //打开dstFileName
        dstFile, err := os.OpenFile(dstFileName, os.O_WRONLY|os.O_CREATE, 0666) //0666 在windos下无效
        if err != nil {
            return
        }
        defer dstFile.Close() //声明延迟调用,先上面先调用
        //调用copy函数
        return io.Copy(dstFile,srcFile)
    }
    

    自定义异常

    内嵌异常接口

    type error interface {
        Error() string
    }
    

    自定义异常 参考内置errors包 errorString结构体实现

    func New(text string) error {
        return &errorString{text}
    }
    // errorString is a trivial implementation of error.
    type errorString struct {
        s string
    }
    func (e *errorString) Error() string {
        return e.s
    }
    

    返回错误方式例子:

    func Divide(a int,b int)(float64,error){
        if b==0{
            return 0, errors.New("除数不能为零值")
        }
        return float64(a)/float64(b),nil
    }
    

    panic+recover

    panic 导致程序终止运行,采用recover可以捕获panic,重新获得Go程序控制权并恢复正常运行

    func Divide(a int, b int) (float64,error){
        defer func() {
            err := recover()  //捕获到异常
            if err != nil{
                fmt.Println(err)
            }
        }()
        defer fmt.Println("recover :",recover())  //recover为nil,向外传递异常
        if b == 0{
            panic("除数不能为零")
        }
        return float64(a)/float64(b),nil
    }
    

    recover只能在被延期的函数内部才有用,且只能被最后一个被延期执行的函数捕获,因为任何未捕获的错误都会沿调用堆栈向外传递,非必要不要使用panic,带来隐患和性能损耗

    相关文章

      网友评论

        本文标题:Go入门教程

        本文链接:https://www.haomeiwen.com/subject/uqirjrtx.html