一、fmt 输出体系:打印与字符串格式化
Go 语言的 fmt 包提供了两套核心输出体系:直接打印到控制台 与 生成字符串返回,两者极易混淆,需明确区分。
1.1 Print 系列 vs Sprint 系列
表格
| 函数 | 是否输出控制台 | 是否返回字符串 | 核心特性 |
|---|---|---|---|
fmt.Print() |
✅ 直接输出 | ❌ 无返回 | 参数间无空格、不换行 |
fmt.Println() |
✅ 直接输出 | ❌ 无返回 | 参数间自动加空格、末尾自动换行 |
fmt.Printf() |
✅ 直接输出 | ❌ 无返回 | 支持格式化占位符 |
fmt.Sprint() |
❌ 不输出 | ✅ 返回字符串 | 拼接参数,无空格无换行 |
fmt.Sprintln() |
❌ 不输出 | ✅ 返回字符串 | 参数间加空格、末尾带换行符 |
fmt.Sprintf() |
❌ 不输出 | ✅ 返回字符串 | 格式化后返回字符串 |
核心记忆口诀:
- 名字带
Print:直接往控制台写内容,没有返回值,不能用于变量赋值、函数返回 - 名字带
Sprint:只在内存中拼接字符串,不打印,返回拼接结果,可存变量、做拼接、当函数返回值
1.2 常用格式化占位符
格式化函数 Printf / Sprintf 通过占位符控制输出格式,高频常用如下:
| 占位符 | 用途 | 适用类型 |
|---|---|---|
%d |
十进制整数输出 | int 系列整数 |
%f |
浮点数输出 | float32 / float64 |
%g |
简洁浮点数,自动去除末尾无效 0 | float 系列 |
%s |
字符串输出 | string |
%t |
布尔值输出 | bool |
%v |
通用默认格式,任意类型均可 | 所有类型,懒人首选 |
%T |
打印变量的类型 | 所有类型,调试神器 |
1.3 常见易错点
- 大小写敏感 :Go 语言严格区分大小写,
fmt.printf会编译报错,正确写法为首字母大写fmt.Printf - 使用场景混淆 :函数需要返回字符串时,必须用
Sprint/Sprintf,不能用Print/Printf - 参数不匹配:占位符数量与传入参数数量必须对应,否则会出现编译警告或运行异常
二、流程控制:分支与循环
2.1 if 条件判断:支持前置短语句
Go 的 if 独具特色:可以在条件判断前执行一条简短语句,通常用于定义临时变量。
if v := math.Pow(x, n); v < lim {
return v
}
return lim
语法规则:
- 分号
;分隔前置语句与判断条件 - 前置语句定义的变量,作用域仅限整个
if-else块内部,外部无法访问 - 变量仅做一次判断、无需复用时,推荐该写法,可缩小作用域、避免变量污染
2.2 if-else 作用域与语法规范
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 此处无法使用 v
关键要点:
- 前置定义的变量,
if和else分支内均可使用 else必须紧跟if的右大括号}同一行,不能换行,否则编译报错if-else结束后,临时变量立即销毁,外部不可访问
2.3 for 循环:Go 唯一的循环关键字
Go 语言只有 for 一种循环关键字,可实现多种循环形态:
// 标准三段式循环
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// 类 while 循环
for condition {
// 循环体
}
// 无限循环
for {
// 需 break 退出
}
2.4 switch 分支:自动 break,无需手动终止
Go 的 switch 与 C/Java 最大区别:匹配成功后自动 break,不会穿透后续 case ,无需手动写 break。
基础用法
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
核心特性
- 从上到下顺序匹配:依次判断 case,匹配成功立即停止,后续 case 不再求值(短路特性)
- case 支持表达式:不局限于整数常量,支持字符串、变量、函数表达式
- 多值匹配 :一个 case 可匹配多个值,逗号分隔
case "a", "b", "c": - 无表达式 switch :等价于
switch true,可替代多层if-else - 主动穿透 :使用
fallthrough关键字可强制继续执行下一个 case
三、defer 延迟调用:资源清理的利器
3.1 基本定义
defer 语句会将其后的函数调用延迟执行,等到包裹它的函数即将返回前才执行。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
// 输出:
// hello
// world
3.2 两大核心特性
特性一:参数立即求值,函数延后执行
defer 语句执行时,会立刻计算并保存函数参数的值;函数本体等到函数返回前才执行。
a := 1
defer fmt.Println(a) // 此处已固化 a=1
a = 100
// 最终输出 1,而非 100
特性二:多个 defer 后进先出(LIFO)
多个 defer 按逆序执行,后定义的先执行,类似栈结构。
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
// 输出顺序:3 → 2 → 1
3.3 典型应用场景
defer 最核心的用途是资源释放 :打开文件、数据库连接、网络连接后,立刻写 defer 关闭(),保证函数无论正常返回还是异常退出,资源都会被正确释放,避免泄漏。
file, err := os.Open("test.txt")
if err != nil {
return err
}
defer file.Close() // 函数退出前自动关闭文件
四、指针:安全的内存地址操作
4.1 核心概念
指针是存储变量内存地址 的变量,通过指针可以间接读写原变量的数据。Go 语言的指针相比 C 语言做了大幅简化,不支持指针运算,更加安全。
4.2 两个核心操作符
| 符号 | 名称 | 作用 |
|---|---|---|
& |
取地址符 | 放在变量前,获取变量的内存地址,生成指针对象 |
* |
解引用符 | 放在指针前,根据地址访问原变量,可读可写 |
i := 42
p := &i // p 是 *int 类型,存储 i 的地址
fmt.Println(*p) // 解引用,读取值 → 42
*p = 21 // 通过指针修改原变量 i 的值
fmt.Println(i) // i 变为 21
4.3 关键特性
- 零值为 nil :声明未赋值的指针默认值为
nil,不指向任何有效内存 - 类型严格匹配 :
*int只能存储 int 变量的地址,不能存储其他类型 - 无指针运算 :不能进行
p++、p+1等地址偏移操作,避免野指针 - 传指针可修改原变量:函数传指针参数时,函数内修改会同步影响外部原变量
五、结构体 Struct:自定义复合类型
5.1 结构体定义
结构体是将多个不同类型的字段打包成一个整体的自定义类型,用于描述一个实体的多个属性。
type Vertex struct {
X int
Y int
}
5.2 四种初始化方式
1. 顺序初始化
按结构体字段定义顺序依次赋值,必须传全量字段。
v1 := Vertex{1, 2} // X=1, Y=2
2. 具名字段初始化(推荐)
显式指定字段名,顺序无关,未指定字段自动填充类型零值。
v2 := Vertex{X: 1} // X=1, Y=0
3. 零值初始化
所有字段默认取对应类型的零值。
v3 := Vertex{} // X=0, Y=0
4. 指针式初始化
直接创建结构体并返回其指针,无需中间临时变量。
p := &Vertex{1, 2} // p 类型为 *Vertex
5.3 结构体指针与语法糖
通过指针访问结构体字段时,标准写法为 (*p).X,Go 提供语法糖,可直接简写为 p.X,编译器自动完成解引用。
v := Vertex{1, 2}
p := &v
p.X = 100 // 等价于 (*p).X = 100
fmt.Println(v) // {100 2}
5.4 导出规则:大小写的意义
Go 语言通过首字母大小写控制可见性:
- 首字母大写:可跨包导出,其他包可以访问
- 首字母小写:仅当前包内可见,外部包无法访问
该规则适用于结构体类型、结构体字段、函数、全局变量、常量。
六、综合实战:牛顿迭代法实现平方根
结合循环、函数、浮点数运算,实现一个经典算法:牛顿法求平方根。
6.1 算法原理
通过迭代公式不断逼近真实平方根: z = z -(z*z-x)/2z
package main
import "fmt"
func Sqrt(x float64) float64 {
z := 1.0 // 初始猜测值
for i := 0; i < 10; i++ {
z = z - (z*z - x)/(2*z)
fmt.Printf("第%d次迭代 z = %v\n", i+1, z)
}
return z
}
func main() {
fmt.Println("最终结果:", Sqrt(2))
}
6.3 进阶实现:精度控制自动停止
当两次迭代差值小于阈值时,认为精度足够,提前退出循环。
func Sqrt(x float64) float64 {
z := 1.0
for {
next := z - (z*z - x)/(2*z)
if z-next < 1e-10 {
break
}
z = next
}
return z
}
6.4 运行效果
对于 √2,仅需 4~5 次迭代即可达到极高精度,结果与标准库 math.Sqrt 几乎完全一致。