Go语言语法学习
2025-05-05 18:28:31

Go语言基础

一些基础语法先不记了,只记录一些必要的东西

变量声明赋值

4种赋值方式

1
2
3
4
var a int //默认值为0
var b int = 10
var c = 10
d := 10

最简易、最常用、且在函数体中 -> 使用 :=

1
2
3
4
func main() {
a := 100
fmt.Printf("a = %d\n", a)
}

若设置全局变量,则使用除:=外的3种。

多变量同时赋值

1
2
3
4
5
6
7
var a, b int = 10, 20  //同数据类型
var c, d = 30, "kagty1" //不同数据类型
//多行多变量声明写法
var (
e int = 40
f bool = true
)
常量

const定义常量 -> 只读不能改

1
2
3
4
func main() {
const a = 10
fmt.Println(a)
}

const定义枚举类型

1
2
3
4
5
const (
beijing = 1
shanghai = 2
shenzhen = 3
)

iota -> 只能配合 const()进行使用,iota会自动进行累加

1
2
3
4
5
const (
beijing = iota
shanghai
shenzhen
)
函数
1
2
3
4
func demo1(a string, b int) int {}
func demo1(a string, b int) (int, int) {} //多返回值
func demo1(a string, b int) (r1 int, r2 int) {} //r1, r2初始值为0
func demo1(a string, b int) (r1, r2 int) {}
导包

先执行导入包中的init函数

image-20250428173824122

目录架构

1
2
3
4
5
Go_Project
/lib
--lib1.go
--lib2.go
main.go

/lib/lib1.go

1
2
3
4
5
6
7
func init() {
fmt.Printf("lib1_init\n")
}

func Lib1_test() {
fmt.Printf("lib1_test\n")
}

/lib/lib2.go

1
2
3
4
5
6
7
func init() {
fmt.Printf("lib2_init\n")
}

func Lib2_test() {
fmt.Printf("lib2_test\n")
}

main.go

1
2
3
4
5
6
import "Go_Project/lib"

func main() {
lib.Lib1_test()
lib.Lib2_test()
}

Lib1_testLib2_test函数名的首字母大写代表着这个函数对外共享

匿名导包

我们知道,如果导入了某个包,但是不使用其中的方法,Go语言会报错,使用 _ "xxxx"可以避免,即使不显示调用包中的函数方法,也会去调用包中的init方法

1
2
3
4
5
6
import "Go_Project/lib"

func main() {
lib.Lib1_test()
lib.Lib2_test()
}

image-20250429085921093

别名导包

fmt别名aaa,使用aaa.Printf进行打印输出

1
2
3
4
5
6
7
import (
aaa "fmt"
)

func main() {
aaa.Printf("kagty1\n")
}
直接调用

使用. "包名"就可以直接调用Printf而不用写fmt.Printf

1
2
3
4
5
6
7
import (
. "fmt"
)

func main() {
Printf("ka\n")
}
指针
1
2
3
4
5
6
7
8
9
10
func main() {
var a int = 10
b := &a
fmt.Println(b)
fmt.Printf("%d\n", *b)
}

//输出
0x140000a2008 //a的内存地址
10 //*&a解引用
defer关键字

若使用defer关键字修饰,则在一个函数中最后执行

函数执行之后出栈执行,在一个函数中,return的调用在defer之前

image-20250430142311314

数组
1
2
3
4
5
6
//固定长度数组 -> 值拷贝传参
var array1 [10]int
array2 [10]int := {1,2,3,4}

//动态数组(slice) -> 引用传递传参
array3 := []int{1,2,3,4} //分配几个值,数组长度就是几
slice切片

1、slice的声明

1
2
3
4
5
6
7
8
9
10
//声明一个切片
slice1 := []int{1, 2, 3}

//使用make开辟空间
var slice2 []int
slice2 = make([]int, 3)

//一键声明
var slice3 []int = make(int[], 3)
slice4 := make([]int 3) //最常见

2、slice容量追加与截取

1
2
var slice4 = make([]int, 3, 5)  //长度为3,容量为5
slice4 = append(slice4, 1) //直接在前三个数据之后追加进行扩容

切片的截取

1
2
3
4
slice5 := []int{1,2,3}
num := slice5[0:2] //截取前两位元素
num := slice5[:2] //截取前两位元素
num := slice5[2:] //截取从第二到最后一个元素
map

1、map的声明

1
2
3
4
5
6
var map1 map[string]string  //声明map, key->string, value->string类型
map1 = make(map[string]string, 10) //给map分配数据空间
map1["one"] = "golang" //给map1赋值

//一键初始化
map2 := map[string]string{"one": "golang"}

2、map的使用方式

