跳到内容

GO语言快速上手

Canyue
发布日期:
23 分钟阅读

GO语言是由Google支持的开源编程语言。它易于学习,适合团队使用

准备动作

安装GO环境

下载GO并解压

https://golang.google.cn/dl/

我的操作系统是Linux,Windows有所区别

修改环境变量,这里的 GOROOT 根据实际情况调整

底部添加

export GOROOT=/data/home/canyue/App/go
export GOPATH=/data/home/canyue/go
export PATH=$GOROOT/bin:$GOPATH:$PATH

生效,并验证是否能够现实版本

canyue@workstation-CanYue:~/Desktop$ source /etc/profile
canyue@workstation-CanYue:~/Desktop$ go version
go version go1.17 linux/amd64

安装VSCode插件

解决插件项目TimeOutwent

由于默认源可能无法访问 也就是:proxy.golang.org

使用我们要使用: goproxy.cn

# 控制台执行
go env -w GOPROXY=https://goproxy.cn

修改GO命令行module功能的启用

什么作用先别管,我后面会说

# 控制台执行
go env -w GO111MODULE=auto

Hello World

使用VSCode创建项目

创建空文件夹并使用vsc打开

创建hello.go

点击右下键Install All

直到你看到它

All tools successfully installed. You are ready to Go. :)

至此,所有的工具包都安装成功了

编写代码

打开hello.go,并编辑它

/*定义包名,表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。*/
package main

/*导包,fmt 包实现了格式化 IO(输入/输出)的函数*/
import "fmt"

/*定义函数,main与Java Scala一致,是程序的入口,如果每个名为Init的函数,则最先执行它*/
func main() {     /*{不能单独在一行上*/
    fmt.Println("Hello World!!!") /*与Scala类似*/
}

如何跑起来

1.使用VSCode

可以使用快捷键 Ctrl + F5 以非调试模式运行

也可以在这里找到 【运行】 - 【以非调试模式运行】

2.使用命令行编译后运行

在hello.go文件所在目录下执行

go build hello.go

这将生成一个可执行文件,Windows就是.exe,Linux就是一个无后缀的二进制文件

一些理论

保留字

breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

自带标识符

appendboolbytecapclosecomplexcomplex64complex128uint16
copyfalsefloat32float64imagintint8int16uint32
int32int64iotalenmakenewnilpanicuint64
printprintlnrealrecoverstringtrueuintuint8uintptr

GO语言中,变量,函数,类等等程序实体不允许与自带标识符同名

数据类型

部分来源菜鸟教程https://m.runoob.com/go/go-data-types.html

序号类型和描述
1布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4派生类型: 包括: (a) 指针类型(Pointer)(b) 数组类型 (c) 结构化类型(struct) (d) Channel 类型 (e) 函数类型 (f) 切片类型 (g) 接口类型(interface) (h) Map 类型

数字类型

Go 也有基于架构的类型,例如:int、uint 和 uintptr。

序号类型和描述
1uint8 无符号 8 位整型 (0 到 255)
2uint16 无符号 16 位整型 (0 到 65535)
3uint32 无符号 32 位整型 (0 到 4294967295)
4uint64 无符号 64 位整型 (0 到 18446744073709551615)
5int8 有符号 8 位整型 (-128 到 127)
6int16 有符号 16 位整型 (-32768 到 32767)
7int32 有符号 32 位整型 (-2147483648 到 2147483647)
8int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

浮点型

序号类型和描述
1float32 IEEE-754 32位浮点型数
2float64 IEEE-754 64位浮点型数
3complex64 32 位实数和虚数
4complex128 64 位实数和虚数

其他数字类型

以下列出了其他更多的数字类型:

序号类型和描述
1byte 类似 uint8
2rune 类似 int32
3uint 32 或 64 位
4int 与 uint 一样大小
5uintptr 无符号整型,用于存放一个指针

字符

类型字节描述
byte/utf81单个UTF-8字符
rune4单个unicode字符

其他

语法

注释

// 单行注释
/* 多行注释 123 456 */

变量

声明一个变量

var a float32 = 12 + 13  //var 变量名 类型 [= 表达式]  或 var 变量名 = 表达式   (将自动推断类型)

批量声明

var (
    a int = 12
    b string
    c float32
    d float64
    e [5]int //数组,后面说
)

//还可以

var a,b,c = 10,20,"abc"
var e,f,g int

简短的声明方法(又叫初始化声明)

a := 10 + 13  //将被自动推断类型,只能用于函数体内(局部变量)

为什么叫初始化声明,因为如果该变量被声明过,则会抛出异常

var a = 0
a := 10
no new variables on left side of :=

局部变量必须被使用

func main() {
    var a = 10
}
a declared but not used

匿名变量

有的函数会返回多个值

func test() (int,int){
    return 1,2
}

在GO中,你可以使用匿名变量接受它,然后不使用,匿名函数是不消耗内存的,这样就相当于丢弃了其中一个返回值,而不需要消耗额外的资源

a, _ := test()   //丢弃1
_, b := test()   //丢弃2

GO语言的多重赋值

之前要交换a,b值我们要使用中间变量

