go环境以及基础语法| 青训营

67 阅读8分钟

go安装

下载地址 go官网就行

1、下载安装包
wget -c https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
2、解压到 /usr/local/ 下
tar -C /usr/local/ -xzf go1.8.3.linux-amd64.tar.gz
3、配置环境
root@promote:/#vim /etc/profile
在文件内容后面加上:export PATH=$PATH:/usr/local/go/bin 后保存
root@promote:/# source /etc/profile  #执行该指令使环境生效
4、测试
root@promote:/# go version
go version go1.8.3 linux/amd64

Linux下下载是一个压缩包,压缩包直接解压到/usr/local下

tar -zxvf 压缩包 -C 目标文件夹

安装完成后需要配置环境变量

设置bashrc,用的zsh设置zshrc

#go path
export GOROOT=/usr/local/go
#这里设置为项目文件夹
export GOPATH=$HOME/go 
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

source一下

最后输入go正常输出就行。

环境变量知识:

  • /etc/profile —— 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置;

  • /etc/environment —— 在登录时操作系统使用的第二个文件,系统在读取你自己的profile前,设置环境文件的环境变量;

  • /etc/bashrc —— 为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取;

  • ~/.profile —— 每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,它设置一些环境变量,执行用户的.bashrc文件;

  • ~/.bashrc —— 该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该文件被读取;

jetbrain 全家桶安装

官网下载jetbrains-toolbox就好

跑起来的第一个go程序

Compiled binary cannot be executed,1.15之后有main函数的go文件都必须在src/main文件夹下,并且包名为main,否则会显示无法执行。

warning: GOPATH set to GOROOT (/usr/local/go) has no effect 的话就是gopath设置错误,这个是网上下载包的地址,设置为项目文件夹就行。 aff6b83f39def2731682d1116d535404.png

显示go mod错误的话,就在启用go module功能,go env -w GO111MODULE=on。

然后运行go mod init helloworld生成go.mod文件。

成功运行的goland设置如下:

GOROOT

Go语言编译器的根目录

95b50606c9ba4638279ef8b55a2ee35e.png

GOPATH

Go语言项目地址,在运行项目的包会下载到这里(目前理解),这里使用系统设置。

154529e21937818a693e7c2734845802.png

d714a3508a7b8b174474e8b6f22bffc7.png

b34a6605aabd0b2553cb66460beb0987.png

两种设置,一种是单个文件编译运行,另一种是整个项目文件夹编译。在main文件夹编译

就会运行含有main函数的文件了(只能有一个main函数)。

go module

go语言的依赖管理工具

主要命令: 命令 作用
go mod init 生成 go.mod 文件
go mod download 下载 go.mod 文件中指明的所有依赖
go mod tidy 整理现有的依赖
go mod graph 查看现有的依赖结构
go mod edit 编辑 go.mod 文件
go mod vendor 导出项目所有的依赖到vendor目录
go mod verify 校验一个模块是否被篡改过
go mod why 查看为什么需要依赖某模块

gopath是以前的1.13前的写法,项目需要在gopath的src文件夹下才能运行。新的gomod并不需要。

go mod init 项目名 创建go.mod文件。可以在其中加入需要的包名。
go mod download ..../..... 下载包
go run main.go 可以直接运行,并且缺失的包会提示

下载包经常会以为网络问题而失败。需要设置代理。

在go env中设置

GO111MODULE=on
GOPROXY=https://goproxy.cn,direct

或者在goland中 设置GOPROXY=https://goproxy.cn

go在1.16版本后不再默认支持go build修改mod文件,添加依赖包需要手动添加(使用tidy或get)。如果看到网上说直接会自动安装,,是go更新了。

go mod tidy 一下或者 go get 包就行。

go基础

静态变量语言

var 变量名 变量类型

多变量定义

var {
  name string
  age int
}

变量作用域

分为局部变量和全局变量,全局和局部可以同时定义同名,但是会就近。
局部变量分两种,函数内的局部变量和语句内的局部变量(如for循环语句中,就是独立的)

