发布时间

探索-GO

学习视频:B站

安装

Go 官网下载地址:https://golang.org/dl/

Go 官方镜像站(推荐):https://golang.google.cn/dl/

安装方式

windows 可以直接下载 .exe 文件即可

linux Debian 系统 没有版本限制直接 sudo apt update && sudo apt install golang-go 一般版本比较旧,但是相对稳定,比如现在已经是1.25.x版本了,提供最新的下载还是1.19.x

# 下载安装包(以1.24.5为例,请替换为实际版本号)
wget https://go.dev/dl/go1.24.5.linux-amd64.tar.gz
# 解压到 /usr/local 目录[citation:1][citation:5]
sudo tar -C /usr/local -xzf go1.24.5.linux-amd64.tar.gz

# 编辑当前用户的配置文件
echo 'export PATH="$PATH:/usr/local/go/bin"' >> ~/.bashrc
# 或编辑 ~/.profile[citation:1][citation:7]
# 使配置立即生效[citation:1]
source ~/.bashrc

# 多版本管理器
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
source ~/.gvm/scripts/gvm

# 直接上面的执行会因为访问外网超时
# 解决办法:1、使用镜像  2、手动安装(将文件下载本地,然后上传服务器)
# 使用下面两个其中一个镜像
bash < <(curl -s -S -L https://raw.fastgit.org/moovweb/gvm/master/binscripts/gvm-installer)
# 使用 ghproxy 代理访问
bash < <(curl -s -S -L https://ghproxy.com/https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
# 我这边是镜像都不行,只能在windows本地打开:https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer
# 在服务器创建 gvm-installer.sh 文件,将上面的内容复制到文件中
# 给 gvm-installer.sh 执行权限
chmod +x gvm-installer.sh
# 执行
./gvm-installer.sh
# 执行上面文件后会报缺少安装依赖, 安装 gvm 依赖
sudo apt update && sudo apt install -y git curl make binutils bison gcc build-essential
# 安装 执行
source /root/.gvm/scripts/gvm
gvm help  # 出现  Usage: gvm [command] 等就是安装成功了,就可以安装不同版本
## 重点,若是使用的是云服务器厂商,直接让ai安装,避免了镜像源等问题出现

# 安装指定版本
gvm install go1.21.5
gvm use go1.21.5 --default

自定义安装gvm问题修复

# GVM 修复指南

## 问题描述
GVM (Go Version Manager)`gvm use` 命令报错:
```
ERROR: Couldn't source environment
```

## 问题原因
GVM 的 environments 目录缺少 Go 版本对应的环境配置文件,导致 use 命令无法加载环境变量。

## 修复步骤

### 1. 检查当前状态
```bash
ls -la /root/.gvm/environments/
ls -la /root/.gvm/gos/
```

### 2. 创建环境配置文件
```bash
# Go 1.21.6
cat > /root/.gvm/environments/go1.21.6 << 'ENV'
export GOROOT="$GVM_ROOT/gos/go1.21.6"
export GOPATH="$GVM_ROOT/pkgsets/go1.21.6/global"
export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"
ENV

# Go 1.23.0
cat > /root/.gvm/environments/go1.23.0 << 'ENV'
export GOROOT="$GVM_ROOT/gos/go1.23.0"
export GOPATH="$GVM_ROOT/pkgsets/go1.23.0/global"
export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"
ENV
```

### 3. 创建目录结构
```bash
mkdir -p /root/.gvm/pkgsets/go1.21.6/global
mkdir -p /root/.gvm/pkgsets/go1.23.0/global
mkdir -p /root/.gvm/gos/go1.21.6/pkgset/global
mkdir -p /root/.gvm/gos/go1.23.0/pkgset/global
```

### 4. 验证修复
```bash
export GVM_ROOT=/root/.gvm
source $GVM_ROOT/scripts/gvm-default
gvm use go1.23.0
go version
gvm use go1.21.6
go version
gvm use go1.23.0 --default
```

## 使用说明

### 基本命令
```bash
# 加载 GVM 环境(每次新终端都需要)
export GVM_ROOT=/root/.gvm
source $GVM_ROOT/scripts/gvm-default

# 切换 Go 版本
gvm use go1.23.0
gvm use go1.21.6

# 设置默认版本
gvm use go1.23.0 --default

# 查看已安装版本
gvm list

# 查看当前版本
go version
```

### 自动加载配置
将以下内容添加到 `~/.bashrc``~/.zshrc````bash
export GVM_ROOT=/root/.gvm
source $GVM_ROOT/scripts/gvm-default
```

然后执行:
```bash
source ~/.bashrc  # 或 source ~/.zshrc
```

## 手动安装新版本

### 1. 下载并解压
```bash
cd /tmp
wget https://mirrors.aliyun.com/golang/go1.xx.x.linux-amd64.tar.gz
mkdir -p /root/.gvm/gos/go1.xx.x
tar -C /root/.gvm/gos/go1.xx.x --strip-components=1 -xzf go1.xx.x.linux-amd64.tar.gz
```

### 2. 创建环境文件
```bash
cat > /root/.gvm/environments/go1.xx.x << 'ENV'
export GOROOT="$GVM_ROOT/gos/go1.xx.x"
export GOPATH="$GVM_ROOT/pkgsets/go1.xx.x/global"
export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"
ENV
```

### 3. 创建目录结构
```bash
mkdir -p /root/.gvm/pkgsets/go1.xx.x/global
mkdir -p /root/.gvm/gos/go1.xx.x/pkgset/global
```

### 4. 验证安装
```bash
gvm use go1.xx.x
go version
```

## 常见问题

### Q1: 为什么 gvm use 报错 "Couldn't source environment"**A:** 因为缺少对应 Go 版本的环境配置文件,需要手动创建 `/root/.gvm/environments/go版本号` 文件。

### Q2: 切换版本后 go version 没有变化?
**A:** 需要确保:
1. 已加载 GVM 环境:`source $GVM_ROOT/scripts/gvm-default`
2. 环境文件存在且配置正确
3. 重新打开终端或重新加载配置

### Q3: 如何添加新的 Go 版本?
**A:** 参考上面的"手动安装新版本"部分,需要:
1. 下载并解压到 `/root/.gvm/gos/`
2. 创建环境配置文件
3. 创建必要的目录结构

### Q4: gvm list 不显示版本?
**A:** 确保 Go 版本已正确安装在 `/root/.gvm/gos/` 目录下,目录命名格式为 `go版本号`
### Q5: 如何完全卸载 GVM**A:** ```bash
rm -rf /root/.gvm
# 从 ~/.bashrc 或 ~/.zshrc 中删除 GVM 相关配置
```

## 环境变量说明

- **GOROOT**: Go 安装目录
- **GOPATH**: Go 工作空间目录
- **PATH**: 包含 Go 二进制文件路径

安装对应电脑最新版本,在1.11版本以后环境变量都已经集成到go中了

# 查看版本
go version
# 查看环境变量
go env
==>
set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=0
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=
set GOAMD64=v1
set GOARCH=amd64
set GOAUTH=netrc
set GOBIN=
set GOCACHE=C:\Users\admin\AppData\Local\go-build
set GOCACHEPROG=
set GODEBUG=
set GOENV=C:\Users\admin\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOGCCFLAGS=-m64 -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\admin\AppData\Local\Temp\go-build1200714809=/tmp/go-build -gno-record-gcc-switches
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=NUL
set GOMODCACHE=C:\Users\admin\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\admin\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=D:\Go
set GOSUMDB=sum.golang.org
set GOTELEMETRY=local
set GOTELEMETRYDIR=C:\Users\admin\AppData\Roaming\go\telemetry
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=D:\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.25.4
set GOWORK=
set PKG_CONFIG=pkg-config

vscode 安装go插件:go(搜索第一个)。在开发环境中使用提示词

设置代理

# 1. 编辑你的 Shell 配置文件(例如 ~/.bashrc, ~/.zshrc~/.profile)
# 使用文本编辑器打开,例如:
nano ~/.bashrc

# 2. 在文件末尾添加以下行(以 goproxy.cn 为例)
export GOPROXY=https://goproxy.cn,direct
# 如果你需要同时设置多个代理,可以用逗号分隔,例如:
# export GOPROXY=https://goproxy.cn,https://proxy.golang.org,direct

# 3. 保存文件,然后让配置生效
source ~/.bashrc

Demo

// 定义当前文件的包名 随意
package main  
//  fmt 打印包名
import 	"fmt"
var a = "hello" 
func main()  {
	// Println 	输出并换行
	fmt.Println("hello world")
	// Print打印输出不换行, 多个Print打印输出不换行, 会在同一行输出
	fmt.Print("hello world A")
	fmt.Print("hello world B")
        // 打印中有变量,输出变量
        fmt.printf("适合 a=%d a的类型 a=%T",a,a)
}

go 语言和其他语言不同的地方是:声明的变量必须使用否则报错。

变量名称的命名:Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。Go 语言中关键字 和保留字都不能使用

  • 变量名 变量类型 = 变量值 =>: var a string = "hello"、var m string、var n = 10 (支持类型推导)
  • 短变量声明法(不能作为全局变量声明)=>变量名 : 变量值: a : = 10
  • 变量名不能在同一作用域重复声明
# 声明多个变量
var (
  a = 10
  c = "hello"   
)

匿 名变量用一个下划线 表示,例如
匿名变量 在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)func getUserinfo()(string, int){
   return "zhangsan", 10
}
var name,age = getUserinfo()
# => 若是声明单个变量: var user,_ = getUserinfo()
//匿名变量不占用命名空间,不会分配内存,所以名变量之间不存在重复声明

# 常量
const (
  a = 10
  b = "hei"
)
//const 同时声明多个常量时,如果省略了值则表示和上面一行的值相同。
const (
  a = 10
  b
)
# iota 是 golang 语言的常量计数器,只能在常量的表达式中使用。
# iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前)const 中每新增一行常量 
# 声明将使 iota 计数一次 iota 可理解为 const 语句块中的行索
const (
  n1 = iota
  n2
  n3
  n4
)
fmt.Print(n1,n2,n3,n4) ==> 0,1,2,3
const(
  n1 = iota //0
  n2=100 //100
  n3 = iota //2
  n4   //3
)
fmt.Println(n1,n2,n3,n4)

在Go语言中:

  1. 单引号 ' 包围的字符字面量表示的是rune类型(Unicode码点)
  2. rune类型实际上是int32的别名,它存储的是字符的Unicode码点数值
  3. 当你直接打印rune类型变量时,Go默认会输出其对应的整数值(Unicode码点)

数据类型

基本数据类型

整型

整型分为以下两个大类:
有符号整形按长度分为:int8、int16、int32、int64

对应的无符号整型:uint8、uint16、uint32、uint64

通过 unsafe.Sizeof 查看不同长度的整型 在内存里面的存储空间

数字字面量语法%v 表示原样输出 %d 表示10进制输出 %b表示二进制输出 %o 八进制输出 %x 表示16进制

int不同长度直接的转换

 var al int32 = 10
 var a2 int64 = 21
 fmt.Println(int64(a1)+ a2)//把a1转换成64位
fmt.Println(a1 +int32(a2))//把a2转换成32位

在电商、金融环境使用第三方包来解决浮点数精度损失问题:https://github.com/shopspring/decimal

布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值
注意:
1.布尔类型变量的默认值为false。
2.Go 语言中不允许将整型强制转换为布尔型.
3.布尔型无法参与数值运算,也无法与其他类型进行转换:

字符串

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

一个字母占用一个字节,一个汉字占用 3 个字节

func changeString(){
  s1 := "big" // 强制类型转换
  bytes1 := []byte(s1)
  byteS1[0]="p"
  fmt.Println(string(bytes1))
  s2 :="白萝卜"
  runeS2 := []rune(s2)
  runeS2[0]='红”
  fmt.Printin(string(runeS2))
}

类型转换

注意:Sprintf 使用中需要注意转换的格式int为%d float 为%f bool 为%t byte 为%c

var i int = 20
var f float64 = 12.456
var t bool = true 
var b byte = 'a'

str1 := fmt.Sprintf("%d",i)
fmt.Printf("值:%v 类型:%T\n",str1,str1)=>:20 类型:string
str2 := fmt.Sprintf("%f",f)
fmt.Printf("值:%v 类型:%T\n",str2,str2)=>:20 类型:string
str3 := fmt.Sprintf("%t",t)
fmt.Printf("值:%v 类型:%T\n",str3,str3)=>:true 类型:string
str4 := fmt.Sprintf("%c",b)
fmt.Printf("值:%v 类型:%T\n",str4,str4)=>:a 类型:string

注意:在 go语言中数值类型没法直接转换成bool类型 bool类型也没法直接转换成数值类

运算符

1、除法注意:如果运算的数都是整数,那么除后,去掉小数部分,保留整数部分

2、取余注意 余数=被除数-(被除数/除数)*除数

3、注意: ++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符。

var a= 10
var b=3
fmt.Println(a%b)		//1
fmt.Println(-10%3)		//-10-(-10/3)*3 =-1
fmt.Println(10%-3)		// 10-(10/-3)*-3=1

遍历

goto 语句通过标签进行代码间的无条件跳转。goto 语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用 goto 语句能简化一些代码的实现过程。

var n = 30
if n>=20{
   goto label66
}

fmt.Println("n<20")
label66:
fmt.Println("hello world")

引用类型

数组|切片

// arr := strings.split(str1,"-")
// str2 := strings.Join(arr,"*")
// fmt.Println(str2)//123*456*789
arr := []string{"php","java","golang"}
// fmt.Println(arr)
str3 := strings.Join(arr,"-")
// fmt.Println(str3)
fmt.Printf("%v-%",str3,str3)

var a = [5]int  //初始化数组 长度为5的数字
b :=[3]string{"css","javascript","golang"}
var b = [...]int{1,22,3335,8787,4545} //自动推导长度
c = [...]int{0:1,1:3,5:6} //长度为6  [1 3 0 0 0 6]

// 数组的值类型和引用类型示例
var arr1 = [...]int{1,2,3}
arr2 := arr1
arr1[0] = 10
fmt.Println(arr1)//[10 2 3]
fmt.Println(arr2)//[1 2 3]

var arr3 = []int{1,2,3}
arr4 := arr3
arr3[0] = 11
fmt.Println(arr3)//[11 2 3]
fmt.Println(arr4)// [11 2 3]
// 值类型:改变变量副本值的时候,不会改变变量本身的值
// 引用类型:改变变量副本值的时候,会改变变量本身的值

var arr1 []int
var arr2 = []int{1,2,34,45}
fmt.Println(arr1)//[]
fmt.Println(arr1 == nil)//true golnag中申明切片以后 切片的默认值就是ni1
fmt.Println(arr2 == nil)//false

在go中数组和切片定义是非常像的,但是切片不限定长度,数组在定义的时候已经定义了长度,不可以扩容

由于切片的底层就是一个数组,所以我们可以基于数组定义切片。

关于切片的长度和容量:

  • 长度:切片的长度就是它所包含的元素个数
  • 容量:切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
  • 没法通过下标的方式给切片扩容:给切片扩容的话要用到append()方法
a:=[5]int{55,56,57,58,59}
b := a[:] //获取数组里面的所有值
fmt.Printf("%v-%T\n",b,b)//[55 56 57 58 59]-[]int
c := a[1:4]
fmt.Printf("%v-%T\n",c,c)//[56 57 58]-[]int
d := a[2:]//表示获取第二个下标后面的数据
fmt.Printf("%v-%T\n",d,d)//[57,58,59]-[]int
e := a[:3] //表示获取第三个下标前面的数据
fmt.Printf("%v-%T\n",e,e)//[55,56,57]-[]int

s:=[]int{2,3,5,7,11,13}
fmt.Printf("长度%d 容量%d\n",len(s),cap(s))//长度6 容量6
a := s[2:]//5,7,11,13
fmt.Printf("长度%d 容量%d\n",len(a),cap(a))//长度4 容量4
b := s[1:3]//3,5
fmt.Printf("长度%d 容量%d\n",len(b),cap(b))//长度2 容量5

// make创建切片
var sliceA = make([]int,4,8)
// fmt.Println(sliceA)//[0 8 8 0]
sliceA[0]= 10
sliceA[1] = 12
sliceA[2]= 40
sliceA[3]= 30
fmt.Println(sliceA)// [10 12 40 30]

var sliceA []int
sliceA = append(sliceA,12,23,35,465)
fmt.Printf("%v-%v--%v",sliceA,len(sliceA),cap(sliceA))// [12 23 35 465]-4--4
sliceA:=[]string{"php","java"} 
sliceB:=[]string{"nodejs","python"}
sliceA = append(sliceA, sliceB...)
fmt.Println(sliceA)//[php java nodejs python]

append合并切片的时候最后一个元素要加

Map类型

make创建map类型的数据
var userinfo = make(map[string]string)
var userinfo = map[string]string{
  "username":"张三”,
  "age":"20",
  "sex":"男”
}
userinfo := map[string]string{
  "username":"张三”,
  "age":"20",
  "sex":"男”
}

Map切片

var userinfo = make([]map[string]string, 3,3)
// fmt.Println(userinfo[e])//map[]map不初始化的默认值ni1
fmt.Println(userinfo[0] == nil)//true

函数

函数的可变参数,可变参数是指函数的参数数量不固定。Go 语言中的可变参数通过在参数名后加...来标

func sum( x,y int) int {
 sub := x+y
 return sub 
}
func sumFn(x ...int)int {
  sum := 0
  for _, v := range x{
    sum += V
  }
  return sum
}

//return 关键词一次可以返回多个值
func calc(x,y int)(int, int){
  sum :=X+y
  sub :=x-y
  return sum, sub
}
func main(){
  a,b:= calc(102)
  fmt.Println(a, b)
}
//命名返回值
func calc1(x,y int)(sum int, sub int){
  fmt.Println(sum, sub)
  sum=x+y
  sub =x-y
  fmt.Println(sum, sub)
  return
}
func calc2(x,y int)(sum, sub int){
  sum=x+y
  sub =x-y
  return
}

函数变量作用域:

  • 全局变量:全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效(全局作用域)
  • 局部变量:局部变量是函数内部定义的变量,函数内定义的变量无法在该函数外使用(局部作用域)

函数可以作为一个类型,参数;但是在一个函数A内不能声明另一个函数,只能在这个函数A外定义,然后在A内使用,除了匿名自执行函数(就是定义的匿名函数立即执行)

type calc func(int,int)int //表示定义一个calc的类型

func add(x,y int) int{
  return x+y
}
func sub(x,y int) int{
  return x-y
}
# 参数是一个函数
func suc(x,y int, c calc) int{
  return calc(x,y)
}
func main(){
  var c calc
   c= sub   //符合同一类型

  h := suc(6,2,add)
  # 匿名自执行函数
  func (x,y int) {
    fmt.PrintIn("test....")
  }(10,20)
}

defer 延迟执行语句

有点像栈,先defer后输出

func a(x int){
  defer func(x int){
     x++
    return 
  }
  return x
}

func main (){
   fmt.PrinIn("开始")
   defer fmt.PrinIn("1")
   defer fmt.PrinIn("2")
   defer fmt.PrinIn("3")
   fmt.PrinIn("结束")
   // ==> 开始 结束 3 2 1
   a(4)
}

提示:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值

func main(){
  var a int = 1
  var b int = 2
  defer fmt.Println("a", a, "b", b)
  a = 10
  defer fmt.Println("A=", a, "B=", b)
  b = 20
  // A= 10 B= 2
  // a 1 b 2
}

panic | recover

在Go中没有try{}catch(err){}方式的函数,panic只有主动抛出错误,和在抛出错误之前使用recover拦截错误

import (
	"errors"
	"fmt"
)

func readFile(fileName string) error {
  if fileName=="main.go"{
    return nil
  } else {
    return errors.New("读取文件失败")
  }
}
func myFn(){
  defer func(){
    err := recover()
    if err != nil {
      fmt.Println("给管理员发送邮件")
    }
  }()
  err := readFile("xxx.go")
  if err != nil {
    panic(err)
  }
}
func main(){
  myFn()
  // 给管理员发送邮件
}

time和date包

func main() {
	timeObj := time.Now()
         // 格式时间
	fmt.Println(timeObj.Format("2006-01-02 15:04:05"))
	fmt.Println(timeObj.Format("2006/01/02 15:04:05"))
	fmt.Println(timeObj.Format("2006/01/02 15:04:05"))

	year := timeObj.Year()
	month := timeObj.Month()
	day := timeObj.Day()
	hour := timeObj.Hour()
	minute := timeObj.Minute()
	second := timeObj.Second()
	fmt.Printf("%d-%d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
       unixtime := timeobj.Unix()//获取当前的时间戳

       unixTime := 1587894706
       timeobj := time.Unix(int64(unixTime),0)
        var str =timeobj.Format("2006-01-02 15:04:05")
       fmt.Println(str)
        // 定时器
       ticker := time.NewTicker(time.Second)
	n := 5
	for t := range ticker.C {
		n--
		fmt.Println(t)
		if n == 0 {
			ticker.Stop() //终止这个定时器继续执行
			break
		}
	}
}

new和make

都是用来做内存分配,两者区别是在于make用于slice、map、channel的初始化,new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针