前言:Go 的一些必备知识
1. Go 语言命名
Go的函数、变量、常量、自定义类型、包(package)
的命名方式遵循以下规则:
- 首字符可以是任意的Unicode字符或者下划线
- 剩余字符可以是Unicode字符、下划线、数字
- 字符长度不限
Go 语言代码风格及开发事项
代码每一行结束后不用写分号
推荐使用驼峰式命名
Go 编译器是一行一行进行编译的,因此我们一行就写一条语句,不能把多条语句写在一起
Go 定义的变量 或 import 的包如果没有用到,代码不能编译通过
左括号必须紧接着语句不换行
func main() { fmt.Println("Hello Go") }
2. Go 关键字
Go 一共有 25 个关键字
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
37 个保留字
Constants: true false iota nil
Types: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
Functions: make len cap new append copy close delete
complex real imag
panic recover
3. Go 语言声明
有四种主要的声明方式
var(声明变量), const(声明常量), type(声明类型) ,func(声明函数)
Go的程序是保存在多个.go文件中,文件的第一行就是 package XXX
声明,用来说明该文件属于哪个包(package),package声明下来就是 import
声明,再下来是类型,变量,常量,函数的声明。
可见性
- 声明在函数内部,是函数的本地值,类似private
- 声明在函数外部,是对当前包可见(包内所有.go文件都可见)的全局值,类似protect
- 声明在函数外部且首字母大写是所有包可见的全局值,类似public
4. Printf 和 Println 的区别
这个其实和 Java 有点像, Print 不自动换行,而 Println 会自动换行
还有就是变量输出的方法,如下:
func main(){
a:=10
b:=20
fmt.Println("a=",a, "b=",b) // a= 10 b= 20
fmt.Printf("a=%d, b=%d", a, b) // a=10, b=20
}
5. Init 和 main 函数
在 Go 语言中,init()
函数和 main()
函数是程序执行的两个关键入口点,它们有明确的执行顺序和作用:
举个例子:
func init() {
fmt.Println("imp-init1()")
}
func init() {
fmt.Println("imp-ini2()")
}
func main() {
fmt.Println("main()")
}
// 输出
imp-init1()
imp-ini2()
main()
① init()
函数特点
- 自动执行:无需显式调用,在程序启动时自动运行。
- 包级别:每个包(包括依赖包)都可以包含一个或多个
init()
函数。 - 执行顺序:
- 按包的依赖关系递归执行(先执行导入包的
init()
)。 - 同一包中,按文件名的字典序执行不同文件中的
init()
。 - 同一文件中,按代码中出现的顺序执行多个
init()
。
- 按包的依赖关系递归执行(先执行导入包的
- 用途:
- 初始化包级变量(尤其是复杂初始化)。
- 注册操作(如数据库驱动、插件)。
- 执行一次性配置(如读取配置文件)。
- 无参数无返回值:
func init() { ... }
② main 函数特点
- 程序入口:
main
包中的main()
是唯一程序入口。 - 执行时机:在所有依赖包的
init()
完成后执行。 - 唯一性:整个程序只能有一个
main()
函数(位于package main
)。 - 无参数无返回值:
func main() { ... }
关键区别
特性 | init() 函数 |
main() 函数 |
---|---|---|
所在包 | 任意包(包括依赖包) | 必须在 main 包 |
数量 | 每个包可有多个 init() |
整个程序仅一个 main() |
调用方式 | 自动执行 | 自动执行(程序入口) |
执行顺序 | 在导入的包初始化后、main() 前 |
在所有 init() 完成后执行 |
典型用途 | 初始化包级状态、注册操作 | 主程序逻辑入口 |
注意事项:
- 避免滥用
init()
- 复杂的初始化逻辑应封装成显式函数调用,而非全放在
init()
中。 - 防止隐式依赖导致代码难以追踪。
- 复杂的初始化逻辑应封装成显式函数调用,而非全放在
- 慎用全局状态:
init()
中初始化的包级变量相当于全局状态,可能引发并发问题。 - 错误处理:
init()
中发生错误通常通过panic
终止程序,需谨慎处理(如配置文件不存在)。 - 明确初始化顺序:当多个包有相互依赖的
init()
时,需确保执行顺序符合预期(通过调整导入顺序)。
一、Go 工具链
Go 提供了强大而完整的命令行工具链,让开发者能够高效地开发、测试、构建和部署 Go 应用程序。
1. 核心命令概览
$ go
Go is a tool for managing Go source code.
Usage:
go command [arguments]
The commands are:
build compile packages and dependencies
clean remove object files
doc show documentation for package or symbol
env print Go environment information
bug start a bug report
fix run go tool fix on packages
fmt run gofmt on package sources
generate generate Go files by processing source
get download and install packages and dependencies
install compile and install packages and dependencies
list list packages
run compile and run Go program
test test packages
tool run specified go tool
version print Go version
vet run go tool vet on packages
Use "go help [command]" for more information about a command.
Additional help topics:
c calling between Go and C
buildmode description of build modes
filetype file types
gopath GOPATH environment variable
environment environment variables
importpath import path syntax
packages description of package lists
testflag description of testing flags
testfunc description of testing functions
Use "go help [topic]" for more information about that topic.
go env
:用于打印Go语言的环境信息。go run
:编译并运行命令源码文件。go get
:根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。go build
:用于编译我们指定的源码文件或代码包以及它们的依赖包。go install
:编译并安装指定的代码包及它们的依赖包。go clean
:删除掉执行其它命令时产生的一些文件和目录。go doc
:可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。go test
:用于对Go语言编写的程序进行测试。go list
:列出指定的代码包的信息。go fix
:把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。go fmt
:格式化代码go vet
:是一个用于检查Go语言源码中静态错误的简单工具。go tool
:工具集。go mod
:模块管理
2. 举例说明
① go run —— 编译运行
go run main.go # 运行单个文件
go run main.go utils.go # 运行多个文件
go run . # 运行当前包的所有 .go 文件
go run main.go arg1 arg2 # 传递参数给程序
② go fmt —— 代码格式化
// 格式化前
package main
import "fmt"
func main() {
fmt.Println("Hello");fmt.Println("World")
}
// 格式化后 (go fmt)
package main
import "fmt"
func main() {
fmt.Println("Hello")
fmt.Println("World")
}
③ go test —— 运行测试
go test # 运行当前目录所有测试
go test ./package # 运行指定包的测试
go test -v # 显示详细测试信息
go test -cover # 运行覆盖率测试
# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
比如:
// math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
// 运行测试
go test -v
// 运行基准测试
go test -bench=.
④ go vet —— 代码静态分析
go vet # 分析当前包
go vet github.com/user/project # 分析指定包
go vet -v # 显示详细信息
⑤ go tool —— 工具集
go tool pprof # 性能分析工具
go tool cover # 覆盖率工具
go tool nm # 符号表查看
go tool objdump # 反汇编
go tool link # 链接器
⑥ go mod —— 模块管理
go mod init module_name # 初始化新模块
go mod tidy # 添加依赖
go mod graph # 查看依赖图
go mod verify # 验证依赖
go mod download # 下载依赖
go mod why package_name # 查看模块信息
# 具体操作
# 1. 创建新项目
mkdir myproject && cd myproject
go mod init github.com/user/myproject
# 2. 添加外部依赖
go get github.com/gin-gonic/gin
# 3. 整理依赖
go mod tidy
3. 完整开发流程
# 1. 初始化项目
mkdir myapp && cd myapp
go mod init github.com/user/myapp
# 2. 编写代码
# 创建 main.go 文件
# 3. 快速测试运行
go run main.go
# 4. 格式化代码
go fmt ./...
# 5. 静态分析
go vet
# 6. 编写测试
# 创建 *_test.go 文件
# 7. 运行测试
go test -v
# 8. 构建可执行文件
go build -o myapp main.go
# 9. 安装到系统
go install
组合命令
# 一键格式化、分析、测试
go fmt ./... && go vet ./... && go test ./...
跨平台编译
GOOS=linux GOARCH=amd64 go build main.go # 编译 Linux 版本
GOOS=windows GOARCH=amd64 go build main.go # 编译 Windows 版本
GOOS=darwin GOARCH=amd64 go build main.go # 编译 macOS 版本
性能优化编译
go build -ldflags "-s -w" main.go # 优化编译
go build -ldflags "-X main.version=1.0.0" main.go# 指定版本信息
二、基础语法
1. 变量 & 常量 申明
变量背景:程序运行过程中的数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所
1.1 常见的变量定义方式
变量名称命名要求:由 字母、数字、下划线组成,其中首个字符不能为数字,而且 Go 语言中 关键字 和 保留字 都不能用作变量名
Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false
。 切片、函数、指针变量的默认为nil
。
当然我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:
var 变量名 类型 = 表达式
① var 声明和定义变量
var age int
var name string = "zs"
// 批量声明
var(
email string = "@12.com"
phone int = 123
flag bool = true
namex = "zs"
)
var a1, a2, a3 string // 这样也可以
② 类型推导 定义变量
有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。
var name = "ls"
③ 短变量声明
在函数内部,可以使用更简略的 := 方式声明并初始化变量。
// 简短声明
:a = 10
:b = 20
- 注意:短变量
:=
只能用于声明局部变量,不能用于全局变量
如下是变量作用域:
// 全局变量
var globalVar = "全局变量"
func main() {
// 局部变量
localVar := "局部变量"
fmt.Println(globalVar, localVar)
// 块级作用域
{
blockVar := "块级变量"
fmt.Println(blockVar)
}
// fmt.Println(blockVar) // 错误:blockVar 作用域已结束
}
④ 匿名变量
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)
。 匿名变量用一个下划线_表示,例如:
func foo() (int, string) {
return 10, "Q1mi"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}
匿名变量 不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。 (在Lua等编程语言里,匿名变量也被叫做 哑元变量。)
注意事项:
- 函数外的每个语句都必须以关键字开始(var、const、func等)
:=
不能使用在函数外。_
多用于占位,表示忽略值。
注意:go 语言中变量定义以后必须要使用,而且同一作用域内不支持重复声明
func main(){
var a = "aa"
fmt.Println(a) // 不使用就会提示错误
}
补充:下面赋值正确的是:
- A. var x = nil B. var x interface{} = nil
- C. var x string = nil D. var x error = nil
参考答案及解析:BD。这道题考的知识点是 nil。nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。强调下 D 选项的 error 类型,它是一种内置接口类型,看它的源码就知道,所以 D 是对的。
1.2 常量声明
相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var
换成了const
,常量在定义的时候必须赋值。
func main() {
// 1. 基本常量声明
const pi = 3.14159
const maxUsers = 1000
// 2. 指定类型常量
const version string = "1.0.0"
const port int = 8080
// 3. 批量声明
const (
appName = "MyApp"
appVersion = "2.0"
debugMode = true
)
fmt.Println(pi, maxUsers, version)
}
- 注意:常量定义之后必须要赋值,不能先声明再赋值
const
同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:
const (
n1 = 100
n2
n3
)
上面示例中,常量n1、n2、n3
的值都是100
1.3 const 常量结合 iota 使用
iota 是 Go 语言的常量计数器,只能在常量的表达式中使用(否则会出现 编译错误)
- iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次
- iota 可理解为 const 语句块的行索引,使用
iota
能简化定义,在定义枚举时很有用。
举个例子
// 自增长
const (
Monday = iota // 0
Tuesday // 1
Wednesday // 2
Thursday // 3
Friday // 4
Saturday // 5
Sunday // 6
)
常见的 iota
示例
① 使用 _
跳过某些值
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
③ 带步长的 iota
const (
A = iota * 10 // 0
B // 10
C // 20
D // 30
)
//这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
④ 多个 iota
定义在一行
const (
a, b = iota + 1, iota + 2 //1,2
c, d //2,3
e, f //3,4
)
2. 数据类型
Go 预言者的数据类型一般分为两种:
- 基本数据类型:整型、浮点型、布尔型、字符串
- 复合数据类型:数组、切片、结构体、函数、map、通道(channel)、接口
Golang 更明确的数字类型命名,支持 Unicode,支持常用数据结构。
类型 | 长度(字节) | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | uint8 |
rune | 4 | 0 | Unicode Code Point, int32 |
int, uint | 4或8 | 0 | 32 或 64 位 |
int8, uint8 | 1 | 0 | -128 ~ 127, 0 ~ 255,byte是uint8 的别名 |
int16, uint16 | 2 | 0 | -32768 ~ 32767, 0 ~ 65535 |
int32, uint32 | 4 | 0 | -21亿~ 21亿, 0 ~ 42亿,rune是int32 的别名 |
int64, uint64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | |
complex64 | 8 | ||
complex128 | 16 | ||
uintptr | 4或8 | 以存储指针的 uint32 或 uint64 整数 | |
array | 值类型 | ||
struct | 值类型 | ||
string | “” | UTF-8 字符串 | |
slice | nil | 引用类型 | |
map | nil | 引用类型 | |
channel | nil | 引用类型 | |
interface | nil | 接口 | |
function | nil | 函数 |
2.1 基本数据类型
整型可以分为下面两个大类
- 有符号整型按长度分为:int8、int16、int32、int64
- 对应的无符号整型:uint8、uint16、uint32、uint64
特殊整型
uint
:32 位操作系统上就是 uint32,64 位操作系统上就是 uint64int
:32 位操作系统上就是 int32,64 位操作系统上就是 int64uintptr
:无符号整型, 用于存放一个指针
可以通过 unsafe.Sizeof()
查看不同长度的整型在内存中的存储空间
浮点型
Go 语言支持两种浮点型数: float32 | float64,这两种浮点型数据格式遵循 IEEE 754 标准。
- float32 浮点数的最大范围位 3.4e38,可用常量定义为:
math.MaxFloat32
- float32 浮点数的最大范围位 1.8e308,可用常量定义为:
math.MaxFloat64
Go 中 float 精度丢失问题
func main() {
var f float64 = 1129.6
fmt.Println(f * 100) // 112959.99999999999
}
- 而且 减法的时候也会出现这种问题,一般来说可以引入第三方包来调用相关方法来解决
举例说明
func main() {
// 整数类型
var a int = 42 // 根据平台决定大小
var b int8 = 127 // 8位整数 (-128 to 127)
var c int16 = 32767 // 16位整数
var d int32 = 2147483647 // 32位整数
var e int64 = 9223372036854775807 // 64位整数
var f uint = 42 // 无符号整数
var g uint8 = 255 // 8位无符号整数
var h uint16 = 65535 // 16位无符号整数
// 浮点数类型
var i float32 = 3.14 // 32位浮点数
var j float64 = 3.141592653589793 // 64位浮点数(默认)
// bool 类型 -- 默认值为 false
var flag = true
// 复数类型
var k complex64 = 1 + 2i
var l complex128 = 3 + 4i
// 字节和别名
var m byte = 'A' // uint8 的别名
var n rune = '中' // int32 的别名,表示 Unicode 码点
fmt.Printf("整数: %d %d %d %d %d\n", a, b, c, d, e)
fmt.Println("int 字节大小: " unsafe.Sizeof(a)) // 8
fmt.Printf("无符号: %d %d %d\n", f, g, h)
fmt.Printf("浮点数: %.2f %.6f\n", i, j)
fmt.Printf("复数: %v %v\n", k, l) // 复数: (1+2i) (3+4i)
fmt.Printf("字符: %c %c\n", m, n)
}
2.2 字符串类型
字符串转义符
转义符 | 含义 |
---|---|
\r |
回车符(返回行首) |
\n |
换行符(直接跳到下一行同列位置) |
\t |
制表符 |
\' |
单引号 |
\" |
双引号 |
\\ |
反斜杠 |
字符串 常用操作
len(s)
:求长度+
或者fmt.Sprintf()
:拼接字符串strings.Split()
:分割字符串strings.contains
:判断是否包含该子串strings.HasPrefix(), strings.HasSuffix
:前缀/后缀 判断strings.Index(), strings.LastIndex()
:子串出现位置strings.Join(a[]string, sep string)
:Join 操作
举例说明:
func main() {
s1 := "goLang"
fmt.Printf("%v -- %T\n", s1, s1)
// 输出 多行字符串 使用 `
s2 := `"Hello
"World`
fmt.Println(s2)
fmt.Println("中文: ", len("你"), "英文: ", len("s")) // 中文: 3 英文: 1
// 字符串拼接
result := s1 + " " + s2
fmt.Println(result)
// 字符串遍历
for i, char := range s1 {
fmt.Printf("索引 %d: %c\n", i, char)
}
arr := []string{"c++", "java", "go"} // 切片
s3 := strings.Join(arr, "-")
fmt.Println(s3) // c++-java-go
}
2.3 字符类型
Go 语言的字符有以下两种类型
- uint8 类型(byte):代表 ASCII 码的一个字符
- rune 类型:代表一个 UTF-8 字符
当需要处理 中文、日文 或 其他复合字符 时候,需用到 rune 类型,rune 类型实际是一个 int32
byte 类型:组成每个字符串的元素叫作 “字符”,可以通过遍历字符串元素获取字符,字符用 ''
包裹起来,比如:
func main(){
a := 'a'
b := '0'
// 直接输出 byte 得到的是这个字符对应码值
fmt.Println(a)
fmt.Println(b)
// 格式化输出 得到这个字符
fmt.Printf("%c--%c\n", a, b)
// 循环输出字符串字符
s := "你好 golang"
for i := 0; i < len(s); i++{
fmt.Printf("%v(%c) ", s[i], s[i])
}
}
上面我们看输出结果会发现 循环输出 中文的时候会出现乱码字符,此时就应该使用 rune 来处理 Unicode
func main() {
s := "你好 golang"
for _, v := range s {
fmt.Printf("%v(%c) ", v, v)
}
}
注意:修改字符串需要先将其转成 []rune
或 []byte
,完成后再转为 string,但是无论哪种转换都会重新分配内存,并复制 字节数组
func main() {
s := "big"
// s[0] = 'p' // 报错
// 复制
bs := []byte(s)
bs[0] = 'p'
fmt.Println(string(bs))
}
2.4 数据类型转换
func main() {
// 1. 整型和整型间转换
var (
a1 int8 = 20
a2 int16 = 40
)
fmt.Println(int16(a1) + a2)
// 2. 整型和浮点型转换
var f float64 = 20
fmt.Println(float64(a1) + f)
// 3. 其他类型 -> string (Sprintf)
// 注意: Sprintf 使用转换格式 int(%d), float(%f), bool(%t), byte(%c)
s := fmt.Sprintf("%d", a1)
fmt.Printf("v: %v, type: %T\n", s, s)
// 4. 通过 strconv 进行转换
var a int = 8
s1 := strconv.Itoa(a) // 也可以 := strcinv.FomatInt(a1, 10) // 指定进制
fmt.Printf("v: %v, type: %T\n", s1, s1)
// folat 转换参数如下:
// 参数1: 要转换值 参数2: 格式化类型 'f'、'b'、'e'、'E'、'g'、'G'
// 参数3: 保留小数点 -1(不对小数点格式化) 参数4: 格式化的类型
s2 := strconv.FormatFloat(f, 'f', 2, 64)
fmt.Printf("v: %v, type: %T\n", s2, s2)
}
注意:go 中 数值类型 无法直接转成 bool 类型,同理 bool 也不能直接转成数值类
2.5 占位符格式
fmt 包实现了格式化 IO 函数,类似于 C 的 printf 和 scanf,格式 占位符 衍生自 C,但比 C 更简单
使用如下:
package main
import "fmt"
func main() {
a := 10
fmt.Printf("a=%v, %T", a, a)
}
// 输出: a=10, int
一般
%v 相应值的默认格式, 在打印结构体时, "加号" 标记 (% + v) 会添加字段名
%#v 相应值的 Go 语法表示
%T 相应值的类型的 Go 语法表示
%% 字面上上的百分号, 并非值的占位符
布尔:%t 单词 ture 或 false
整数:
%b 二进制表示
%c 相应 Unicode 码点所表示字符
%d 十进制表示
%o 八进制表示
%q 单引号围绕的字符字面值, 由 Go 语法安全的转义
%x 十六进制表示, 字母形式为小写 a-f
%X 十六进制表示, 字母形式为大写 A-F
%U Unicode格式: U + 1234, 等同于 "U+%04X"
浮点数及其复合构成
%b 无小数部分的、指数为 2 的幂的科学计数法, 如 -123456p-78
%e 科学计数法, 如 -1234.56e+78
%E 科学计数法, 如 -1234.56E+78
%f 有小数点但无指数, 如 123.456
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出
3. 运算符 & 控制语句
运算符就那些最基本的,这里就不过过多讲解,直接来看看 控制语句 相关例子
条件语句
Go 的 if
语句表达式外无需小括号 ( )
,而大括号 { }
则是必须的
- 而且
if
语句可以在条件表达式前执行一个简短语句,该语句声明的变量作用域仅在if
之内。 - 在Go语言中,else必须紧跟在if的右大括号后面,不能换行(即else不能放在下一行的开头,除非前面有右大括号)
func main() {
score := 85
// 基本 if 语句
if score >= 60 {
fmt.Println("及格")
}
// if-else 语句
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
// if 中的简短声明
if n := len("Hello"); n > 5 {
fmt.Println("字符串长度大于5")
} else {
fmt.Println("字符串长度小于等于5")
}
// 注意:n 在这里不可访问
// fmt.Println(n) // 错误!
}
循环语句
Go 的for
循环 与 if
语句类似,C/C++ 中的While循环相当于Go中的 for 循环
func main() {
// 1. 传统 for 循环
for i := 0; i < 5; i++ {
fmt.Printf("i = %d ", i)
}
fmt.Println()
// 2. while 风格循环
j := 0
for j < 5 {
fmt.Printf("j = %d ", j)
j++
}
fmt.Println()
// 3. 无限循环
count := 0
for {
count++
if count > 3 {
break
}
fmt.Printf("count = %d ", count)
}
fmt.Println()
// 4. continue 和 break
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // 跳过偶数
}
if i > 7 {
break // 大于7时跳出
}
fmt.Printf("%d ", i)
}
fmt.Println()
}
Go 中可以使用 for range
遍历数组、切片、字符串、map 及 通道,通过 for range 遍历的返回值规律如下:
- 数组、切片、字符串 返回索引和值
- map 返回 键和值
- 通道(channel) 只返回通道内的值
例子
func main(){
// 1. for range 键值循环(遍历数组、切片、map等)
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("索引 %d: 值 %d\n", index, value)
}
// 2. 只遍历索引
for index := range numbers {
fmt.Printf("索引: %d ", index)
}
fmt.Println()
// 3. 只遍历值
for _, value := range numbers {
fmt.Printf("值: %d ", value)
}
fmt.Println()
}
分支语句
func main() {
grade := "B"
// 基本 switch
switch grade {
case "A":
fmt.Println("优秀")
case "B":
fmt.Println("良好")
case "C":
fmt.Println("及格")
default:
fmt.Println("不及格")
}
// 多条件 case
score := 85
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
// switch 中的简短声明
switch day := "Monday"; day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("工作日")
case "Saturday", "Sunday":
fmt.Println("周末")
}
}
fallthrough
在 Go 语言中,fallthrough
是一个特殊的控制流语句,用于在 switch
语句中强制"穿透"到下一个 case
块。它的主要用途是允许单个匹配条件执行多个 case 块,这在某些特定场景下非常有用。
如下:
switch n := 2; n {
case 1:
fmt.Println("Case 1")
fallthrough // 强制穿透到下一个 case
case 2:
fmt.Println("Case 2")
fallthrough // 继续穿透
case 3:
fmt.Println("Case 3")
default:
fmt.Println("Default case")
}
注意:
- 必须是
case
块的最后一条语句,后面不能有其他语句(编译器会报错) - 只能穿透到下一个
case
块,不能穿透到default
块(如果后面是default
会执行它)
4. 数组
func main() {
// 1. 声明数组
var arr1 [5]int // 声明长度为5的整数数组
var arr2 [3]string = [3]string{"a", "b", "c"} // 声明并初始化
arr3 := [4]float64{1.1, 2.2, 3.3, 4.4} // 类型推导
// 2. 自动计算长度
arr4 := [...]int{10, 20, 30, 40} // 长度自动计算为4
// 3. 访问和修改数组元素
fmt.Println("arr1[0]:", arr1[0]) // 默认值为0
arr1[0] = 100
fmt.Println("修改后 arr1[0]:", arr1[0])
// 4. 数组长度
fmt.Printf("arr2 长度: %d\n", len(arr2))
fmt.Printf("arr3 长度: %d\n", len(arr3))
// 5. 遍历数组
fmt.Println("遍历 arr4:")
for i := 0; i < len(arr4); i++ {
fmt.Printf("arr4[%d] = %d ", i, arr4[i])
}
fmt.Println()
// 6. range 遍历
fmt.Println("range 遍历 arr3:")
for index, value := range arr3 {
fmt.Printf("索引 %d: 值 %.1f\n", index, value)
}
// 7. 数组是值类型(拷贝传递)
arr5 := [3]int{1, 2, 3}
arr6 := arr5 // 拷贝整个数组
arr6[0] = 100
fmt.Printf("arr5: %v, arr6: %v\n", arr5, arr6) // arr5 不变
// 8. 多维数组
var matrix [2][3]int = [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
var pos = [2][2] string{
{"北京", "上海"},
{"广州", "深圳"},
}
fmt.Println("二维数组:")
for i := 0; i < len(matrix); i++ {
for j := 0; j < len(matrix[i]); j++ {
fmt.Printf("%d ", matrix[i][j])
}
fmt.Println()
}
}
实用示例 1:
func main() {
// 示例1:数组初始化的不同方式
var scores [5]float64
scores = [5]float64{95.5, 87.0, 92.5, 88.0, 90.0}
// 计算平均分
sum := 0.0
for _, score := range scores {
sum += score
}
average := sum / float64(len(scores))
fmt.Printf("平均分: %.2f\n", average)
// 示例2:查找最大值
numbers := [...]int{34, 78, 23, 90, 12, 67}
max := numbers[0]
for _, num := range numbers {
if num > max {
max = num
}
}
fmt.Printf("数组中的最大值: %d\n", max)
}
实用示例2(值 类型 & 引用类型)
// 数组作为函数参数(值传递)
func modifyArray(arr [3]int) {
arr[0] = 100
fmt.Println("函数内 arr:", arr)
}
// 数组指针作为参数(引用传递)
func modifyArrayPtr(arr *[3]int) {
(*arr)[0] = 100
fmt.Println("函数内 arr:", *arr)
}
func main(){
// 数组传递
arr := [3]int{1, 2, 3}
fmt.Println("调用前 arr:", arr)
modifyArray(arr)
fmt.Println("调用后 arr:", arr) // 原数组不变
modifyArrayPtr(&arr)
fmt.Println("指针调用后 arr:", arr) // 原数组改变
// 值类型: 数组
var a1 = [...]int{1, 2, 3}
a2 := a1
a1[0] = 11
fmt.Println(a1) // 11 2 3
fmt.Println(a2) // 1 2 3
// 引用类型: 切片
var b1 = []int{1, 2, 3}
b2 := b1
b1[0] = 11
fmt.Println(b1) // 11 2 3
fmt.Println(b2) // 11 2 3
}
5. 下划线
“_”
是特殊标识符,用来忽略结果
① 下划线在 import 中
在Golang里,import的作用是导入其他package
import 下划线(如:import hello/imp
)的作用:当导入一个包时,该包下的文件里所有 init()
函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行 init()
函数而已。
- 这个时候就可以使用 import 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用
init()
函数,所以无法通过包名来调用包中的其他函数。
示例:
// 代码结构
src
|
+--- main.go
|
+--- hello
|
+--- hello.go
package main
import _ "./hello"
func main() {
// hello.Print()
//编译报错:./main.go:6:5: undefined: hello
}
// hello.go
package hello
import "fmt"
func init() {
fmt.Println("imp-init() come here.")
}
func Print() {
fmt.Println("Hello!")
}
输出结果:
imp-init() come here.
② 下划线在代码中
import "os"
func main() {
buf := make([]byte, 1024)
f, _ := os.Open("/Users/***/Desktop/text.txt")
defer f.Close()
for {
n, _ := f.Read(buf)
if n == 0 {
break
}
os.Stdout.Write(buf[:n])
}
}
解释1:
- 下划线意思是忽略这个变量
- 比如
os.Open
,返回值为*os.File,error
;普通写法是f,err := os.Open("xxxxxxx")
- 如果此时不需要知道返回的错误值,就可以用
f, _ := os.Open("xxxxxx")
,如此则忽略了error变量
解释2:
- 占位符,意思是那个位置本应赋给某个值,但是咱们不需要这个值,所以就把该值赋给下划线,意思是丢掉不要。
- 这样编译器可以更好的优化,任何类型的单个值都可以丢给下划线。
- 这种情况是占位用的,方法返回两个结果,而你只想要一个结果。那另一个就用 “_” 占位,而如果用变量的话,不使用,编译器是会报错的。
补充:
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
第二个import就是不直接使用mysql包,只是执行一下这个包的init函数,把mysql的驱动注册到sql包里,然后程序里就可以使用sql包来访问mysql数据库了。
三、函数初始
我们这里暂时就看一些最基本的函数,后面再来深入
// 最简单的函数
func sayHello() {
fmt.Println("Hello, World!")
}
// 带参数的函数
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
// 带返回值的函数
func add(a, b int) int {
return a + b
}
// 多返回值函数
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
func main() {
sayHello()
greet("张三")
result := add(3, 5)
fmt.Printf("3 + 5 = %d\n", result)
quotient, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", quotient)
}
}
内置函数
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。
append
:用来追加元素到数组、slice中,返回修改后的数组、sliceclose
:主要用来关闭channeldelete
:从map中删除key对应的valuepanic
:停止常规的 goroutine (panic和recover:用来做错误处理)recover
:允许程序定义goroutine的panic动作imag
:返回complex的实部 (complex、real imag:用于创建和操作复数)real
:返回complex的虚部make
:用来分配内存,返回Type本身(只能应用于slice, map, channel)new
:用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针cap
:capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)copy
:用于复制和连接slice,返回复制的数目len
:来求长度,比如string、array、slice、map、channel ,返回长度print、println
:底层打印函数,在部署环境中建议使用 fmt 包