常量

const 常量一般用大写。在定义大量的产量数据时,如果其数字是递增的,可以使用iota自动技术来实现。iota是一个用于枚举的自增常量生成器,通常用于创建一系列相关值。它在定义状态、选项、位掩码等场景中很有用,使代码更具可读性和易维护性。

const{
a=iota
b
c="haha"
d
e=100
f
g=iota
}

数组初始化

数组初始化有多种方式:

//直接初始化
arr:=[3]int{1,2,3}
//先定义数组,再添加数值
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
//用...代替数组长度定义,如果不填就是切片
var arr = [...]int{1,2,3}
//通过指定索引
arr := [5]int{0:1,4:3}

切片

切片Sclice就是可变长度的数组,可以自动扩容。

//可变参数传入结果就是切片类型
func add(num ...int) int {
	sum := 0
	//遍历切片的一种方式
	for _, v := range num {
		sum += v
	}
	//简单遍历方式
	for i := 0; i < len(num); i++ {
    //slice[i]
  }
	return sum
}

自定义数据类型 别名

MyInt和int go不认为相同类型,无法互相赋值。

type MyInt int

地址

基本和c相同,包括指针&取地址,*取地址中的内容,也就是指针操作。

  • *:用于声明指针类型和解引用指针,以访问指针指向的值。
    var x int = 42
    var ptr *int     // 声明整型指针
    ptr = &x         // 将x的地址赋给ptr
    fmt.Println(*ptr)  // 输出 42,*ptr 解引用指针,获取指向的值
  • &:用于获取变量的地址,从而创建指针。
var y int = 24
var ptrY *int    // 声明整型指针
ptrY = &y        // 将y的地址赋给ptrY

switch和if使用

switchif 都是Go语言中的条件语句,用于不同的条件控制情况。

  1. if 语句:

    • if 用于在满足特定条件时执行一段代码块。
    • 可以使用单个 if,也可以与 elseelse if 一起使用,处理多个条件分支。
    • 适用于需要基于一个或多个条件来做出决策的情况。
if age >= 18 {
    fmt.Println("You are an adult.")
} else {
    fmt.Println("You are a minor.")
}
  1. switch 语句:

    • switch 用于在多个可能的条件中进行选择,根据某个表达式的值来执行相应的代码块。
    • 可以用 case 表达式来列举不同的值或范围。
    • 可以带有 default 分支,处理未匹配到的情况。
    • 适用于在多个固定的值中进行选择,以及简化多重条件嵌套的情况。
switch day {
case "Monday":
    fmt.Println("It's Monday.")
case "Tuesday":
    fmt.Println("It's Tuesday.")
default:
    fmt.Println("It's another day.")
}

switch 通常可以代替 if 在以下情况下使用:

  • 当需要检查一个表达式的多个可能值时,switch 语句的结构会更加清晰和简洁。
  • 如果多个 if-else 分支是基于同一个表达式的值进行判断,那么可以考虑使用 switch 来提高可读性。
switch 变量{
	case 值:
	  fallthrough //穿透下一个
	case2if 变量=值1{
	    break  //终止穿透跳出语句
	  }
	default:
} //判断具体值

for 循环

特殊一些,死循环可以不添加参数。

for {
}

正常使用的话如下,break(结束整个循环,跳出)和continue(结束单次循环)。

func main() {  
    for i := 0; i < 5; i++ {  
        if i != 2 {  
            continue  
        }  
        if i == 3 {  
            break  
        }  
        fmt.Println(i)  
    } //2  
}

自然还有range形式。

arr := [5]int{1, 2, 3, 4, 5}  
for i, _ := range arr {  
}   

可变参数

在Go语言中,可变参数是一种函数参数的形式,允许函数接受不定数量的参数。这在需要处理变长参数列表的情况下非常有用。Go语言中使用 ... 表示可变参数,它会将传递的参数打包成一个切片(slice)。

func getSun(num ...int) int {
  sum:=0
  for i
}