var a = 10
var b = 20
var tmp = a
a = b
b = tmp

或是使用算法

var a = 10
var b = 20
a = a ^ b
b = b ^ a
a = a ^ b

在GO语言,你这些都不要,你只需要

var a = 10
var b = 20
a, b = b, a

常量

声明(联系变量)

const a = 10
const b int64
const c,d,e = 10,20,"abc"
const (
    a = 10
    b int    
)

常量的枚举

func main() {
    const (
        a = 10
        b = "c会学我"
        c
    )
    fmt.Println(a, b, c)
}
10 c会学我 c会学我

以这种方式定义的常量,若无初始值和类型,则会与上一个非空的常量值相同

iota常量

iota常量初始值为了,每出现一个const常量,iota常量会被编译器自增1(但程序无法修改它)

func main() {
    const (
        a = iota
        b = iota
        c = "不一定要赋值才会自增"
        d = iota
    )
    fmt.Println(a, b, c, d)
}
0 1 不一定要赋值才会自增 3

iota只能在常量声明时出现,不能直接用,例如放在Println方法内

类型别名与定义新的类型定义

取一个类型别名

别名就像一个外号,都是同一个人

type String = string
    var a string = "aa"
    var b String = "bb"

定义个新类

再来一个新的一个人,只不过这两个一样

type String string
    var a string = "aa"
    var b String = "bb"

在Go 1.9之前,没有别名,定义新类的方法是 type 新类型名 = 类型名

与现在取别名一致

类型别名在编译后将去除,因为别名与原名是同一个东西,GO只会保留原先的

别名主要作用还是用于解决程序兼容性的问题

运算符

恕我 + - * / == >= <= != < > | || & && !>> << 忽略 这些与其他语言一致,将写不常见的

若 a 1;b 2

运算符描述实例
++自增a ++ 结果 2
自减a — 结果 0
+=相加后赋值
-=相减后赋值
*=相乘后赋值
/=相除后赋值
<<= \>>=位移后赋值
&=按位与后赋值
^=按位异或后赋值
! = (连起来)按位或后赋值
&也可是输出存储地址&a
*也可是表指针变量*a

优先级(由上往下,从大到小)

运算符
^ !
/ * % << >> & &^
+ - \
== != <<= >>= >= <= 以及其他xx后赋值
<-
&&
\

控制流程

IF语句(不多赘述)

func main() {
    if false {
        if true {
            /*代码*/
        }
    } else if 1+1 == 3 {
        fmt.Println("不可能,绝对不可能")
    } else {
        /*代码*/
    }
}

SWITCH语句(不多赘述)

const score = 90
var msg string
switch { //相当于  switch true  你也可以使用switch xxx csae 1 这种
case score >= 90:
    msg = "优秀"
case score >= 75 && score < 90:
    msg = "良好"
case score >= 60 && score < 75:
    msg = "及格"
case score < 60:
    msg = "不及格"
default: //都不满足
    msg = "无数据"
}

SELECT语句

类似SWITCH语句,不一样的是SELECT会随机执行一个满足条件的case,如果一个都没有,就会将其堵塞,直到有满足条件的

func main() {
    ch1 := make(chan int)
    ch2 := make(chan string)

    go func() {
        ch1 <- 100
    }()

    go func() {
        num2 := <-ch2
        fmt.Println(num2)
    }()

    select {
    case num1 := <-ch1: //读取channel数据
        fmt.Println("ch1中的数据是:", num1)
    case ch2 <- "201": //channel写入数据
        fmt.Println("ch2有数据写入")
    }
}
201
ch2有数据写入

循环

go语言没有while循环

for循环

语法类似JS

func main() {
    for i := 1; i <= 9; i++ {
        for j := 1; j <= i; j++ {
            fmt.Printf(" %d * %d = %d", j, i, j*i)
        }
        fmt.Println()
    }
}
 1 * 1 = 1
 1 * 2 = 2 2 * 2 = 4
 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
 1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
 1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
 1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
 1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
 1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
 1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81

遍历我后面讲

循环控制语句

break语句

结束循环(不多赘述)

continue

跳过这圈循环(不多赘述)

goto

无条件跳转到某一标签所在行

(CMD有类似的写法)

func main() {
    var num = 0
A:   //标签
    fmt.Println("位置A")
    if num == 3 {
        goto B   //无条件跳转到标签A所在行
    }
    num++
    goto A
B:
    fmt.Println("跳转到B")
}
位置A
位置A
位置A
位置A
跳转到B

函数

定义函数

//func 函数名 (参数列表) (返回值列表) {}    || 若相邻参数数据类型一致,只需在最后一个指定类型即可
func myFunc (a,b int, c ... string) (int,string){    //c为不定长参数
    return a + b , ""    //不像Python返回一个列表,这是真真切切的两个值
}

其实在GO语言中,函数也是一种类型,可以被保存在变量中(有点像Scala,只不过Scala函数不是类型)

返回值列表不仅仅可以放返回数据类型,还可以带上名称

这时候返回值就是一个局部变量,最后只需一个return就可以自动将变量值返回

func main() {
    fmt.Println(mySum(3, 4))
}