1
2
3
4
5
6
map3 := make(map[string]string)
//赋值
map3["China"] = "Shanghai"
map3["Japan"] = "Tokyo"
//删除
delete(map3, "China")
struct结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//定义结构体
type book struct {
title string
auth string
}

//使用结构体
func main() {
var book1 book
book1.title = "Golang"
book1.auth = "kagty1"
fmt.Printf("book1: %v\n", book1)
}
------------------------------------
//结构体传参
func changeBook (book *Book) {
//指针传递
book.auth = "zhangsan"
}

func main() {
changeBook(&book1)
}
面向对象

类名/属性的首字母大写 -> public

​ 首字母小写 -> private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Hero struct {
Name string
Age string
}

func (this Hero) Show() {
fmt.Printf("%s is %s!\n", this.Name, this.Age)
}

//使用指针进行修改
func (this *Hero) SetName(newName string) {
this.Name = newName
}

func (this *Hero) SetAge(newAge string) {
this.Age = newAge
}

// 使用结构体
func main() {
var hero Hero
hero.Name = "kagty1"
hero.Age = "18"
hero.Show()
hero.SetName("kagty2")
hero.SetAge("20")
hero.Show()
}

创建子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Hero struct {
Name string
Age string
}

func (this Hero) Show() {
fmt.Printf("%s is %s!\n", this.Name, this.Age)
}

//使用指针进行修改
func (this *Hero) SetName(newName string) {
this.Name = newName
}

func (this *Hero) SetAge(newAge string) {
this.Age = newAge
}

// 使用结构体
func main() {
s := Hero{"kagty3", "22"} //创建一个子类s,有点像java中的构造函数
s.Show()
}
Interface

接口作用

1、多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Animal interface { 
Sound() string
}

type Dog struct{}
func (d Dog) Sound() string { return "Woof!" }

type Cat struct{}
func (c Cat) Sound() string { return "Meow!" }

func main() {
animals := []Animal{Dog{}, Cat{}}
for _, a := range animals { fmt.Println(a.Sound()) }
}

2、解耦合

其实感觉这个用法和java很像,不同的类继承接口去实现接口中的方法

1
2
3
4
5
6
7
8
9
10
11
12
type Logger interface { Log(string) }

type ConsoleLogger struct{}
func (c ConsoleLogger) Log(msg string) { fmt.Println(msg) }

type FileLogger struct{}
func (f FileLogger) Log(msg string) { /* 写入文件 */ }

func main() {
var logger Logger = ConsoleLogger{}
logger.Log("控制台日志") // 可随时替换为FileLogger
}

3、空接口

空接口不实现任何方法

使用场景如下所示

(1) 某func需要接收多种类型的参数,为了防止重复构造相同的函数,就引入空接口可以解决

比如想要重写fmt.Println(),如果想要打印多种类型的数据,可能如下所示重写,但是显得非常冗余

1
2
3
4
5
6
7
8
9
10
11
func Print1(arg int) {
fmt.Println(arg)
}

func Print2(arg string) {
fmt.Println(arg)
}

func Print3(arg bool) {
fmt.Println(arg)
}

故可以使用空接口,利用了空接口可以承载任意类型的数据的特性

1
2
3
4
5
6
7
8
9
func PrintAny(args interface{}) {
for _, arg := range args {
fmt.Println("arg = ", arg)
}
}

func main() {
PrintAny(1, "kagty1", 8.8)
}
断言

会判断a的类型是否为string,如果是string符合预期,才会进行进一步的逻辑

1
2
3
4
var a interface{} = "kagty1"
if str, ok := a.(string); ok {
fmt.Println("str = ", str)
}

比如某参数用户可控,为了防止攻击者进行非法攻击,会限制用户输入数据的类型

比如 productId用户可控,则限制productId的类型必须为Int才会执行具体的业务逻辑

反射

使用reflect库 -> import ("reflect")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import (
"fmt"
"reflect"
)

type User struct {
Name string
Age int
}

func (this User) Call() {
fmt.Println("user is called")
}

func main() {
user := User{"kagty1", 20}
ReflectDemo(user)
}

func ReflectDemo(input interface{}) {
//TypeOf(类).Name()->反射获取类名
inputType := reflect.TypeOf(input)
fmt.Println("imputType:", inputType.Name())

//ValueOf(类)->反射获取类中的值
inputValue := reflect.ValueOf(input)
fmt.Println("imputValue:", inputValue)

//inputType.Field(i)->类中字段, inputType.Field.Interface()->类中字段的值
for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i)
value := inputValue.Field(i).Interface()
fmt.Printf("field:%v value:%v\n", field.Name, value)
}
}
标签

