GO 语言快速上手
编辑本笔记仅适用与已经熟练掌握一门及以上编程语言者快速掌握GO语言
GO语言是由Google支持的开源编程语言。它易于学习,适合团队使用
准备动作
安装GO环境
下载GO并解压
我的操作系统是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就是一个无后缀的二进制文件
一些理论
保留字
自带标识符
GO语言中,变量,函数,类等等程序实体不允许与自带标识符同名
数据类型
数字类型
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
浮点型
其他数字类型
以下列出了其他更多的数字类型:
字符
其他
GO语言大小写敏感
在G语言中,给程序实体命名,不得使用@、$、%以及自带标识符与保留字
函数实体命名第一个字符必须是字母或_,不能是数字
只有package 名为 main 的包才被允许拥有main方法,一个可执行程序有且只有一个main包
import 只能导入非main包
GO语言无需像C或Java必须以;结尾
语法
注释
// 单行注释
/*
多行注释
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
优先级(由上往下,从大到小)
控制流程
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,切片的删除没有提供方法实现。切片元素的删除我分为两类
掐头去尾
删除头尾可以直接使用下标进行切片操作
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]
去任意某个
这就有点特殊,在此之前,先拓展一个方法 ...
,语法:
数组或切片...
...在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()方法复制元素遵循如下规则
复制的元素会放在原来的位置(1,2,3 替换了11,12,13)
要是被复制到的切片容量不足,那就只会复制能装得下的前几个(只复制1,2,3)
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
常用包
面向对象
准确来说,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_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代码
RESTful规范
https://blog.csdn.net/qq_26460841/article/details/119977022
一个Http测试网站
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¶m2=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¶m2=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¶m2=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"
}
- 0
-
赞助
微信支付宝 -
分享