func mySum(num1 int, num2 int) (sum int) {
    sum = num1 + num2
    return
}
7

作用域

函数内定义的变量叫局部变量,局部变量仅作用与该函数题内,生命周期与该函数相同

函数外定义的叫全局变量,全局变量作用与整个包甚至外部包吗,所以之前与main函数相同

GO允许局部变量与某一全局变量同名,在局部变量所在的函数体内,局部变量优先级高于全局变量

意思作用于对常量,类型,函数皆适用

匿名函数

func main() {
    f := func(msg string) {
        fmt.Println(msg)
    }
    f("Hello World")
}
Hello World

也可以这样用

func main() {
    func(msg string) {
        fmt.Println(msg)
    }("Hello World")
}
Hello World

个人觉得没有Scala灵活

闭包

闭包严格意义上不算是函数,函数内部可执行的代码在函数定义时就被实现,且不会随程序的运行额发生改变,且一个函数通常只有一个实例

闭包在运行是可以有多个实例,不同的引用环境会生产多个实例

闭包在有些变成语言里面叫lambda表达式(熟悉了吧)

函数本身不具有记忆性,而与环境结合的闭包才具有记忆性(下面有实验)

函数是编译器静态的概念,闭包是运行期间的动态的概念

也不是所有的变成语言都支持闭包,闭包需依赖语言的以下特性

对比实验(记忆性)

无闭包

func main() {
    for i := 1; i <= 5; i++ {
        fmt.Println(myAdd(i))
    }
}

func myAdd(num int) int {
    sum := 0
    sum += num
    return sum
}
1
2
3
4
5

闭包

func main() {
    f := myAdd()
    for i := 1; i <= 5; i++ {
        fmt.Println(f(i)) //每个小循环又是一个实例
    }
}

func myAdd() func(int) int { //分开看  返回一个函数
    sum := 0
    return func(num int) int { //返回一个匿名函数
        sum += num
        return sum
    }
}
1
3
6
10
15

可变参数

可变参数及可接受不确定的参数数量,当一个函数参数数量不确定,但确定参数的数据类型时,就可以使用可变参数

可变参数在GO中使用 ... 表示

可变参数必须写在最后,且一个函数只能有一个可变参数

func main() {
    sum := mySum(10, 12, 15)
    fmt.Println(sum)
}

func mySum(num ...int) (sum int) {
    for _, n := range num { //for index,value := range num  遍历我后面讲
        sum += n
    }
    return
}
37

递归

递归,就是函数内部调用函数本身

func main() {
    fmt.Println(factorial(3))
}

func factorial(n int) (int) {
    if n == 0 {
        return 1
    }
    return n * factorial(n-1)
}
6

指针

一个保存了 另一个变量 的 内存地址 的 变量

声明指针

与声明变量极为类似

var a *int   //在数据类型前加*,数据类型是被指向变量的数据类型

如何给指针赋值

还记得前面提到的运算符中&符号还可以表示返回某个元素的内存地址吗??

var a *int   //在数据类型前加*,数据类型是被指向变量的数据类型
var num = 12
a = &num
//结合以往知识点,指针还可以这样声明(作用域:局部)
a1 := &num

空指针

但一个指针被声明但未被赋值,就是空指针

空指针值为nil

使用指针

我前面也提到过,*指针名可以表示指针指向的变量

func main() {
    num := 10
    a := &num
    *a = 12   //*指针名表示指针指向的变量,间接修改了变量
    fmt.Println(*a)   //指针也可作为参数
}
12

指针的指针

一个指针指向一个指向元素的指针

在使用的时候需要多个*

例如

func main() {
    num := 10
    a := &num
    b := &a
    **b = 12 //分开看 * *b | **b ==> *a ==> num
    fmt.Println(**b)
}
12

指针的指针的指针同理,理解就行,我就不套娃了

浅拷贝与深拷贝

指针赋值给别人叫拷贝

浅拷贝只复制了一份指向自己内存地址的指针,所以在修改副本的时候会影响真身

类型赋值给别人叫拷贝

深拷贝值将内容复制给了一个新的内存位置,所以在修改副本时不改变真身

容器(集合)

数组

语法

var 数组名 [数组长度] 数据类型 {[初始元素]}    //一维简化
var 数组名 [维度1长度][ [维度2长度] ... ] 数据类型 { {[维度1初始元素]} , [{[维度2初始元素]}] ... }

数组长度的方括号不可省略

若不指定数据长度,可省略不写,GO将按照初始元素的数量指定数组长度,这种声明方法在Go中称为切片

可以将数组长度写成...效果同上,只不过这次声明的是数组

长度

长度使用只带len()函数获取

func main() {
    a := []int{1, 2, 3}
    fmt.Println(len(a))
}
3

数组中 长度与容量相同,使用GO语言有个cap()函数,在数组中,与len()结果相同,具体在下方遍历章节有实验

访问

可以使用[index]访问

func main() {
    a := []int{1, 2, 3}
    fmt.Println(a[1])
    b := [2][3]int{{1, 3}, {5, 7, 9}}
    fmt.Println(b[1][1])
}
2
7

切片

