Go语言错误与异常处理
Go语言错误与异常处理Go语言没有 try/catch 异常捕获机制——这是刻意的设计选择。Go认为错误是程序的正常组成部分,应该被显式处理而非隐藏在异常流中。函数作为Go的一等公民,通过多返回值将error作为结果的一部分返回,调用方必须决定如何处理它。 Go的错误处理哲学在Java/Python中,异常通过 try/catch 捕获,错误沿调用栈自动传播。Go选择了不同的路: Java: 异常自动传播,调用方可以选择"不处理"Go: 错误显式返回,调用方必须"做出决定" 这意味着在Go中,你会频繁看到这样的代码: result, err := doSomething()if err != nil { // 必须处理} 这看似啰嗦,但带来了两个好处: 错误处理路径清晰可见——代码审查时一眼能看到每个错误是否被处理 没有隐藏的控制流——不会有异常突然跳过几层函数调用 error接口Go的错误就是一个接口: type error interface { Error()...
Go语言线程安全与sync.Map
Go语言线程安全与sync.Map当多个goroutine同时读写共享数据时,如果不加保护就会产生数据竞争(Data Race),导致结果不可预期甚至程序崩溃。Go通过 sync 包提供了互斥锁、读写锁和并发安全的Map等工具来解决这个问题。 为什么需要线程安全先看一个不安全的例子: package mainimport ( "fmt" "sync")func main() { counter := 0 var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter++ // 多个goroutine同时读写,存在数据竞争 }() } wg.Wait() fmt.Println("counter:", cou...
Go语言协程与信道
Go语言协程与信道并发是Go语言的核心竞争力。Go通过goroutine(协程)和channel(信道)提供了一套简洁而强大的并发编程模型,遵循CSP(Communicating Sequential Processes)理念——不要通过共享内存来通信,而要通过通信来共享内存。 什么是Goroutine(协程)Goroutine是Go运行时管理的轻量级线程。与操作系统线程相比: 特性 OS线程 Goroutine 栈大小 固定,通常1-8MB 初始2KB,按需增长 创建开销 大(系统调用) 小(用户态) 调度 内核调度 Go运行时调度(GMP模型) 数量上限 通常数千个 轻松支持数十万个 Go运行时使用GMP调度模型:G(Goroutine)、M(Machine,即OS线程)、P(Processor,逻辑处理器)。多个G被调度到少量M上执行,P控制并发度(默认等于CPU核心数)。 创建Goroutine使用 go 关键字即可启动一个协程: package mainimport ( "fmt" "time&q...
Go语言自定义类型与接口
Go语言自定义类型与接口Go的类型系统简洁但表达力很强。type 关键字可以创建自定义类型和类型别名,接口(interface)定义行为契约,类型断言在运行时判断具体类型。这三者构成了Go类型系统的核心。 自定义类型基于已有类型创建新类型使用 type 可以基于任何已有类型创建一个全新的类型: type Age inttype Score float64type Name stringtype Handler func(string) errortype UserMap map[string]int 自定义类型与底层类型是不同的类型,不能直接赋值或混合运算: type Age intvar a Age = 25var b int = 25// a = b // 编译错误:cannot use b (type int) as type Agea = Age(b) // 正确:显式类型转换b = int(a) // 正确:反向转换// fmt.Println(a + b) // 编译错误:类型不同,不能直接运算 自定义类型可以...
Go语言结构体
Go语言结构体Go没有 class 关键字,但结构体(struct)承担了面向对象中”类”的角色。结构体定义数据结构,方法定义行为,组合替代继承——这是Go的面向对象哲学。 定义结构体使用 type + struct 定义结构体: type User struct { Name string Age int Email string} 命名规范: 结构体名首字母大写(如 User)→ 可被其他包访问(导出) 字段名首字母大写(如 Name)→ 字段可被其他包访问 字段名首字母小写(如 name)→ 字段仅包内可见 type User struct { Name string // 导出字段:其他包可以访问 phone string // 未导出字段:仅当前包可以访问} 创建与初始化1. 零值初始化声明后所有字段自动初始化为零值: var u Userfmt.Println(u) // { 0 } Name="", Age=0, Email="...
Go语言init与defer
Go语言init与deferinit 和 defer 是Go中两个特殊的函数机制。init 负责包的初始化,在程序启动时自动执行;defer 负责延迟调用,在函数返回前执行。两者都不需要手动调用,由运行时自动管理。 init 函数init 是Go中专门用于包初始化的特殊函数。它不能被手动调用,不能有参数和返回值,由运行时在 main 函数执行前自动调用。 基本语法package mainimport "fmt"func init() { fmt.Println("init 执行")}func main() { fmt.Println("main 执行")}// 输出:// init 执行// main 执行 init 的四个特性1. 无参数、无返回值、不能手动调用 func init() { // 正确:无参数无返回值}func init() int { // 编译错误:不能有返回值 return 0}fu...
Go语言函数
Go语言函数函数是Go程序的基本构建单元。Go的函数设计简洁而强大——支持多返回值、命名返回值、可变参数、匿名函数和闭包,同时函数本身也是一等公民(first-class),可以作为参数传递和返回。 函数定义基本语法使用 func 关键字定义函数: func 函数名(参数列表) 返回值 { 函数体} func greet(name string) string { return "Hello, " + name}fmt.Println(greet("Gopher")) // Hello, Gopher 同类型参数简写连续多个参数类型相同时,前面的类型可以省略,只保留最后一个: // 完整写法func add(x int, y int) int { return x + y}// 简写:x 和 y 都是 intfunc add(x, y int) int { return x + y}// 多个参数组合简写func compute(a...
Go语言循环语句
Go语言循环语句Go只有一个循环关键字——for。没有 while、没有 do-while,但通过 for 的不同写法可以覆盖所有循环场景。简洁统一,这就是Go的风格。 for 循环的四种写法1. 经典三段式与C/Java的 for 结构一致,由初始化、条件、后置语句三部分组成: for i := 0; i < 5; i++ { fmt.Println(i)}// 输出: 0 1 2 3 4 与 if 一样,条件不需要小括号,花括号必须有。 三个部分都可以省略,但分号不能省: i := 0for ; i < 5; i++ { fmt.Println(i)} 2. while 模式Go没有 while 关键字,但省略初始化和后置语句后,for 就是 while: n := 1for n < 100 { n *= 2}fmt.Println(n) // 128 等价于其他语言的: // Javawhile (n < 100) { n *= 2;...
Go语言判断语句
Go语言判断语句判断语句是程序控制流的基础。Go提供了 if 和 switch 两种判断结构,语法上与C/Java有不少差异——不需要括号、默认不穿透、支持初始化语句等。本文将系统介绍Go判断语句的写法、逻辑运算符、以及与Java的对比。 if 语句基本语法Go的 if 不需要小括号包裹条件,但花括号是必须的: age := 20if age >= 18 { fmt.Println("成年")}if age >= 18 { fmt.Println("成年")} else { fmt.Println("未成年")}if age < 12 { fmt.Println("儿童")} else if age < 18 { fmt.Println("青少年")} else { fmt.Println("...
Go语言数组、切片与Map
Go语言数组、切片与MapGo语言中最常用的三种集合类型:数组(Array) 是固定长度的值类型,切片(Slice) 是基于数组的动态引用类型,Map 是键值对的哈希表。日常开发中切片和Map使用频率远高于数组。 数组(Array)数组是固定长度、同一类型元素的集合。长度是类型的一部分——[3]int 和 [5]int 是不同的类型。 声明与初始化// 声明后自动初始化为零值var a [3]int // [0, 0, 0]// 声明并初始化b := [3]int{1, 2, 3}// 让编译器根据元素个数推断长度c := [...]int{1, 2, 3, 4} // 长度为4// 指定索引位置初始化d := [5]int{1: 10, 3: 30} // [0, 10, 0, 30, 0] 数组与索引:为什么随机访问是O(1)数组在内存中是一段连续的、等大小的空间。正因为连续且等大小,CPU可以通过一个公式直接算出任意元素的地址,无需遍历: 元素地址 = 首地址 + 索引 × 元素大小 ...













