变量定义

Go中有很多种定义变量的方式,集百家之长,最标准的应该是先定义,后赋值。

注意:在Go中,局部变量定义了就必须使用,否则编译不成功

标准定义方式,关键字 变量名称 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main() {
// 先声明
var name string
// 后赋值
name = "xpctf"
fmt.Println(name)
}

定义并赋值, 关键字 变量名 类型 = 值

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
var name string = "xpctf"
fmt.Println(name)
}

省略类型,Go会自动根据值来推断类型 关键字 变量名 = 值

这种定义方式是最常用,项目中使用最多的

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
var name = "xpctf"
fmt.Println(name)
}

最简单的定义方式(只能在方法里面使用

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
name := "xpctf"
fmt.Println(name)
}

全局变量,在函数外部的就是全局变量,全局变量必须使用varconst关键字,不能使用:=

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

var age = 18

func main() {
name := "xpctf"
fmt.Println(name, age)
}

一次定义多个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

func main() {
// 第一种方式
var name1, name2 = "xpctf", "xpctf1"
fmt.Println(name1, name2)
// 第二种方式, 类型省略也可以
var (
name3 string = "xpctf3"
name4 = "xpctf4"
)
fmt.Println(name3, name4)
}

定义常量,使用const关键字定义

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

const version string = "1.1.1"

func main() {
const version1 = "1.1.2"
fmt.Println(version, version1)
}

命名规范

核心思想:首字母大写的变量、函数、方法、属性可在包外(跨包)进行访问

类似java的公开和私密,首字母大写就是公开,首字母小写就私密

package概念

Go中是以package(包)为单位的,在同一个包内可以直接使用其他文件定义的变量和方法

package useruser/user.go中定义了Nameage变量,在user/info.go中可以直接使用,不需要导包

前提是user.go和info.go都在同一目录下,并且都写了package user

总结:Go 是以包为单位控制可见性的:同包共享所有内容,跨包只能访问导出的(首字母大写)成员。

输入输出

输出

在go中,常用的输出就fmt.Printlnfmt.Printf

Println是换行输出,可以一次性输出多个值,使用空格分隔,和python的print一样

fmt.Printf是格式化输出,用的最多,但是不会自动换行, 可以在末尾加上\n

常用格式有: %s %d %f %.2f %T %v %#v 分别对应:字符串 整数 浮点数 保留2位小数点 类型 任意类型 空文本输出双引号

Sprintf和Printf一样,但是返回格式化后的文本, 可以使用变量接收

输入

Go中使用fmt.Scan来接收标准输入,和python的input一样,但是没有输入提示,要自己输出

如果输入的值不能转化为对应类型,那么就会报错,但是会输出变量的默认值,比如age输入a会输出0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
var (
name string
age int
)
fmt.Print("你叫什么名字:")
fmt.Scan(&name)
fmt.Print("你多大了:")
fmt.Scan(&age)
fmt.Printf("你好, %s,%d岁的帅哥\n", name, age)

}

基础数据类型

基础数据类型有:整数类型 浮点数类型 复数类型 字符串类型 布尔类型

整数类型

int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64

int后面的数字就是能存储的最大数据长度,int8是8个字节(8个二进制)

int取决于平台,32位操作系统就是int32 64位操作系统就是int64

uint和int同理,但它是无符号整数,只能用来存正整数,不能存负数

什么时候用int8或者int32? 在内存很匮乏并且长度短的项目中使用

浮点数类型

就一个float32和float64, 没什么好说的,用的也少

数组和切片

Go的数组需要定义长度,并且长度不能改变, 所以在Go中不常用,常用的是切片,和python中的列表类似

1
2
3
4
5
6
7
func main() {
nameList := [3]string{"张三", "李四", "王五"}
fmt.Println(nameList) // 输出整个数组
nameList[0] = "张三改名了" // 修改指定元素
fmt.Println(nameList[0]) // 输出下标为0的元素
fmt.Println(len(nameList)) // 获取数组长度
}

切片可以动态增加和删除元素, 不用定义长度,也是由数组演变而来

1
2
3
4
5
6
7
func main() {
nameList := []string{"张三", "李四"} // 这种是只能存储string类型的元素
fmt.Println(nameList)
nameList = append(nameList, "王二", "麻子")
nameList = append(nameList, []string{"猴子", "八戒"}...)
fmt.Println(nameList)
}

为了更像python中的列表,能存储任意类型,可以使用any类型定义

1
2
3
4
5
6
7
func main() {
nameList := []any{"张三", "李四"}
fmt.Println(nameList)
nameList = append(nameList, 18, 20)
fmt.Println(nameList)
fmt.Println(len(nameList))
}

创建全0切片, 默认长度为10,元素值都为0, 这样在可预估的长度条件下就更高效,不用一直扩充

它还有一个容量参数,不写为长度,容量是底层数组容量, 只要不超过容量都不会进行底层数组扩充