GO中,通过声明一个未指定长度的数组来定义切片

虽然不定义长度,但容量还是有限

GO不支持负数下标

func main() {
    a := []int{1, 2, 3, 4, 5}
    fmt.Println(a[:])   //所有元素
    fmt.Println(a[1:])  //下标为1到最后一个元素
    fmt.Println(a[:3])  //起始元素到下标为3的元素(不包含)
    fmt.Println(a[1:3]) //下标为1到下标为3的元素(不包含)
}
[1 2 3 4 5]
[2 3 4 5]
[1 2 3]
[2 3]

返回的切片不是数组,它只是数组的一种引用,对切片的操作会映射到原数组上

遍历

func main() {
    a := []int{1, 2, 3}
    //方法1
    for i := 0; i < len(a); i++ {
        fmt.Print(a[i], " ")
    }
    fmt.Println("\n---------")
    //方法2
    //indexd 或 value 可使用匿名变量丢弃 常写作 for _, value := range xxx
    for index, value := range a {
        fmt.Print("i:", index, ";v:", value, " ")
    }
    fmt.Println("\n---------")

    //多维(上面两种方法都行)
    c := [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8}, //需要,   //这里我故意留一个空,使得数组的一个元素每个初始值
    }
    for _, v1 := range c {
        for _, v2 := range v1 {
            fmt.Print(v2, " ")
        }
    }
}
1 2 3 ---------
i:0;v:1 i:1;v:2 i:2;v:3 ---------
1 2 3 4 5 6 7 8 0     //使用int类型的默认值0填充

该语法同样适用于其他集合,包括字符串

修改数组

添加元素

append()方法会返回一个在原切片的基础上追加了元素的切片

使用append()方法向切片末尾追加一个或多个元素

这里为什么是切片,结合之前的实验,以及数组特性,数组是定长的,数组空值会自动按数据类型默认值补齐,所以,理论上无法追加!!!

func main() {
    a := []int{1, 2, 3, 4, 5}
    a = append(a, 6)           //追加元素
    a = append(a, 7, 8, 9, 10) //追加多个元素
    fmt.Println(a)
}
[1 2 3 4 5 6 7 8 9 10]

删除元素

这里吐槽GO,切片的删除没有提供方法实现。切片元素的删除我分为两类

  1. 掐头去尾

删除头尾可以直接使用下标进行切片操作

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    a = a[2:]           //去前两个
    a = a[:len(a)-2]    //去后两个
    a = a[2 : len(a)-2] //头尾都去两个
    fmt.Println(a)
}
[5]

  1. 去任意某个

这就有点特殊,在此之前,先拓展一个方法 ...,语法:

数组或切片...

…在GO语言中的地位相当于Scala的_,是该语言的语法糖

在GO语言中,...有两个意思,一为不定长,这在函数定义上提到过。二为打散某个集合再传递

GO语言无法直接拼接两个切片,append()方法添加多个元素必须传递在其不定长参数上,怎么办,只能打散再传递。

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    a = append(a[:5], a[5+1:]...) //去下标5
    a = append(a[:2], a[4+1:]...) //再去下标234
    fmt.Println(a)
}
[1 2 7 8 9]

事实证明,GO语言没Scala好用

复制元素

copy()方法可以将一个切片的元素复制到另一个切片,由于上面提到的原因,所以数组还是不能使用copy()方法

copy()方法复制元素遵循如下规则

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    b := []int{11, 12, 13}
    count := copy(b, a)    //coty方法将返回复制的元素数量   //注意方向,这个写法是a->b
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(count)
}
[1 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9 4 4 4]
9

MAP

哈希表,键值对,数据结构我不多叙述

语法

var 变量名 = map [key类型] value类型 {[k1:v1, k2:v2 ....]}              //不初始化,值为nil

var 变量名 = make (map [key类型] value类型)                             //不初始化,值非nil

[key类型] 不代表可省略,而是key类型需放在[]中

赋值

func main() {
    a := map[string]int{
        "李二狗": 12,
        "张三猫": 13,     //不可省略,
    }
    a["李四牛"] = 15
    fmt.Println(a)
}
map[张三猫:13 李二狗:12 李四牛:15]

删除

删除可以使用delete()方法

func main() {
    a := map[string]int{
        "李二狗": 12,
        "张三猫": 13, //不可省略,
    }
    a["李四牛"] = 15
    delete(a, "李四牛") //只能一个个来
    fmt.Println(a)
}
map[张三猫:13 李二狗:12]

查询

func main() {
    a := map[string]int{
        "李二狗": 12,
        "张三猫": 13, //不可省略,
    }
    a["李四牛"] = 15
    fmt.Println(a["李四牛"])
    fmt.Println(a["王五虎"])
}

查不到将返回该值类型的默认值

15
0

遍历

func main() {
    a := map[string]int{
        "李二狗": 12,
        "张三猫": 13,      //不可省略,
    }
    a["李四牛"] = 15
    for k, v := range a { //v可省略
        fmt.Println(k, v)
    }
}
李二狗 12
张三猫 13
李四牛 15

常用包

详见https://studygolang.com/pkgdoc

来源https://github.com/polaris1119/pkgdoc/