延迟函数

func main(){
  a=10
  defer f(a)
  a+=1
  f(a)
}
func f(a int){
  fmt.Println(a)
}
/**
输出
11
10
特殊的地方在defer语句所在的地方参数已经传递,后续修改参数不会影响f函数内参数的结果
**/

函数本质

go语言中函数就是一个数据类型

可以直接打印函数名的%T

package main

import "fmt"

// func()本身就是一个数据类型
func main() {
	//f1如果不加括号,函数就是一个变量
	//f1()如果加了括号那就成了函数的调用
	fmt.Printf("format:%T\n", f1) //func()func(int,int)|func(int,int)int
	//fmt.Printf("%T",10)//int
	//fmt.Printf("%T\n","hello")//string

	//定义函数类型的变量
	var f5 func(int, int)
	f5 = f1 //引用类型的
	fmt.Println(f5)
	fmt.Println(f1)
	f5(1, 2)
}
func f1(a, b int) {
	fmt.Println(a, b)
}

可以直接把函数赋值给函数,此时地址相同,其实就是指针指向内存中同一个地址。

函数名指向函数体的内存地址,是一种特殊类型的指针变量。

匿名函数

函数可以支持匿名,并且可以在最后加上参数自己调用自己。

func main() {
	sum := func(a, b int) int {
		return a + b
	}(1, 2)
	fmt.Println(sum)

	f1 := func() {
		fmt.Println("func1")
	}
	f1()
	f2(f1)

}
func f2(f1 func()) {
	f1()
}

回调函数

将匿名函数作为另一个函数的参数。

package main

import "fmt"

// 回调函数测试
func main() {
	a := 2
	b := 1
	r1 := opre(a, b, add)
	fmt.Println(r1)
	r2 := opre(a, b, sub)
	fmt.Println(r2)
	r3 := opre(a, b, mul)
	fmt.Println(r3)
	r4 := opre(a, b,
		func(a, b int) int {
			if b == 0 {
				fmt.Println("除数不能为0")
				return 0
			}
			return a / b
		})
	fmt.Println(r4)
}

// 高阶函数,接收其他函数作为输入
func opre(a int, b int, f func(int, int) int) int {
	return f(a, b)
}
func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}
func mul(a, b int) int {
	return a * b
}
func div(a, b int) int {
	if b == 0 {
		fmt.Println("除数不能为0")
		return 0
	}
	return a / b
}

闭包结构

闭包就是匿名函数和匿名函数所引用环境的组合。当我们使用匿名函数作为另外一个函数的返回值时,匿名函数会直接保存使用的外部变量的地址。 如下方过程中,闭包函数f中保存了n变量的地址,因此在调用时,n会一直增加,最后输出结果为1和2。

func main() {
	n := 0
	f := func() {
		n += 1
		fmt.Println(n)
	}
	f() //1
	f() //2
}

掘金社区中的例子,这个过程中,由于添加到funcSlice中的闭包函数,全部指向i的地址,由于i最后的结果为3,因此打印为3。

//
var funcSlice []func()
for i := 0; i < 3; i++ {
	funcSlice = append(funcSlice, func() {
		println(i)
	})
}
for j := 0; j < 3; j++ {
	funcSlice[j]() // 3, 3, 3
}

函数作为函数返回值时,返回的函数也是个闭包,如下方例子当中,如果直接使用add()(),返回的是一个独立的闭包结构,两次add()()返回的是两个不同的函数,他们保存的n变量地址也不同。而后面定义的f作为一个闭包函数,被多次调用,其中的n的地址始终不变,所以可以看到打印的n的值在增加。

func add() func() {
	n := 0
	return func() {
		n += 1
		fmt.Println(n)
	}
}

func main() {
	f := add()
	add()() //1
	add()() //1
	f()     //1
	f()     //2
	f()     //3
}

对返回值命名

特殊点,不需要关注返回值顺序。

func add_sub(a int, b int) (sum int, sub int) {
	sum = a + b
	sub = a - b
	return
}