GO(一) | 青训营笔记

102 阅读12分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

Go安装配置

Windows下安装配置

官网:The Go Programming Language (google.cn)

Downloads - The Go Programming Language (google.cn)

环境配置:

GOPATH:即默认的workspace路径,在未指定项目路径时使用; GOROOT:Golang的安装路径;

配置全局的GOPATH,先创建GOPATH文件夹,包括里面的 bin、pkg、src文件夹

在系统环境变量里,新建GOPATH【新建的GOPATH文件夹路径】、GOROOT【Goland的安装目录】配置,同时将D:\goland\Go\bin加到path

配置结果查看:go env

问题:

Go调用包报错build command-line-arguments: cannot find module for path XXXXXX 解决办法:

1、打开shell命令窗:ctrl+r , 输入 cmd 2、输入go env -w GO111MODULE=auto,这句是将GO111MODULE设为自动【即使用 go module来进行依赖管理】 3、再次测试,问题解决

配置代理:go env -w GOPROXY=goproxy.cn,direct

Linux下安装配置

选择通过上传解压包的方式安装,也可以通过 wget 方式安装

安装:

  • 安装目录 cd /usr/local/
  • 下载 wget https://dl.google.com/go/go1.18.2.linux-amd64.tar.gz
  • 解压安装 tar -C /usr/local -xzf go1.18.2.linux-amd64.tar.gz
  • 查看安装结果 使用cd /usr/local/go/命令进入该目录,然后执行bin/go version 命令 go version go1.18.2 linux/amd64

环境变量:

注意:(配置到/etc/profile中是不生效的)配置在 bashrc中 vim ~/.bashrc 进入配置文件,在文件最后面添加

export GOROOT=/usr/local/go #GOROOT是系统上安装Go软件包的位置。
export GOPATH=/root/go/GOPATH #GOPATH是工作目录的位置。这个是自己创建的,想放在哪都行
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
export GOPROXY="https://goproxy.cn"

加完后按esc键退出编辑模式,输入:wq保存退出

source ~/.bashrc 更新配置文件

输入go env -w GO111MODULE=on,这句是将GO111MODULE设为开启(即使用 go module来进行依赖管理)

goland安装:

官网:GoLand by JetBrains: More than just a Go IDE

用Goland打开一个新建的文件夹,然后在settings里配置GOROOT、GOPATH、Go Modules

常用go mod命令

命令作用
go mod download下载依赖包到本地(默认为GOPATH/pkg/mod目录)
go mod edit编辑go.mod文件
go mod graph打印模块依赖图
go mod init初始化当前文件夹,创建go.mod文件
go mod tidy增加缺少的包,删除无用的包
go mod vendor将依赖复制到vendor目录下
go mod verify校验依赖
go mod why解释为什么需要依赖

Go基础

主要特征:

  1. 自动立即回收
  2. .更丰富的内置类型
  3. 函数多返回值
  4. 错误处理
  5. 匿名函数和闭包
  6. 类型和接口
  7. 并发编程
  8. 反射
  9. 语言交互性

Go语言命名

  1. Go的函数、变量、常量、自定义类型、包(package)的命名方式遵循以下规则:

    1. 首字符可以是任意的Unicode字符或者下划线
    2. 剩余字符可以是Unicode字符、下划线、数字
    3. 字符长度不限
  2. Go只有25个关键字

    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
    
  3. Go还有37个保留字

    Constants:    true  false  iota  nil
    ​
    Types:    int  int8  int16  int32  int64  
              uint  uint8  uint16  uint32  uint64  uintptr
              float32  float64  complex128  complex64
              bool  byte  rune  string  error
    ​
    Functions:   make  len  cap  new  append  copy  close  delete
                 complex  real  imag
                 panic  recover
    
  4. 可见性

    • 声明在函数内部,是函数的本地值,类似private
    • 声明在函数外部,是对当前包可见(包内所有.go文件都可见)的全局值,类似protect
    • 声明在函数外部且首字母大写是所有包可见的全局值,类似public

Go语言声明:

有四种主要声明方式:

var(声明变量), const(声明常量), type(声明类型) ,func(声明函数)。