面向对象

准确来说,Go语言没有类的概念,不过却有struct(结构体)和interface(接口)

结构体(Scala样例类阉割版)

单一数据类型已无法满足现实的开发需求,于是GO语言提供了结构体来单一复杂的数据类型

结构体由相同或不同类型的数据结构组成

使用结构体需注意

语法

type Cat struct { //定义结构体
    name, color string
    age         int
}

func main() {
    //实例化方法1:
    var huahua = Cat{name: "花花", color: "三花", age: 5} //不一定要都指定,这和Scala不同
    fmt.Println(huahua.name)                          //调用参数
    //实例化方法2:后面再给参数赋值
    var mimi Cat //注意没有等号   等同于 mimi := Cat{}
    mimi.name = "咪咪"
    fmt.Println(mimi.name)
    fmt.Println(mimi.age) //为赋值的参数值为该类型的初始值
}
花花
咪咪
0

匿名结构体与匿名字段

func main() {
    res := struct {     //无需type
        a int           //参数
        float32            //匿名参数(会使用类型名作为参数名,一个结构体内一个类型只能有一个匿名参数)
    }{12,0.12}          //初始化参数值
}

嵌套(聚合和继承)

Go貌似没有传统意义上的继承

不过我们可以使用嵌套实现,在一个结构体内嵌套一个结构体,被嵌套的结构体与外层结构体的关系:

GO语言其实没有继承,所以无法实现其他语言的许多操作

例如不能给蓝猫结构体某个参数初始值

//父类
type Cat struct {
    name string
    age  int
}

//子类
type BlueCat struct {
    Cat   //匿名就行
    color string
}

func main() {
    //实例1
    tom := BlueCat{Cat{"汤姆", 5}, "蓝"}
    //实例2
    blue := BlueCat{}
    blue.name = "布鲁"
    blue.age = 6
    blue.color = "蓝"
}

方法

方法本质上是函数,只不过方法与函数在逻辑上不同

函数属于包,被包调用;方法属于某一结构体,通过结构体变量类调用

方法在定义的时候语法与函数类似,不过要指定属于那个结构体

语法

func (变量名 结构体名) 方法名 (参数列表) 返回值列表 {
    方法体
}

GO语言不支持类,方法是为了实现一些类似于类的功能

同名的方法可以在多个结构体上定义,所以以函数不同,方法是可以重名的

样例

type Cat struct {
    name string
    age  int
}

//实现一个方法,这时候相当于有个Cat类,类里面有个方法叫printInFO()
func (cat Cat) printInFo() {
    fmt.Printf("%s几年%d岁了", cat.name, cat.age)
}

func main() {
    //实例化一个结构体
    tom := Cat{"Tom", 5}
    tom.printInFo()
}
Tom几年5岁了

继承

方法会随着结构体的’继承’而被继承

即其中一匿名字段实现了一个方法,那这个方法就会被继承到该结构体

type Cat struct {
    name string
    age  int
}

//“继承”
type BlueCat struct {
    Cat
}

//实现一个方法,这时候相当于有个Cat类,类里面有个方法叫printInFO()
func (cat Cat) printInFo() {
    fmt.Printf("%s几年%d岁了", cat.name, cat.age)
}

func main() {
    //实例化一个结构体
    tom := BlueCat{Cat{"Tom", 6}}
    tom.printInFo()
}
Tom几年6岁了

重写

若一结构体内的一匿名字段实现了一个方法,但结构体也实现了一个同名方法,那这方法就会被重写

type Cat struct {
    name string
    age  int
}

//“继承”
type BlueCat struct {
    Cat
}

//实现一个方法,这时候相当于有个Cat类,类里面有个方法叫printInFO()
func (cat Cat) printInFo() {
    fmt.Printf("%s几年%d岁了", cat.name, cat.age)
}

//BlueCat再实现一个同名方法
func (cat BlueCat) printInFo() {
    fmt.Printf("%s几年%d岁了,它是一中蓝色的猫", cat.name, cat.age)
}

func main() {
    //实例化一个结构体
    tom := BlueCat{Cat{"Tom", 6}}
    tom.printInFo()
}
Tom几年6岁了,它是一中蓝色的猫

接口

在GO语言中,任何定义了接口中所有方法的结构体就自动隐式实现了接口

语法

type 接口名 interface {
    方法1([参数列表]) [返回值]
    方法2([参数列表]) [返回值]
    .....
}

样例

type Cat interface {
    printInFo()
}

//“继承”
type BlueCat struct {
    name string
    age  int
}

//BlueCat再实现一个同名方法
func (cat BlueCat) printInFo() {
    fmt.Printf("%s几年%d岁了,它是一中蓝色的猫", cat.name, cat.age)
}

func main() {
    //实例化一个结构体
    tom := BlueCat{"Tom", 7}
    tom.printInFo()
}

空接口

空接口中没有任何方法,任何结构体都可以实现该接口,空接口类似以于Java以及Scala的object

异常

error

error错误,程序出现了不正常的情况,一般用于自定义一个函数或方法可能发生的异常,将其返回

