Go语言快速上手
在Linux上安装GoLang
wget https://golang.google.cn/dl/go1.19.1.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.profile
source $HOME/.profile
go version
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
在Windows上安装GoLang
下载.msi
文件安装后,配置环境变量
将安装的Go\bin 目录添加到 Path 环境变量中
将工作目录也添加到环境变量中
确认之后检查是否成功
配置开发环境
配置「七牛云」的代理服务
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
配置工作路径
go env -w GOPATH=C:\WorkDir\Go # 这是我的工作路径,填自己的
打开vscode
安装GO语言的插件
之后工作目录下新建一个src
目录,在该目录下创建xxx.go
文件,根据vscode
的提示安装所有的包
变量的声明
// 法一
var num int = 100
var str string = "123"
// 法二 知道变量的值后可以不用声明属性
var num = 100
var str = "123"
// 法三 此方法只能用在局部变量中
num := 100
str := "123"
// 多行声明
var num, str = 100, "123"
var (
num = 100
str = "123"
)
num, str := 100, "123"
常量的定义
// 常量只读,不允许修改
const num int = 100
// const 用来定义枚举类型
const (
// 可以在 const() 中添加关键字 iota(也只能在const 中使用), iota 会自增,第一行默认为 1
January = iota // iota = 0
February // iota = 1
March // iota = 2
April // iota = 3
May // ... ...
June
July
)
函数的返回值
package main
import "fmt"
// 1个匿名返回值
func demo1(a int) int {
b := a * 2
return b
}
// 2个匿名返回值
func demo2(a int, b int) (int, int) {
return a * 2, b * 3
}
// 不匿名的返回
func demo3(a int, b int) (r1 int, r2 int) {
r1 = a * 2
r2 = b * 3
return
}
func demo4(a int, b int) (r1, r2 int){
}
func main() {
ret1 := demo1(10)
fmt.Printf("ret1 = %d\n", ret1)
ret2, ret3 := demo2(10, 20)
fmt.Printf("ret2 = %d, ret3 = %d\n", ret2, ret3)
ret4, ret5 := demo3(100, 200)
fmt.Printf("ret4 = %d, ret5 = %d\n", ret4, ret5)
}
init() 函数和import
// 程序会先执行最里面的包的init()方法
**注意:**函数名开头大写表示函数是public
, 小写表示函数是private
import (
_ "$GOPATH/xxx/xxx" // 匿名导包,不调用也会执行init()
name "$GOPATH/xxx/xxx" // 起别名
. "$GOPATH/xxx/xxx" // 导入到当前程序中,可直接使用包内的方法
)
defer 语句的调用顺序
defer
在函数的生命周期结束后调用(在return
之后),遵循先进后出原则
package main
import "fmt"
func fun1() {
fmt.Println("this is fun1 called")
}
func fun2() int {
fmt.Println("this is fun2 called")
return fun3()
}
func fun3() int {
fmt.Println("this is fun3 called")
return 0
}
func main() {
defer fun1()
defer fun2()
}
/* 结果
this is fun2 called
this is fun3 called
this is fun1 called
*/
静态数组和动态数值
// 静态数组,上来就声明长度或者内容,有着固定长度
arr1 := [4]int{1,2,3,4}
arr2 := [10]int{1,2,3,4} // 前四有值,后面的全为0
var arr3 [5]int // 定义五个为0的数组
// 动态数组没有固定的长度,也叫 切片 (其本质为指针)
slice := []int{1,2,3} // 默认值 1 2 3 长度为3 len=3 , cap = 3
slice1 := make([]int, len, cap) // len <= cap
slice2 := make([]int, 3) // len = 3, cap = 3
// 切片的追加
slice1 = append(slice1, 1) // 向slice1追加一个值为1的元素
// 若向一个容量已满的切片追加,则会新建一个之前两倍容量的切片,
// 之后将旧的切片赋值给新的切片,在新的切片上进行追加
// 切片的截取
slice4 := []int{1,2,3,4,5,6}
// 若修改num,则slice4也会改变 浅拷贝
num := slice4[0:3] // 左闭右开 [:2] [1:]
// 深拷贝
num2 := make([]int, 3)
copy(num2, slice4)
map的使用
和切片一样,是动态的,要分配空间
|->值
mymap := make(map[int]string, 10) // map[key]value / cap = 10
|-> 键
mymap[0] = "Jun"
mymap[1] = "xxx"
... ...
mymap1 := make(map[int]string) // 可以不加容量
mymap := map[string]string{
"one": "Jun",
"tow": "Fer",
"key": "vlaue"
}
// 删除
delete(mymap, "key")
// 遍历
for key, value := range mymap {
fmt.Println("key = ", key)
fmt.Println("value = ", value)
}
**注意:**map 和 切片 传参时传的是指针,因此改变值时,原来的也会改变
结构体
// 大写表示public 小写表示private
type book struct{
name string
autoh string
price int
}
type Student struct {
Name string
Gender string
Age int8
}
结构体之间的嵌套可以看作继承
type Personal struct {
Name string
Age int
Gender string
}
// Student 继承 Personal
type Student struct {
Personal
Classroom string
}
// 与结构体绑定方法
// 父类的方法
func (p Personal) Run() {
fmt.Println(p.Name + "在奔跑~~~~~~~~")
}
// 子类的方法
func (t Student) HaveClass() {
fmt.Println(t.Name + "在" + t.Classroom + "上课")
}
func main() {
var s = Student{
Personal: Personal{
"Colzry",
21,
"男",
},
Classroom: "16班",
}
fmt.Printf("%#v\n", s)
// 调用父类的方法
s.Run()
// 调用自己的方法
s.HaveClass()
}
接口
使用接口来规范结构体的方法
package main
import "fmt"
//定义一个接口
type Operate interface {
start()
stop()
}
type Phone struct {
Name string
}
// 让结构体实现接口
func (p Phone) start() {
fmt.Println(p.Name + "开机")
}
func (p Phone) stop() {
fmt.Println(p.Name + "关机")
}
func main() {
// 实例化接口
var redmi Operate = Phone{Name: "RedMi k40"}
// 调用实现的方法
redmi.start()
redmi.stop()
}
使用空接口来做泛型
var a interface{}
a = 20
fmt.Printf("a的值: %v, a的类型: %T\n", a, a)
a = "Colzry"
fmt.Printf("a的值: %v, a的类型: %T\n", a, a)
a = true
fmt.Printf("a的值: %v, a的类型: %T\n", a, a)
a = []int{1, 2, 3}
fmt.Printf("a的值: %v, a的类型: %T\n", a, a)
b := make(map[interface{}]interface{})
b[4] = true
b["str"] = 25
fmt.Println(b)
c := []interface{}{1, 2, "3", true}
fmt.Println(c)
######## 打印 ##########
a的值: 20, a的类型: int
a的值: Colzry, a的类型: string
a的值: true, a的类型: bool
a的值: [1 2 3], a的类型: []int
map[4:true str:25]
[1 2 3 true]
常用在函数的参数和返回值处
func getObj(value interface{}) interface{} {
return value
}
类型断言
package main
import "fmt"
// 类型断言
func TypePrint(value interface{}) {
// 两种判断类型的方法(if switch),但是switch只能使用 x.(type)
if _, ok := value.(string); ok {
fmt.Println("它居然是个字符串")
} else {
fmt.Println("可惜它不是字符串")
}
switch value.(type) {
case int:
fmt.Println("int类型")
case string:
fmt.Println("string类型")
case bool:
fmt.Println("bool类型")
case []int:
fmt.Println("[]int类型")
default:
fmt.Println("不在列表范围内")
}
}
func main() {
a := 20
TypePrint(a)
b := "Colzry"
TypePrint(b)
}
打印
可惜它不是字符串
int类型
它居然是个字符串
string类型
协程
使用关键字go可以将一个方法(函数)作为协程来使用
func main() {
go test()
for i := 0; i < 10; i++ {
fmt.Println("main ---------------", i)
time.Sleep(time.Millisecond * 500)
}
}
func test() {
for i := 0; i < 10; i++ {
fmt.Println("test ---------------", i)
time.Sleep(time.Millisecond * 1000)
}
}
这样main函数和test函数就能同时运行,但会出现一个问题,main函数会因为先结束而导致test函数没运行完就结束了,这时需要使用异步
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1) // 添加一个协程
go test() // 启动一个协程
for i := 0; i < 10; i++ {
fmt.Println("main ---------------", i)
time.Sleep(time.Millisecond * 500)
}
wg.Wait() // 等待所有协程执行完
}
func test() {
for i := 0; i < 10; i++ {
fmt.Println("test ---------------", i)
time.Sleep(time.Millisecond * 1000)
}
wg.Done() // 完成一个协程
}
Add, Wait, Done 需要一起出现
管道
使用关键字chan定义一个管道(注意:管道也是地址引用型)
var ch chan int // 类型为 int 型
ch = make(chan int, 10) // 给管道ch分配10个空间
ch <- 10 // 将10写入管道ch中
value := <- ch // 取出管道中的第一个值,也可匿名取出 <- ch
close(ch) // 关闭管道
管道中的值遵循先进先出,一旦没有导致取不出或者满了导致放不了都会报错,错误如下
fatal error: all goroutines are asleep - deadlock!
使用for range 遍历管道时要先关闭管道
func main() {
ch := make(chan int, 10)
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
for v := range ch{
fmt.Println(v)
}
}
单向管道
chan<- // 只写
<-chan // 只读
ch1 = make(chan<- int, 10) // 只写的int管道
ch2 = make(<-chan int, 10) // 只读的int管道
// 形参表示
func fn1(ch chan<- int){}
func fn2(ch <-chan int){}
管道和线程的使用
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func fn1(ch chan int){
for i := 1; i <= 10; i++ {
ch <- i
fmt.Println("写入", i, "成功")
time.Sleep(time.Millisecond * 100)
}
close(ch)
wg.Done()
}
func fn2(ch chan int) {
for v := range ch{
fmt.Println("读取", v, "成功")
time.Sleep(time.Millisecond * 10)
}
wg.Done()
}
func main() {
ch := make(chan int, 10)
wg.Add(2)
go fn1(ch)
go fn2(ch)
wg.Wait()
}
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func putNum(numChan chan int) {
for i := 2; i <= 1200000; i++ {
numChan <- i
}
close(numChan)
wg.Done()
}
func getPrime(numChan chan int, primeChan chan int, exitChan chan bool) {
for num := range numChan {
flag := true
for i := 2; i < num; i++ {
if num % i == 0 {
flag = false
break
}
}
if flag {
primeChan <- num
}
}
// 存放素数管道完成数 +1
exitChan <- true
wg.Done()
}
func printPrime(primeChan chan int) {
for v := range primeChan{
fmt.Println(v)
}
wg.Done()
}
func main() {
numChan := make(chan int, 1000) // 存放数字的管道
primeChan := make(chan int, 1000) // 存放素数的管道
exitChan := make(chan bool, 16) // 存放素数管道完成数
count := 20 // 判断素数的协程数
wg.Add(1)
go putNum(numChan)
for i := 0; i < count; i++ {
wg.Add(1)
go getPrime(numChan, primeChan, exitChan)
}
wg.Add(1)
go printPrime(primeChan)
wg.Add(1)
go func() {
for i := 0; i < count; i++ {
<- exitChan // 存放素数管道完成数 -1
}
// 判断存放素数管道完成数都运行完后关闭管道
close(primeChan)
wg.Done()
}()
wg.Wait()
fmt.Println("执行结束......")
}
多路复用
多路复用使用select关键字,通常搭配for使用,让case中的代码随机的执行,也叫并发
package main
import "fmt"
func main() {
intChan := make(chan int, 10)
strChan := make(chan string, 10)
for i := 0; i < 10; i++ {
intChan <- i
strChan <- "hello" + fmt.Sprint(i)
}
for {
select {
case v := <- intChan:
fmt.Println("intChan 读取的数据", v)
case v := <-strChan:
fmt.Println("strChan 读取的数据", v)
default:
fmt.Println("结束")
return
}
}
}
goroutine异常处理
使用defer + recover进行异常捕获
defer func(){
if err := recover(); err != nil {
fmt.Println("发生错误", err)
}
}()
package main
import (
"fmt"
"time"
)
func fn1() {
defer func(){
if err := recover(); err != nil {
fmt.Println("发生错误", err)
}
}()
var name map[int]string
name[0] = "test"
}
func fn2() {
for i := 0; i < 10; i++ {
fmt.Println("hell0", i)
}
}
func main() {
go fn1()
go fn2()
time.Sleep(time.Second * 5)
}