Go的程序是保存在多个.go文件中,文件的第一行就是package XXX声明,用来说明该文件属于哪个包(package),package声明下来就是import声明,再下来是类型,变量,常量,函数的声明

Go项目建构及编译

一个Go工程中主要包含以下三个目录:

  • src:源代码文件
  • pkg:包文件
  • bin:相关bin文件

1: 建立工程文件夹 goproject

2: 在工程文件夹中建立src,pkg,bin文件夹

3: 在GOPATH中添加projiect路径 例 e:/goproject

4: 如工程中有自己的包examplepackage,那在src文件夹下建立以包名命名的文件夹 例 examplepackage

5:在src文件夹下编写主程序代码代码 goproject.go

6:在examplepackage文件夹中编写 examplepackage.go 和 包测试文件 examplepackage_test.go

7:编译调试包

go build examplepackage

go test examplepackage

go install examplepackage

这时在pkg文件夹中可以发现会有一个相应的操作系统文件夹如windows_386z, 在这个文件夹中会有examplepackage文件夹,在该文件中有examplepackage.a文件

8:编译主程序

go build goproject.go

成功后会生成goproject.exe文件

至此一个Go工程编辑成功。

1.建立工程文件夹 go
$ pwd
/Users/***/Desktop/go
2: 在工程文件夹中建立src,pkg,bin文件夹
$ ls
bin        conf    pkg        src
3: 在GOPATH中添加projiect路径
$ go env
GOPATH="/Users/liupengjie/Desktop/go"
4: 那在src文件夹下建立以自己的包 example 文件夹
$ cd src/
$ mkdir example
5:在src文件夹下编写主程序代码代码 goproject.go
6:在example文件夹中编写 example.go 和 包测试文件 example_test.go
    example.go 写入如下代码:
​
    package example
​
    func add(a, b int) int {
        return a + b
    }
​
    func sub(a, b int) int {
        return a - b
    }
​
    example_test.go 写入如下代码:
​
    package example
​
    import (
        "testing"
    )
​
    func TestAdd(t *testing.T) {
        r := add(2, 4)
        if r != 6 {
            t.Fatalf("add(2, 4) error, expect:%d, actual:%d", 6, r)
        }
        t.Logf("test add succ")
    }
​
7:编译调试包
    $ go build example
    $ go test example
    ok      example    0.013s
    $ go install example
​
$ ls /Users/***/Desktop/go/pkg/
darwin_amd64
$ ls /Users/***/Desktop/go/pkg/darwin_amd64/
example.a    
8:编译主程序
    oproject.go 写入如下代码:
    package main 
​
    import (
        "fmt"
    )
​
    func main(){
        fmt.Println("go project test")
    }
​
    $ go build goproject.go
    $ ls
    example        goproject.go    goproject
​
       成功后会生成goproject文件
    至此一个Go工程编辑成功。
​
       运行该文件:
    $ ./goproject
    go project test

编译问题:

golang的编译使用命令 go build , go install;除非仅写一个main函数,否则还是准备好目录结构; GOPATH=工程根目录;其下应创建src,pkg,bin目录,bin目录中用于生成可执行文件,pkg目录中用于生成.a文件; golang中的import name,实际是到GOPATH中去寻找name.a, 使用时是该name.a的源码中生命的package 名字;这个在前面已经介绍过了。

注意点:

1.系统编译时 go install abc_name时,系统会到GOPATHsrc目录中寻找abc_name目录,然后编译其下的go文件;
2.同一个目录中所有的go文件的package声明必须相同,所以main方法要单独放一个文件,否则在eclipseliteide中都会报错;
编译报错如下:(假设test目录中有个main.gomymath.go,其中main.go声明packagemainmymath.go声明packagtest);
$ go install test
can't load package: package test: found packages main (main.go) and test (mymath.go) in /home/wanjm/go/src/test
报错说 不能加载package test(这是命令行的参数),因为发现了两个package,分别时main.gomymath.go;
3.对于main方法,只能在bin目录下运行 go build path_tomain.go; 可以用-o参数指出输出文件名;
4.可以添加参数 go build -gcflags "-N -l" ****,可以更好的便于gdb;详细参见 http://golang.org/doc/gdb
5.gdb全局变量主一点。 如有全局变量 a;则应写为 p 'main.a';注意但引号不可少;

