Go 语言基础1

一、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 常见易错点

  1. 大小写敏感 :Go 语言严格区分大小写,fmt.printf 会编译报错,正确写法为首字母大写 fmt.Printf
  2. 使用场景混淆 :函数需要返回字符串时,必须用 Sprint/Sprintf,不能用 Print/Printf
  3. 参数不匹配:占位符数量与传入参数数量必须对应,否则会出现编译警告或运行异常

二、流程控制:分支与循环

2.1 if 条件判断:支持前置短语句

Go 的 if 独具特色:可以在条件判断前执行一条简短语句,通常用于定义临时变量。

复制代码
if v := math.Pow(x, n); v < lim {
    return v
}
return lim

语法规则

  1. 分号 ; 分隔前置语句与判断条件
  2. 前置语句定义的变量,作用域仅限整个 if-else 块内部,外部无法访问
  3. 变量仅做一次判断、无需复用时,推荐该写法,可缩小作用域、避免变量污染

2.2 if-else 作用域与语法规范

复制代码
if v := math.Pow(x, n); v < lim {
    return v
} else {
    fmt.Printf("%g >= %g\n", v, lim)
}
// 此处无法使用 v

关键要点

  1. 前置定义的变量,ifelse 分支内均可使用
  2. else 必须紧跟 if 的右大括号 } 同一行,不能换行,否则编译报错
  3. 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)
}
核心特性
  1. 从上到下顺序匹配:依次判断 case,匹配成功立即停止,后续 case 不再求值(短路特性)
  2. case 支持表达式:不局限于整数常量,支持字符串、变量、函数表达式
  3. 多值匹配 :一个 case 可匹配多个值,逗号分隔 case "a", "b", "c":
  4. 无表达式 switch :等价于 switch true,可替代多层 if-else
  5. 主动穿透 :使用 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 关键特性

  1. 零值为 nil :声明未赋值的指针默认值为 nil,不指向任何有效内存
  2. 类型严格匹配*int 只能存储 int 变量的地址,不能存储其他类型
  3. 无指针运算 :不能进行 p++p+1 等地址偏移操作,避免野指针
  4. 传指针可修改原变量:函数传指针参数时,函数内修改会同步影响外部原变量

五、结构体 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 几乎完全一致。