假设有一个场景: 一个班级最多有100个学员,就可以像下面这样定义, 不要太大也不要太小

1
2
indexList := make([]int, 100)
fmt.Println(indexList)

map

map在每种语言都存在,只是叫法不一样,js叫对象,python叫字典,就是有一个键和一个值(key-value)组成的集合

1
2
3
4
5
6
7
8
9
10
func main() {
userMap := map[string]string{
"张三": "张三",
"李四": "李四",
}
fmt.Println(userMap)
delete(userMap, "李四") // 删除
userMap["王二"] = "王二"
fmt.Println(userMap)
}

还有其他定义方式,喜欢那种用那种, 但make用的多一点

1
2
3
4
5
6
7
8
9
10
func main() {
userMap := map[string]string{}
var ageMap = make(map[string]int, 100)
fmt.Println(userMap)
delete(userMap, "李四") // 删除
userMap["王二"] = "王二"
fmt.Println(userMap)
ageMap["王二"] = 25
fmt.Println(ageMap)
}

判断语句

使用if写一个成绩等级判断,大于90为优, 80-90为良,60-70为中,小于60为差

在go中且运算符为&&,或运算符为||,还有一个取反运算符,!(表达式);对应python的and、or、一样

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
var score int
score = 79
if score > 90 {
fmt.Println("优")
} else if score >= 80 && score <= 90 {
fmt.Println("良")
} else if score >= 70 && score <= 80 {
fmt.Println("中")
} else {
fmt.Println("差")
}
}

go中有switch语句,python中没有,所以它不常用,基本上if…else if…else就够用了

go和java有一点不同,go的switch有一个条件满足就跳出,不会进入自动下一层判断

如果要进入下层判断,可以使用fallthrough关键字, 和java恰恰相反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
var score int
fmt.Printf("请输入你的成绩:")
fmt.Scan(&score)
switch {
case score > 90:
fmt.Println("优")
case score >= 80 && score <= 90:
fmt.Println("良")
case score >= 70 && score <= 80:
fmt.Println("中")
case score >= 0 && score <= 70:
fmt.Println("差")
fallthrough // 会输出 输入错误
default:
fmt.Println("输入错误")
}
}

switch还有一种写法,表达式写在switch后面,case是表达式的运算结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
var score int
fmt.Printf("请输入你的成绩:")
fmt.Scan(&score)
switch score {
case 90:
fmt.Println("优")
case 80:
fmt.Println("良")
case 70:
fmt.Println("中")
case 60:
fmt.Println("差")
default:
fmt.Println("输入错误")
}
}

for循环

Go只有for循环,没有while循环和do…while循环

for循环很简单,和其他语言一样,也有i–

1
2
3
4
5
6
7
func main() {
var count int
for i := 1; i <= 100; i++ {
count += i
}
fmt.Println(count)
}

老样子,99乘法表练练嵌套for循环, 在学校上学就是这么写的

1
2
3
4
5
6
7
8
func main() {
for i := 1; i <= 9; i++ {
for j := 1; j < i+1; j++ {
fmt.Printf("%d*%d=%d\t", j, i, i*j)
}
fmt.Println()
}
}

死循环

1
2
3
4
5
6
func main() {
for {
fmt.Println(time.Now())
time.Sleep(1 * time.Second)
}
}

模拟while循环, 没人会这么写

1
2
3
4
5
6
7
func main() {
i := 1
for i <= 100 {
fmt.Println(i)
i++
}
}

遍历切片

基础写法和js一样,但go中也有range的写法

1
2
3
4
5
6
func main() {
list := []string{"张三", "李四"}
for i := 0; i < len(list); i++ {
fmt.Println(list[i])
}
}

range写法, 可以迭代全部可以迭代数组、切片、字符串、map、channel

对于map来说,range返回的是key和value, 如果不需要其中一个,可以使用_占位符来忽略

1
2
3
4
5
6
func main() {
list := []string{"张三", "李四"}
for idx, item := range list {
fmt.Println(idx, item)
}
}

跳过循环

break是跳出整个for循环体

continue是跳过本次循环中没有执行的部分

和python一模一样的

函数参数

函数是一段封装了特定功能的可重用代码块,用于执行重复的任务或者计算,函数接收一个或多个参数并返回一个或多个结果

Go使用func关键字定义函数,下面是一个最简单的函数;main函数是入口函数,由Go调用

1
2
3
func main() {
fmt.Println("Hello World")
}

go中定义函数参数由很多中方式,基础是参数名称 参数类型, 如果类型相同可以省略前面的参数类型

1
2
3
func login(username, password string)  {
fmt.Println(username, password)
}

接收不确定参数个数的参数,可以使用...类型的方式,就像Println函数一样

1
2
3
func Println(a ...any) (n int, err error) {
return Fprintln(os.Stdout, a...)
}

函数返回值

函数的返回值类型在参数后面定义,可以返回一个或多个返回值

1
2
3
func add(a, b int) int {
return a + b
}

多个返回值使用小括号包裹,指定类型