error在GO语言中本质是一个接口,其中包含Error()方法,错误值存储在变量中,最后通过该发放返回,返回的错误必须是某个函数或方法的最后一个返回值

在GO语言中,通常判断函数或方法是否发生了错误,可以对比该方法或函数返回值的最后一个值是不是nil,如果是,则未发生错误

import (
    "errors"
    "fmt"
)

func myMath(num1 float32, num2 float32) (float32, error) {
    if num2 == 0 {
        return 0, errors.New("除数为0")
    } else {
        return num1 / num2, nil
    }
}

func main() {
    res, err := myMath(1, 0)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println(res)
    }
}

panic

恐慌,程序出现了无法处理的灾难级的错误,通常程序在遇到无法处理的问题且没被recover拦截,将抛出painc

panic()是一个内建函数,当程序运行到这时,程序将被中断运行

与error不同,panic通常用于报告致命错误,例如在数组越界时

func myFunc(n int) { //放在函数,骗过编译器
    a := [3]int{}
    a[n] = 12
}

func main() {
    myFunc(10)
}

GO语言就会抛出一个panic而不是error

panic: runtime error: index out of range [10] with length 3

goroutine 1 [running]:
main.myFunc(0xa)
    /home/canyue/Desktop/GOProject/hello.go:6 +0x52
main.main()
    /home/canyue/Desktop/GOProject/hello.go:10 +0x1e

对比error返回的消息,panic抛出了错误将更为详细,那是因为panic的工作流程有关

当一行代码发生panic,GO会立即停止该行代码,然后将控制权转移到所在的函数或方法,然后该函数或方法内的代码将立即停止,控制权立即转移到其调用者以及上级调用者,直到最初调用。在此期间,panic会搜集详细消息,最终抛出

panic本质是一个内建函数,使用我们其实可以随时调用panic使得一个程序陷入恐慌,然后停止运行

func myFunc(n int) { //放在函数,骗过编译器
    fmt.Println("hello")
    panic("手动出发恐慌")
    fmt.Println("程序不在执行到此") //VSC提示   unreachable code  遥不可及的,不会执行到这
}

func main() {
    myFunc(10)
}
DAP server listening at: 127.0.0.1:36413
hello
panic: 手动出发恐慌

goroutine 1 [running]:
main.myFunc(0xa)
    /home/canyue/Desktop/GOProject/hello.go:8 +0x7e
main.main()
    /home/canyue/Desktop/GOProject/hello.go:13 +0x1e

defer

defer用于延迟一个函数或方法,具体为如果一各函数有多个defer语句,GO会先执行没有带defer分语句,直到最后运行到最后一个defer语句时,再逆序执行所有带defer的语句

func myFunc() {
    defer fmt.Println("1")
    fmt.Println("2")
    defer fmt.Println("3")
    defer fmt.Println("4")
}

func main() {
    myFunc()
}
2
4
3
1

虽然defer语句是运行到最后一句在一块运行的,不过,如果defer语句中有传参,那参数则是事先在到达该句时先赋值

func myFunc(num int) {
    println(num)
}

func main() {
    n := 1
    defer myFunc(n)
    n++
    myFunc(n)
    n++
    defer myFunc(n)
}
2
3
1

异常处理

GO语言与Java、Scala的异常处理上有很大的不同

因为GO语言的作者在觉得,将异常控制流程混在一起(try…catch…finally)会使代码变得混乱

在上面提到了程序在发生panic时会导致程序恐慌奔溃,其实我们可以拦截panic,具体如下

recover

recover()本质是一个内建函数,在执行它时,会捕获panic的异常消息,并让程序正常运行(出现panic的函数会方法除外),如果程序正常运行,则返回nil

不过要注意,recover只能在defer延迟语句内执行才会生效,且放在可能会抛出panic的代码执行顺序之后

func myFunc() { //放在函数,骗过编译器
    defer func() { //defer 用于延迟一个函数或方法,通常这会写一个匿名函数
        msg := recover()
        if msg != nil {
            fmt.Printf("发生paincm,消息:%s", msg)
        }
    }()
    /*除数5到0,0会抛出异常*/
    for i := 5; i >= -5; i-- {
        fmt.Println(10 / i)
    }
}

func main() {
    myFunc()
}
2
2
3
5
10
发生paincm,消息:runtime error: integer divide by zero

导致panic的函数或方法将不会继续执行

IO

键盘输入

类似Python的input

只需向Scanln()方法传递若干个指针,即可实现

func main() {
    name := ""
    age := 0
    fmt.Scanln(&name, &age)
    fmt.Printf("%s几年%d岁了\n", name, age)
}
canyue@workstation-CanYue:~/Desktop/GOProject$ ./hello
小明 12
小明几年12岁了

每个输入之间使用[空格]隔开

VSC的非调试运行会出问题

文件系统

在文件系统上的IO操作

查询文件信息

import (
    "fmt"
    "os"
)