作用类似于java中的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type User struct {
Name string `info:"User's name"`
Age int `info:"User's age"`
}

func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
tagString := t.Field(i).Tag.Get("info")
fmt.Println("info: ", tagString)
}
}

func main() {
var user User
findTag(&user)
}
1
2
3
需要注意的是
`info:"User's name"` ✅
`info: "User's name"` ❌ -> 不能出现空格,否则无法正确解析
标签在json中的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}

func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
tagString := t.Field(i).Tag.Get("info")
fmt.Println("info: ", tagString)
}
}

func main() {
user := User{"kagty1", 20}

//编码:结构体 -> json
jsonStr, err := json.Marshal(user)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonStr))

//解码:json -> 结构体
user2 := User{}
err = json.Unmarshal(jsonStr, &user2)
if err != nil {
fmt.Println(err)
}
fmt.Println(user2)
}

输出

1
2
{"name":"kagty1","age":20}
{kagty1 20}
Goroutine

关键字: go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func task() {
i := 0
for {
i++
fmt.Println("Goroutine is running: ", i)
time.Sleep(1 * time.Second)
}
}

func main() {
go task()

i := 0
for {
i++
fmt.Println("Main is running: ", i)
time.Sleep(1 * time.Second)
}
}

执行结果如下所示,可以看到并行执行的效果:

image-20250505111304540

Channel

通过make一个channel,作为中间商,负责两个gorountine间的通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
//创建一个channel
c := make(chan int)

go func() {
defer fmt.Println("Goroutine exited")
fmt.Println("Goroutine is running")
c <- 666
}()

//将值666赋值给main线程的num变量
num := <-c
fmt.Println("num is: ", num)
}

带有缓存的channel -> 防止发生阻塞

定义带有缓存的channel -> make(chan int, 3) -> 设置缓存大小为3的channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
//创建一个带有缓存大小为3的channel
c := make(chan int, 3)

go func() {
defer fmt.Println("Goroutine exited")
for i := 0; i < cap(c); i++ {
c <- i
fmt.Println("Goroutine is running, i is: ", i)
}
}()

time.Sleep(1 * time.Second)

for i := 0; i < cap(c); i++ {
num := <-c
fmt.Println("The number is: ", num)
}
}

执行结果:

1
2
3
4
5
6
7
Goroutine is running, i is:  0
Goroutine is running, i is: 1
Goroutine is running, i is: 2
Goroutine exited
The number is: 0
The number is: 1
The number is: 2

main线程接受之前可以在缓存中存储,从而避免造成堵塞

关闭channel -> close(c)

1
2
3
4
5
6
7
for {
if data, ok := <-c; ok { //ok为true代表channel开启,ok为false代表channel关闭
fmt.Println(data)
} else {
break
}
}

若不关闭channel,而main线程一直在获取,在拿到匿名函数子线程赋值的所有值后会发生死锁问题

image-20250505145826229

channelrange

如上使用for{}if语句过于冗余,使用range可以更加简洁

1
2
3
for data := range c {
fmt.Println(data)
}

channelselect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func calc(c, quie chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x = y
y = x + y

//当quit能读时说明go func()中的0已经写入了quit channel中,说名匿名函数线程已结束
case <-quit:
fmt.Println("End calc..")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)

go func() {
for i := 0; i < 6; i++ {
fmt.Println(<-c)
}

quit <- 0
}()

calc(c, quit)
}
Go Module初始化项目

配置Go国内代理:https://blog.csdn.net/qq_41168737/article/details/137160590

当使用GoLand工具创建一个项目后,会自动给我们创建一个go.mod文件

image-20250505162727282

当我们需要使用,如github中的某个项目中的某段代码时,如github.com/aceld/zinx中的代码

则只需要使用go get -u github.com/aceld/zinx即可

image-20250505163427015

下载成功后就回多出一个go.sum文件

image-20250505163731780

这之后直接用脱下来的那个项目的代码就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import (
"fmt"
"github.com/aceld/zinx/ziface"
"github.com/aceld/zinx/znet"
)

// PingRouter MsgId=1
type PingRouter struct {
znet.BaseRouter
}

// Ping Handle MsgId=1
func (r *PingRouter) Handle(request ziface.IRequest) {
//read client data
fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
}

func main() {
//1 Create a server service
s := znet.NewServer()

//2 configure routing
s.AddRouter(1, &PingRouter{})

//3 start service
s.Serve()
}

若运行时依然发生某些错误,可以使用命令 go mod tidy 进行修复,它会自动下载缺失的依赖和未使用的依赖。

若要替换引入依赖版本,执行命令:

1
go mod edit -replace=旧版本=新版本
上一页
2025-05-05 18:28:31
下一页