内置类型和函数

内置类型

值类型:

bool
int(32 or 64), int8, int16, int32, int64
uint(32 or 64), uint8(byte), uint16, uint32, uint64
float32, float64
string
complex64, complex128
array    -- 固定长度的数组

引用类型:(指针类型)

slice   -- 序列数组(最常用)
map     -- 映射
chan    -- 管道

内置函数

Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。

    append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
    close           -- 主要用来关闭channel
    delete            -- 从map中删除key对应的value
    panic            -- 停止常规的goroutine  (panicrecover:用来做错误处理)
    recover         -- 允许程序定义goroutine的panic动作
    real            -- 返回complex的实部   (complexreal imag:用于创建和操作复数)
    imag            -- 返回complex的虚部
    make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
    new                -- 用来分配内存,主要用来分配值类型,比如intstruct。返回指向Type的指针
    cap                -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
    copy            -- 用于复制和连接slice,返回复制的数目
    len                -- 来求长度,比如string、array、slice、map、channel ,返回长度
    printprintln     -- 底层打印函数,在部署环境中建议使用 fmt 包

内置接口error

    type error interface { //只要实现了Error()函数,返回值为String的都实现了err接口
​
            Error()    String
​
    }

Init函数和main函数

init函数

go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。

有下面的特征:

    1 init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
​
    2 每个包可以拥有多个init函数
​
    3 包的每个源文件也可以拥有多个init函数
​
    4 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
​
    5 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
​
    6 init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

main函数

    Go语言程序的默认入口函数(主函数):func main()
    函数体用{}一对括号包裹。
​
    func main(){
        //函数体
    }

init函数和main函数的异同:

  • 相同点:两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用

  • 不同点:

    • init可以应用于任意包中,且可以重复定义多个
    • main函数只能用于main包中,且只能定义一个

两个函数的执行顺序:

对同一个go文件的init()调用顺序是从上到下的。

对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数。

对于不同的package,如果不相互依赖的话,按照main包中"先import的后调用"的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。

如果init函数中使用了println()或者print()你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用

命令

go env用于打印Go语言的环境信息。

go run命令可以编译并运行命令源码文件。

go get可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。

go build命令用于编译我们指定的源码文件或代码包以及它们的依赖包。

go install用于编译并安装指定的代码包及它们的依赖包。

go clean命令会删除掉执行其它命令时产生的一些文件和目录。

go doc命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。

go test命令用于对Go语言编写的程序进行测试。

go list命令的作用是列出指定的代码包的信息。

go fix会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。

go vet是一个用于检查Go语言源码中静态错误的简单工具。

go tool pprof命令来交互式的访问概要文件的内容

运算符

算数运算符

+,-,*,/,%

关系运算符

==,!=,>,>=,<,<=

逻辑运算符

运算符描述
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False
ll逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False
!逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True

位运算符

对整数在内存中的二进制位进行操作

运算符描述
&参与运算的两数各对应的二进位相与(两位均为1才为1)
l参与运算的两数各对应的二进位相或(两位有一个为1就为1)
参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1(两位不一样则为1)
<<左移n位就是乘以2的n次方。“a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0
>>右移n位就是除以2的n次方。“a>>b”是把a的各二进位全部右移b位

赋值运算符

=,+=,-=,*=,/=,%=,<<=(左移后赋值),>>=,&=(按位与后赋值),l=(按位或后赋值),^=(按位异或后赋值)

常量和变量

变量

变量声明

变量声明格式为:var 变量名 变量类型

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明

var{
    a string
    b int
    c bool
    d float32
}

变量初始化

标准格式如下:var 变量名 类型 = 表达式

短变量声明

在函数内部,可以使用更简略的 := 方式声明并初始化变量。

package main
​
import (
    "fmt"
)
// 全局变量m
var m = 100func main() {
    n := 10
    m := 200 // 此处声明局部变量m
    fmt.Println(m, n)
}

匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

func foo() (int, string) {
    return 10, "Q1mi"
}
func main() {
    x, _ := foo()
    _, y := foo()
    fmt.Println("x=", x)
    fmt.Println("y=", y)
}

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。 (在Lua等编程语言里,匿名变量也被叫做哑元变量。)