func main() {
    //字符串表示路径
    path := "./hello.sol" //支持相对、绝对路径
    //Go语言中,文件或目录使用fileStat类型表示
    stat, error := os.Stat(path) //通过os包中的stat返回两个数据,一是文件的消息,二是错误(前面提到过)
    if error == nil {
        fmt.Printf("数据类型: %T \n", stat)
        fmt.Printf("文件名:%s \n", stat.Name())
        fmt.Printf("是否是一个目录:%t \n", stat.IsDir())
        fmt.Printf("文件权限:%s \n", stat.Mode())
        fmt.Printf("文件最后修改时间:%s \n", stat.ModTime())
        fmt.Printf("文件大小:%d bit\n", stat.Size())
    }
}
数据类型: *os.fileStat 
文件名:hello.sol 
是否是一个目录:false 
文件权限:-rw-r--r-- 
文件最后修改时间:2022-08-19 14:28:06.674944276 +0800 CST 
文件大小:167 bit

创建与删除

func main() {
    //创建
    err := os.Mkdir("./test", os.ModeDir) //创建文件夹
    if err == nil {
        fmt.Println("文件夹创建成功")
        //递归生成文件夹
        err2 := os.MkdirAll("canyue/go/test", os.ModePerm)
        if err2 == nil {
            fmt.Println("递归生成成功")
        } //递归删除不会因为已存在而抛出异常
        //创建文件
        file, err3 := os.Create("./test/canyue.txt") //返回该文件以及错误,已存在文件将覆盖
        if err3 == nil {
            fmt.Printf("文件创建成功,内存地址:%v", file)
        }
    } else {
        fmt.Println("文件夹已存在") //已存在将抛出错误
    }
    //删除
    os.Remove("./test/canyue.txt")
    os.Remove("./test") //只能删除一个空目录
    //递归删除
    os.RemoveAll("./canyue")
}

文件读写

OS包实现

文件打开方式: os.关键字

关键字模式
O_RDONLY只读(read-only)
O_WRONLY只写(write-only)
O_RDWR可读可写(read-write)
O_APPEND追加写
O_CREATE文件不存在时创建该文件

打开方式之间可以使用 | 分隔,代表同时使用多个模式

通常与O_CREATE同时使用

func main() {
    file, err := os.OpenFile("./canyue.txt", os.O_RDWR|os.O_CREATE, os.ModePerm) //打开文件
    if err == nil {
        defer file.Close() //关闭文件,通常使用defer语句
        fmt.Println("文件打开成功")
        //写入文件
        n, err2 := file.Write([]byte("你好\n我是残月"))
        if err2 == nil {
            fmt.Println("文件写入成功,写入字节", n)
        }
    } else {
        fmt.Println("文件打开错误")
    }
}
func main() {
    //OS包实现
    file, err := os.OpenFile("./canyue.txt", os.O_RDWR|os.O_CREATE, os.ModePerm) //打开文件
    if err == nil {
        //读取文件
        buf := make([]byte, 512) //创建一个512字节的缓冲区
        for {                    //一次读一行
            n, err3 := file.Read(buf) //字节数,错误
            if n == 0 || err3 != nil {
                fmt.Println("打印完成")
                break
            }
            fmt.Println(string(buf[:n])) //将缓冲区所有数据装换为字符串
        }
    } else {
        fmt.Println("文件打开错误")
    }
}
你好
我是残月
打印完成

ioutil包实现

func main() {
    //打开刚刚的文件
    data1, err1 := ioutil.ReadFile("./canyue.txt")
    if err1 == nil {
        fmt.Println(string(data1), "\n------------")
    } else {
        fmt.Println("读取canyue.txt失败", err1)
    }
    //无法读取不存在的文件
    data2, err2 := ioutil.ReadFile("./canyue.md")
    if err2 == nil {
        fmt.Println(string(data2), "\n------------")
    } else {
        fmt.Println("读取canyue.md失败", err2)
    }
    //写文件 (追加写入,不存在将会创建文件)
    writeText01 := []byte("文件001") //传入的是字节类型的数组
    err := ioutil.WriteFile("./test/001.txt", writeText01, os.ModePerm) == nil
    writeText02 := []byte("文件002")
    err = ioutil.WriteFile("./test/002.txt", writeText02, os.ModePerm) == nil && err
    writeText03 := []byte("文件003")
    err = ioutil.WriteFile("./test/003.txt", writeText03, os.ModePerm) == nil && err
    if err {
        fmt.Println("文件写入成功")
    } else {
        fmt.Println("有文件写入失败")
    }

}
你好
我是残月 ------------
读取canyue.md失败 open ./canyue.md: no such file or directory
文件写入成功

原生网络编程

常见HTTP代码

https://http.cat/

RESTful规范

https://blog.csdn.net/qq_26460841/article/details/119977022

一个Http测试网站

http://httpbin.org

JSON字符串转Map

func main() {
    //原数据
    originData := `[{"name":"小明","age":"12","sex":"男"}]` //`` 不转译字符串
    //解析后的数据
    var data []map[string]string
    //解析
    json.Unmarshal([]byte(originData), &data)
    fmt.Println(data)
}
[map[age:12 name:小明 sex:男]]

HTTP客户端

GO语言原生内置HTTP包net/http

包内提供了最简介的HTTP客户端实现方式

简单演示

func main() {
    httpRequest("GET", "http://httpbin.org/get")
}