1
2
3
4
5
6
func div(a, b int) (int, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}

定义返回名称,可以使用直接使用return关键字,Go会自动返回返回名称的当前值

1
2
3
4
5
6
7
func div(a, b int) (r int, ok bool) {
if b == 0 {
return 0, false // 显式返回
}
r = a / b
return // go自动返回r和ok的当前值
}

匿名函数

Go不支持函数内再定义函数,但是可以使用匿名函数来实现相同的作用

1
2
3
4
5
6
7
8
9
10
func userInfo() (name string, age int) {
GetName := func() (string, int) {
GetAge := func() int {
return 24
}
return "枫枫", GetAge()
}
name, age = GetName()
return
}

匿名函数只能在定义的函数体内使用,不能在其他函数内使用;同一个文件不行,首字母大写也不行

并且匿名函数只能在函数体内定义,不能在外部定义, 匿名函数的上级必须是有个func, 匿名函数内可以定义匿名函数

高阶函数

在Go中,可以把一个函数当成参数传递到另一个函数中,函数也可以返回另外一个函数

下面是一个计数器,实现调用一次id就加1的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func makeGetId() func() {
var id = 1
return func() {
fmt.Println(time.Now(), id)
id++
}
}

func main() {
getId := makeGetId()
getId()
getId()
getId()
}

// 输出
// 2025-05-18 00:14:20.9754681 +0800 CST m=+0.005441501 1
// 2025-05-18 00:14:21.0083377 +0800 CST m=+0.038311101 2
// 2025-05-18 00:14:21.0083377 +0800 CST m=+0.038311101 3

值传递和引用传递

值传递就是把变量拷贝一份传递给变量,函数内修改不会影响外部变量的值

引用传递就是传递变量的内存地址(指针)给函数,函数内修改会影响外部变量的值

在go中使用&变量名可以获取到变量的内存地址,使用*指针获取内存地址的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func value(a int) {
a = 100
fmt.Println(a, &a)
}

func pointer(a *int) {
*a = 100
fmt.Println(*a, a)
}
func main() {
a := 1
fmt.Println(a, &a)
value(a)
fmt.Println(a, &a)
pointer(&a)
fmt.Println(a, &a)
}

// 输出
//1 0xc00001a0a8
//100 0xc00001a0c8
//1 0xc00001a0a8
//100 0xc00001a0a8
//100 0xc00001a0a8

四个特殊函数

init函数是一个特殊的函数,存在以下特征:

  • 在main函数之前执行,自动调用, 并且不能被其他函数调用
  • 不能作为其他函数的参数传入
  • 不能有传入参数和返回值
  • 一个go文件可以有多个init函数,谁在前面谁先执行

总结: init函数主要是用于非main包的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func init() {
fmt.Println("init-1")
}
func init() {
fmt.Println("init-2")
}
func init() {
fmt.Println("init-3")
}
func main() {
fmt.Println("main")
}
// 输出
// init-1
// init-2
// init-3
// main

defer函数也是一个特殊的函数,存在以下特性:

  • 使用defer定义,用于注册延迟调用
  • defer 函数会在 函数“结束”之前调用
  • 多个defer函数,遵循先进后出,先定义的后调用
  • defer函数中的变量,在defer声明时就赋值了

总结:defer函数用于资源释放和异常处理,保证资源在return前被释放

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
defer func() {
fmt.Println("defer1")
}()
defer func() {
fmt.Println("defer2")
}()
fmt.Println(time.Now())
}
// 输出
//2025-05-18 00:50:59.3556863 +0800 CST m=+0.006081401
//defer2
//defer1

recover函数也是一个特殊的函数,存在以下特征:

  • recover函数只能在defer函数中使用
  • recover() 返回 panic 的传入值(异常信息),没有异常时返回 nil

总结:用来捕获(恢复)panic引发的异常, 如果程序中发生了 panic,而且用 recover() 捕获了它,程序就不会崩溃,可以继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func safeDivide(a, b int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到异常:", r)
}
}()

fmt.Println("结果:", a/b) // b为0时会panic
}

func main() {
safeDivide(10, 2) // 正常输出结果
safeDivide(10, 0) // 会捕获到异常,防止程序崩溃
fmt.Println("程序继续运行")
}

panic函数也是一个特殊的函数,存在以下特征:

  • 抛出运行时错误,导致程序崩溃(除非被 recover 捕获)
  • 程序立即停止当前函数,逐层向上展开调用栈

总结:panic抛出一个致命错误,让程序停止执行;类似python中的raise,但是go不鼓励使用panic来做常规错误处理,而是使用 error 类型返回错误;Go 标准库里,函数通常返回一个 error 类型作为最后一个返回值,调用方通过判断这个错误值是否为 nil 来判断是否有错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func testRecover() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
}
}()

panic("发生了致命错误!") // 触发panic
fmt.Println("这行代码不会执行")
}

func main() {
testRecover()
fmt.Println("程序继续执行")
}