👨环境配置
ubuntu16.04
go1.20.4.linux-amd64.tar.gz
vscode
remote -ssh插件,用于连接linuxGo v0.38.0插件,用于代码提示
😄详细配置过程🍻
-
直接解压包到
/usr/localtar -zxvf go1.20.4.linux-amd64.tar.gz -C /usr/local -
配置环境变量使得可以在命令行直接输入
go而不是/usr/local/go/bin/govim ~/.bashrc # 在最后一行添加 export PATH=$PATH:/usr/local/go/bin source /home/ljq/.bashrc # 加载使其生效 -
配置
go env的环境go env -w GO111MODULE=on go env -w GOPROXY=https://goproxy.cn,direct go env -w GOSUMDB=sum.golang.google.cn- 该命令将
GO111MODULE环境变量设置为"on",启用Go模块支持。 - 设置
Go模块的代理服务器地址,使其可以加速依赖包的下载,并解决一些境内无法访问的问题 - 改了这个环境字段后我
vscode的go插件才能install all
- 该命令将
🐺 go模块支持
这是go内置的最好的项目管理模块
go mod简单使用
-
go mod init创建一个
.go文件前就在其目录下面go mod init以下,比如我要创建一个包含main函数的hello.gogo mod init github.com/luopanforever/hello在当前目录下就会创建一个
go.mod文件,hello.go文件内容如下package main import "fmt" func main() { fmt.Println("Hello, World!") }没有以来其他的库所以直接
go run hello.go即可,可以看见输出. -
go mod tidy此时想要调用外部包的代码,修改
hello.go为如下package main import "fmt" import "rsc.io/quote" func main() { fmt.Println(quote.Go()) }此时调用了其他库的包,直接
go run肯定是不行 不信你试试🐇此时我们的
mod管理工具就起作用了,直接调用go mod tidy,如果卡了则换代理go env -w GOPROXY=https://goproxy.cn,direct,此时go.mod中就会出现一行新的东西,和目录下面一个新文件go.sum,酱紫过后再次go run就可以了
go mod命令大全😄
go mod init initialize new module in current directory 在当前目录初始化mod
go mod tidy //拉取缺少的模块,移除不用的模块。
go mod download //下载依赖包
go mod vendor //将依赖复制到vendor下
go mod verify //校验依赖
go list -m -json all //依赖详情
go mod graph //打印模块依赖图
go mod why //解释为什么需要依赖
命名要求🤙
- 包含
main函数的.go文件其package后面要跟main,其他的(不包含main函数的)就跟目录名一样就行 - 文件名随意取
基础语法🍰
go变量与常量
1.Go语言的变量声明格式为:
var 变量名 变量类型
var name string
var age int
// 批量声明
var (
name string
age int
hight float32
)
2.初始化格式
-
声明并初始化(显示类型)
var name string = "luopanforever" -
声明并初始化(类型隐式推导)
var name = "luopanforever" -
声明并初始化(短变量声明)> 只能在函数内部使用
name := "string"
3.匿名变量
匿名变量用一个下划线_表示
func foo() (int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}
匿名变量不占用命名空间,不会分配内存,匿名变量之间不存在重复声明。
4.常量
把var换成了const,常量在定义的时候必须赋值
const pi = 3.1415
const同时声明多个常量时,如果省略了值则表示和上面一行的值相同
const (
n1 = 100
n2
n3
)
5.iota
常用于定义枚举
const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
特殊使用
-
使用
_跳过某些值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
应用描述内存单位
const (
_ = iota
KB = 1 << (10 * iota) // 2^10
MB = 1 << (10 * iota) // 2^20
GB = 1 << (10 * iota) // 2^30
TB = 1 << (10 * iota) // 2^40
PB = 1 << (10 * iota) // 2^50
)
6.注意事项
- 函数外的每个语句都必须以关键字开始(
var、const、func等) :=不能使用在函数外。_多用于占位,表示忽略值。
go基础数据结构类型
编程的时候需要用大数据的时候才需要申请大内存,这样就可以充分利用内存。
-
bool(布尔) -
数值类型
int8、int16、int32、int64、intuint8、uint16、uint32、uint64、uintfloat32, float64complex64, complex128byterune
-
string
1.布尔类型
// 短类型声明
var isExit := true
// 或
var isExit bool = false
2.字符串类型
字符串是 Go 中字节的集合
var say = "hello" //单行字符串
var tag = """ // 转义符
var say = `hello` //原样输出
var mLine = `line1 //多行输出
line2
line3
`
var str = "hello, 世界"
3.数字类型
取值范围
uint8 // 所有无符号8位整数的集合 (0 to 255)
uint16 // 所有无符号16位整数的集合 (0 to 65535)
uint32 // 所有无符号32位整数的集合 (0 to 4294967295)
uint64 // 所有无符号64位整数的集合 (0 to 18446744073709551615)
int8 // 所有带符号8位整数的集合 (-128 to 127)
int16 // 所有带符号16位整数的集合 (-32768 to 32767)
int32 // 所有带符号32位整数的集合 (-2147483648 to 2147483647)
int64 // 所有带符号64位整数的集合 (-9223372036854775808 to 9223372036854775807)
float32 // 所有 IEEE-754 32 位浮点数的集合
float64 // 所有 IEEE-754 64 位浮点数的集合
complex64 // 浮点实数和虚数为32的所有复数的集合
complex128 // 浮点实数和虚数为64的所有复数的集合
4.类型转换
Go对显式类型非常严格。没有自动类型提升或转换。
互加
i := 55 // int
j := 67.8 // float64
sum := i + j // int类型 + float类型是不被允许的
-
编译失败。
go不允许隐式类型转换改正
sum := i + int(j) // j被转到了int类型
赋值
i := 10
var j float64 = float64(i) // 没有显示转换将会报错
循环与条件判断⚪️
用 if、switch、for 来进行条件判断和流程控制
for是 Go 中唯一可用的循环。Go没有其他语言(如 C)中存在的while或 do while循环。
for格式
for initialisation; condition; post {
}
if格式
if 条件 {
# 业务代码
}
if 条件 {
# 业务代码
} else if {
# 业务代码
} else {
# 业务代码
}
switch格式
无需break,在 Go 中 switch 只要匹配中了就会中止剩余的匹配项
age := 10
switch age {
case 5, 7, 10:
fmt.Println("The age is 5 7 10")
case 11:
fmt.Println("The age is 11")
default:
fmt.Println("The age is unkown")
}
也可以使用bool表达式
age := 7
switch {
case age >= 6 && age <= 12:
fmt.Println("It's primary school")
case age >= 13 && age <= 15:
fmt.Println("It's middle school")
case age >= 16 && age <= 18:
fmt.Println("It's high school")
default:
fmt.Println("The age is unkown")
}
高级数据类型
1.数组
1)声明
声明方式
var a [3]int👈
数组中的所有元素都被自动赋值为数组类型的零值
简式声明并赋
a := [3]int{8, 18, 88}👈
-
忽略长度 使用
...代替让编译器完成操作a := [...]int{8, 18, 88}
2)数组是值类型
当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本(可以按照深拷贝来理解)
如果对新变量进行更改,则不会影响原始数组
3)数组的遍历
-
普通
for循环for i:= 0; i < len(a); i++{} -
range for循环for i, v := range a {}range返回索引和该索引位置的值
4)多维数组
声明
var a = [3][2]string{ // or a:= or var a [3][2]string =
{"lion", "tiger"},
{"cat", "dog"},
{"pigeon", "peacock"},
}
2.切片
切片是数组一个连续片段的引用,引用的这个数组被称为相关数组,通常是匿名的
切片是一个长度可变的数组
1)创建切片
-
对数组进行切片创建切片
a := [5]int{76, 77, 78, 79, 80} var b []int = a[1:4] // len 3 cap 4 -
在右边空出
[]来创建切片var c []int = []int{6, 7, 8} // len 3 cap 3
2)切片的修改
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
- 对
dslice修改darr的对应索引的值也会修改 c选手可以理解为切片是一个数组指针,并且其有超出索引保护
3)切片的长度和容量
切片的长度是切片中的元素数。
切片的容量是从创建切片索引开始的底层数组中元素数。
a := [...]string{"1", "2", "3", "4", "5", "6", "7"}
b := a[1:3]
fmt.Printf("length of slice %d capacity %d\n", len(b), cap(b)) // len = 2 cap = 6
fmt.Println(len(a)) // len = 7
len为6:容量是从创建切片索引开始的底层数组中元素数
切片可以重置其长度len
b = b[:cap(b)]
- 之后切片的
len与cap就相等了 - 索引超过
len是不被允许的,必须重置len
4)使用make创建一个切片
函数原型: func make([]T,len,cap)[]T
var i []int = make([]int, 5, 5)
5)追加切片长度
函数原型: func append(s[]T,x ... T) []T
a := []string{"1", "2", "3"}
fmt.Println("old length", len(a), "and capacity", cap(a))
a = append(a, "4")
fmt.Println("new length", len(a), "and capacity", cap(a))
-
当新的元素被添加到切片时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量是旧切片的两倍
- 由于是返回的新数组,故没有指向原来的
a,则对后面新的切片的改变会不改变原数组了
- 由于是返回的新数组,故没有指向原来的
6)切片的函数传递
函数的的切片形参与切片实参都是指向同一个底层数组
二维切片的创建
pls := [][]string {
{"C", "C++"},
{"Java"},
{"Go", "Rust"},
}
// make来创建
matrix := make([][]int, rows)
for i := range matrix {
matrix[i] = make([]int, cols)
}
3.map
1)创建map
-
使用关键字
map来声明a := map[string]int{"ljq" :001, "psy":002}
-
使用
makecMap := make(map[string]int)
2)添加元素到map
-
数组形式添加
cMap["cc"] = 003 -
创建时指定
a := map[string]int{"ljq" :001, "psy":002}
3)不能向未初始化的map添加元素
Map在没有初始化时默认为nil
必须在添加元素之前初始化 Map。
4)检查键是否存在
value, ok := map[key]
如果ok为真,则该键存在且其值存在于变量value中,否则该键不存在
5)遍历
需要注意的是map是无序的
-
range返回key-valuefor key, value := range cities{}
6)删除键值对
delete(map, key)
尝试删除 Map中不存在的键,不会出现运行时错误
7)线程不安全
线程不安全, 一个goroutine在对map进行写的时候,另外的 goroutine不能进行读和写操作,Go 1.6版本以后会抛出 runtime 错误信息
4.string
字符串是 Go 中的字节切片。可以通过将一组字符括在双引号中来创建字符串" "。
函数
func name(parameter) (result-list){
//body
}
命名返回值
func plus(a, b int) (res int){
res = a + b
return // res 可写可不写
}
参数可变函数
func sum(nums ...int)int{
fmt.Println("len of nums is : ", len(nums))
res := 0
for _, v := range nums{
res += v
}
return res
}
- 通过
len来获取个数
匿名函数
func(name string){
fmt.Println(name)
}("https://www.gotribe.cn")
没有函数名,声明了马上要传参运行
指针
go的指针作用很有限,仅为了传参时函数能够改变实参的值
使用new函数创建指针
new函数将一个类型作为参数并返回一个指针,该指针指向作为参数传递的类型的新分配的空值
函数能够返回局部变量的指针
func hello() *int {
i := 5
return &i
}
Go编译器足够智能,它会在堆上分配这个变量- 此代码的行为在
C和C++等编程语言中是未定义的,因为一旦函数返回,变量i就会超出范围。但是在Go的情况下,编译器会进行转义分析,并·在地址转义本地范围时在堆上进行分配。
不支持指针运算
b := [...]int{109, 110, 111}
p := &b
p++
- 上述抛出编译错误
结构体
声明一个结构体
type StructName struct{
FieldName type
}
type Student struct {
Age int
Name string
}
func main(){
stu := Student{
Age: 18,
Name: "name",
}
}
创建匿名结构体
emp3 := struct {
firstName string
lastName string
age int
salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee 3", emp3)
结构体的指针
var emp8 *Employee = &Employee{
firstName: "Sam",
lastName: "Anderson",
age: 55,
salary: 6000,
}
- 使用
emp8.字段与(*emp8).字段访问是一样的
匿名字段
type Person struct {
string
int
}
func main(){
p1 := Person{
string: "naveen",
int: 50,
}
fmt.Println(p1.string)
fmt.Println(p1.int)
}
方法
方法(method)的声明和函数很相似, 它必须指定接收者
注意:
- 接收者的类型只能为用关键字
type定义的类型,例如自定义类型,结构体。 - 同一个接收者的方法名不能重复 (没有重载),如果是结构体,方法名还不能和字段名重复。
- 值作为接收者无法修改其值,如果有更改需求,需要使用指针类型。
type Employee struct {
name string
salary int
currency string
}
func (e *Employee) displaySalary() {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee {
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
emp1.displaySalary()
}
接口
声明与实现接口
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
小例子
-
声明一个接口,里面包含一个方法
type VowelsFinder interface { FindVowels() []rune } -
将
string封装为自定义类型,因为方法的接收者的类型只能是自定义类型type MyString string -
MyString实现其接口的方法func (ms MyString) FindVowels() []rune { var vowels []rune for _, rune := range ms { if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' { vowels = append(vowels, rune) } } return vowels }
上述MyString实现了VowelsFinder接口的所有方法,所以接口变量可以被MyString变量赋值
func main(){
name := MyString("Sam Anderson")
var v VowelsFinder
v = name
fmt.Printf("Vowels are %c", v.FindVowels())
}
空接口
空接口里面没有任何方法,所以可以当作所有类型都实现了它,于是它可以被所有类型赋值
-
可用在函数的形参中
func describe(i interface{}){}
类型断言
func assert(i interface{}) {
s, ok := i.(int) //get the underlying int value from i
fmt.Println(s, ok)
}
func main() {
var s interface{} = 56
assert(s)
}
协程
Go是一种并发语言,Go 中使用 Goroutine 和 channel 处理并发。
如何启动一个 Goroutine?
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
}
- 当一个新的
Goroutine启动时,goroutine调用立即返回。与函数不同,主程不等待Goroutine完成执行。在Goroutine调用之后,主程立即返回到下一行代码,并且Goroutine的任何返回值都将被忽略。
- 主
Goroutine应该正在运行以供任何其他Goroutines运行。如果主Goroutine终止,则程序将终止,并且不会运行其他Goroutine。
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
- 这样以来协程就有足够的时间在主
Goroutine终止之前执行
Channel
Channels可以被认为是 Goroutine 进行通信的管道。类似于水在管道中从一端流向另一端的方式,数据可以从一端发送并使用Channel从另一端接收。
声明Channel
var a chan T
-
channel的零值为nil。nil在Channels没有任何用处,因此必须使用make类似于maps和slices来定义Channel。if a == nil { fmt.Println("channel a is nil, going to define it") a = make(chan int) fmt.Printf("Type of a is %T", a) } -
简短定义
a := make(chan int)
channels发送和接受
data := <- a // read from channel a
a <- data // write to channel a
- 在第一行中,箭头指向外
a,因此我们从Channels读取a并将值存储到变量data中。 - 在第二行中,箭头指向
a,因此我们正在写入Channel。
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
time.Sleep(1 * time.Second)
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done // 没有使用数据 这是合法的
fmt.Println("main function")
}
单向channel
使用sendch关键字并将chan T改写为chan <- T
sendch chan <- T
- 可以用于协程入口函数的参数讲双向信道转化为单向的
Buffer Channels
通过将附加容量参数传递给make指定缓冲区大小的函数来创建缓冲通道。
- 仅当缓冲区已满时,才会阻止发送到缓冲通道。
- 只有当缓冲区为空时,来自缓冲通道的接收才会被阻塞。
ch := make(chan type, capacity)
select语句
select语句用于从多个send/receive通道操作中进行选择。select语句会阻塞,直到其中一个发送/接收操作准备好。
func server1(ch chan string) {
time.Sleep(6 * time.Second)
ch <- "from server1"
}
func server2(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
- 最终打印
"from server2"
互斥锁
import "main"
/*
锁的操作
- m.Lock();
- m.Unlock();
*/
func main(){
var m sync.Mutex
}
文件操作
读文件
在包"io/ioutil"
data, err := ioutil.ReadFile("test.txt")
fmt.Println(string(data))
data的类型为[]uint8
写文件
import "os"
f, err := os.Create("test.txt") //create file
l, err := f.WriteString("Hello World") // writing file
- 先打开文件得到文件描述符
- 用文件描述符读取文件,返回读取到的字节与错误信息