//http请求
func httpRequest(method string, url string) {
    //创建客户端
    client := http.Client{}
    //创建请求
    request, err := http.NewRequest(method, url, nil)
    checkError(err)
    //添加Cookie
    cookieName := &http.Cookie{Name: "n1", Value: "v1"}
    request.AddCookie(cookieName)
    //设置请求头
    request.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0")
    //发送Do请求
    response, err := client.Do(request)
    checkError(err)
    defer response.Body.Close()
    //查看请求头数据
    fmt.Println("Header:", request.Header)
    //查看状态
    fmt.Println("HTTPCode:", response.StatusCode)
    fmt.Println("HTTPState:", response.Status)
    //操作数据
    if response.StatusCode == 200 {
        data, err := ioutil.ReadAll(response.Body)
        checkError(err)
        fmt.Println(string(data))
    } else {
        fmt.Println("访问异常:", response.Status)
    }
}

//检查错误
func checkError(err error) {
    if err != nil {
        panic(err)
    }
}
Header: map[Cookie:[n1=v1] User-Agent:[Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0]]
HTTPCode: 200
HTTPState: 200 OK
{
  "args": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Cookie": "n1=v1", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0", 
    "X-Amzn-Trace-Id": "Root=1-630478c3-3b53d2801c6f0b190dab7480"
  }, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "http://httpbin.org/get"
}

Get请求

client.Get()

func main() {
    httpRequest("http://httpbin.org/get?param1=12&param2=13")
}

//http请求
func httpRequest(url string) {
    //创建客户端
    client := http.Client{}
    //发送请求
    request, err := client.Get(url)
    checkError(err)
    defer request.Body.Close()
    //处理返回
    if request.StatusCode == 200 {
        data, err := ioutil.ReadAll(request.Body)
        checkError(err)
        fmt.Println(string(data))
    } else {
        fmt.Println(request.Status)
    }

}

//检查错误
func checkError(err error) {
    if err != nil {
        panic(err)
    }
}
{
  "args": {
    "param1": "12", 
    "param2": "13"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-63047a79-1bcdea4942031af9214acd15"
  }, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "http://httpbin.org/get?param1=12,param2=13"
}

http.Get()

两者区别在,后者无需创建一个客户端,直接发送请求

func main() {
    httpRequest("http://httpbin.org/get?param1=12&param2=13")
}

//http请求
func httpRequest(url string) {
    request, err := http.Get(url)
    checkError(err)
    //处理返回
    if request.StatusCode == 200 {
        data, err := ioutil.ReadAll(request.Body)
        checkError(err)
        fmt.Println(string(data))
    } else {
        fmt.Println(request.Status)
    }

}

//检查错误
func checkError(err error) {
    if err != nil {
        panic(err)
    }
}
{
  "args": {
    "param1": "12", 
    "param2": "13"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-63047b82-70e59d7d44c5bfe337698d64"
  }, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "http://httpbin.org/get?param1=12&param2=13"
}

Post请求

client.Post()

func main() {
    params := map[string]string{
        "name": "小明",
        "sex":  "男",
    }
    httpRequest("http://httpbin.org/post", params)
}

//http请求
func httpRequest(u string, params map[string]string) {
    //创建客户端
    client := http.Client{}
    //处理请求参数
    data := url.Values{}
    for k, v := range params {
        data.Add(k, v)
    }
    requestData := strings.NewReader(data.Encode())
    //发送请求
    request, err := client.Post(
        u,
        "application/x-www-form-urlencoded",
        requestData,
    )
    checkError(err)
    //处理返回
    if request.StatusCode == 200 {
        data, err := ioutil.ReadAll(request.Body)
        checkError(err)
        fmt.Println(string(data))
    } else {
        fmt.Println(request.Status)
    }
}
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "\u5c0f\u660e", 
    "sex": "\u7537"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "37", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-63047f1a-0523d8e45f53e8c311926ecd"
  }, 
  "json": null, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "http://httpbin.org/post"
}

http.Post()

func main() {
    params := map[string]string{
        "name": "小明",
        "sex":  "男",
    }
    httpRequest("http://httpbin.org/post", params)
}

//http请求
func httpRequest(u string, params map[string]string) {
    //处理请求参数
    data := url.Values{}
    for k, v := range params {
        data.Add(k, v)
    }
    requestData := strings.NewReader(data.Encode())
    //发送请求
    request, err := http.Post(
        u,                                          //url
        "application/x-www-form-urlencoded",        //第二个参数必须设置成"application/x-www-form-urlencoded",否则post参数无法传递
        requestData,
    )
    checkError(err)
    //处理返回
    if request.StatusCode == 200 {
        data, err := ioutil.ReadAll(request.Body)
        checkError(err)
        fmt.Println(string(data))
    } else {
        fmt.Println(request.Status)
    }
}
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "\u5c0f\u660e", 
    "sex": "\u7537"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "37", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Go-http-client/1.1", 
    "X-Amzn-Trace-Id": "Root=1-63047f69-1d941f48748d9bf60bceacc7"
  }, 
  "json": null, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "http://httpbin.org/post"
}
上一篇
MySQL中的NULL
下一篇
Scala解析XML