注意事项:

函数外的每个语句都必须以关键字开始(var、const、func等) :=不能使用在函数外 _多用于占位,表示忽略值

常量

声明格式为:const 变量名 变量类型

iota

iotago语言的常量计数器,只能在常量的表达式中使用。 iotaconst关键字出现时将被重置为0const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

举个例子:

    const (
            n1 = iota //0
            n2        //1
            n3        //2
            n4        //3
        )

几个常见的iota示例:

使用_跳过某些值

    const (
            n1 = iota //0
            n2        //1
            _
            n4        //3
        )

iota声明中间插队

    const (
            n1 = iota //0
            n2 = 100  //100
            n3 = iota //2
            n4        //3
        )
    const n5 = iota //0

定义数量级 (这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)

    const (
            _  = iota
            KB = 1 << (10 * iota)
            MB = 1 << (10 * iota)
            GB = 1 << (10 * iota)
            TB = 1 << (10 * iota)
            PB = 1 << (10 * iota)
        )

多个iota定义在一行

    const (
            a, b = iota + 1, iota + 2 //1,2
            c, d                      //2,3
            e, f                      //3,4
        )

基本类型

整型

浮点型

复数

布尔值

默认为false

Go 语言中不允许将整型强制转换为布尔型

布尔型无法参与数值运算,也无法与其他类型进行转换

字符串

字符串转义符

\r回车符(返回行首)
\n换行符(直接跳到下一行的同列位置)
\t制表符
'单引号
"双引号
\反斜杠

多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符

s1 := `第一行
第二行
第三行
`
fmt.Println(s1)

字符串的常用操作

方法介绍
len(str)求长度
+或fmt.Sprintf拼接字符串
strings.Split分割
strings.Contains判断是否包含
strings.HasPrefix,strings.HasSuffix前缀/后缀判断
strings.Index(),strings.LastIndex()子串出现的位置
strings.Join(a[]string, sep string)join操作

byte和rune类型

uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。 rune类型,代表一个 UTF-8字符。

rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成

修改字符串

要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。会重新分配内存,并复制字节数组

类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用

func sqrtDemo() {
    var a, b = 3, 4
    var c int
    // math.Sqrt()接收的参数是float64类型,需要强制转换
    c = int(math.Sqrt(float64(a*a + b*b)))
    fmt.Println(c)
}

数组

数组初始化

数组拷贝和传参

package main
​
import "fmt"func printArr(arr *[5]int) {
    arr[0] = 10
    for i, v := range arr {
        fmt.Println(i, v)
    }
}
​
func main() {
    var arr1 [5]int
    printArr(&arr1)
    fmt.Println(arr1)
    arr2 := [...]int{2, 4, 6, 8, 10}
    printArr(&arr2)
    fmt.Println(arr2)
}

练习

求数组所有元素之和

ackage main
​
import (
    "fmt"
    "math/rand"
    "time"
)
​
// 求元素和
func sumArr(a [10]int) int {
    var sum int = 0
    for i := 0; i < len(a); i++ {
        sum += a[i]
    }
    return sum
}
​
func main() {
    // 若想做一个真正的随机数,要种子
    // seed()种子默认是1
    //rand.Seed(1)
    rand.Seed(time.Now().Unix())
​
    var b [10]int
    for i := 0; i < len(b); i++ {
        // 产生一个01000随机数
        b[i] = rand.Intn(1000)
    }
    sum := sumArr(b)
    fmt.Printf("sum=%d\n", sum)
}

找出数组中和为给定值的两个元素的下标

package main
​
import "fmt"//找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],
// 找出两个元素之和等于8的下标分别是(0,4)和(1,2)// 求元素和,是给定的值
func myTest(a [5]int, target int) {
    // 遍历数组
    for i := 0; i < len(a); i++ {
        other := target - a[i]
        // 继续遍历
        for j := i + 1; j < len(a); j++ {
            if a[j] == other {
                fmt.Printf("(%d,%d)\n", i, j)
            }
        }
    }
}
​
func main() {
    b := [5]int{1, 3, 5, 8, 7}
    myTest(b, 8)
}