自定义包
package calculator
import "math"
// 计算圆的面积(首字母大写,表示可以被外部访问)
func CircleArea(radius float64) float64 {
return math.Pi * radius * radius
}
// 计算矩形面积
func RectangleArea(width, height float64) float64 {
return width * height
}
// 私有函数(首字母小写,只能在包内使用)
func square(x float64) float64 {
return x * x
}
// 使用私有函数的公共函数
func SquareArea(side float64) float64 {
return square(side)
}
main.go
package main
import (
"fmt"
"myproject/calculator" // 导入自定义包
)
func main() {
// 使用自定义包的函数
fmt.Printf("圆的面积: %.2f\n", calculator.CircleArea(5.0))
fmt.Printf("矩形面积: %.2f\n", calculator.RectangleArea(3.0, 4.0))
fmt.Printf("正方形面积: %.2f\n", calculator.SquareArea(3.0))
// 下面这行会报错,因为 square 是私有函数
// fmt.Println(calculator.square(3.0))
}
变量四种声明方式
方式一:完整声明
var name string = "张三"
var age int = 25
var height float64 = 1.75
var isStudent bool = true
方式二:类型推断
var name = "张三" // 推断为 string
var age = 25 // 推断为 int
var height = 1.75 // 推断为 float64
var isStudent = true // 推断为 bool
方式三:短变量声明(最常用)
name := "张三"
age := 25
height := 1.75
isStudent := true
方式四:批量声明
var (
name string = "张三"
age int = 25
height float64 = 1.75
isStudent bool = true
)
多变量的声明
package main
import "fmt"
func main() {
// 同时声明多个相同类型的变量
var a, b, c int = 1, 2, 3
fmt.Printf("a=%d, b=%d, c=%d\n", a, b, c)
// 同时声明多个不同类型的变量
var name, age, height = "李四", 30, 1.80
fmt.Printf("姓名: %s, 年龄: %d, 身高: %.2f\n", name, age, height)
// 使用短变量声明
x, y := 10, 20
fmt.Printf("x=%d, y=%d\n", x, y)
// 变量交换
x, y = y, x
fmt.Printf("交换后: x=%d, y=%d\n", x, y)
}
常量的特点
编译时确定:常量的值必须在编译时就能确定,不能是运行时才能计算的值。
package main
import (
"fmt"
"math"
)
const (
// 正确:编译时就能确定的值
A = 100
B = 3.14
C = "Hello"
D = true
E = 2 + 3 // 编译时计算
F = "Hello" + " World"
// 错误:运行时才能确定的值
// G = math.Sin(1.0) // 这会导致编译错误
)
func main() {
fmt.Printf("A = %d\n", A)
fmt.Printf("E = %d\n", E)
fmt.Printf("F = %s\n", F)
// 但可以在运行时使用常量进行计算
result := math.Sin(PI)
fmt.Printf("sin(π) = %.2f\n", result)
}
字符类型
Go 语言中的字符类型有两种:
package main
import "fmt"
func main() {
// byte 类型(uint8 的别名)
var b byte = 'A'
fmt.Printf("byte: %c, 数值: %d\n", b, b)
// rune 类型(int32 的别名),用于表示 Unicode 字符
var r rune = '中'
fmt.Printf("rune: %c, 数值: %d\n", r, r)
// 字符串遍历
s := "Hello, 世界!"
// 按字节遍历
fmt.Print("按字节遍历: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%c ", s[i])
}
fmt.Println()
// 按字符遍历
fmt.Print("按字符遍历: ")
for _, r := range s {
fmt.Printf("%c ", r)
}
fmt.Println()
// 字符转换
fmt.Printf("'A' + 1 = %c\n", 'A'+1)
fmt.Printf("'a' - 'A' = %d\n", 'a'-'A')
}
Go 语言是强类型语言,不同类型之间不能直接进行运算,需要进行显式的类型转换。
基本类型转换
package main
import (
"fmt"
"strconv"
)
func main() {
// 数值类型转换
var i int = 42
var f float64 = float64(i)
var ui uint = uint(i)
fmt.Printf("int: %d\n", i)
fmt.Printf("float64: %.2f\n", f)
fmt.Printf("uint: %d\n", ui)
// 注意:浮点数转整数会截断小数部分
var pi float64 = 3.14159
var iPi int = int(pi)
fmt.Printf("pi: %.5f, int(pi): %d\n", pi, iPi)
// 字符串和数字之间的转换
// 数字转字符串
num := 123
str := strconv.Itoa(num)
fmt.Printf("数字 %d 转换为字符串: '%s'\n", num, str)
// 字符串转数字
str2 := "456"
num2, err := strconv.Atoi(str2)
if err != nil {
fmt.Printf("转换失败: %v\n", err)
} else {
fmt.Printf("字符串 '%s' 转换为数字: %d\n", str2, num2)
}
// 更复杂的转换
floatStr := "3.14159"
floatNum, err := strconv.ParseFloat(floatStr, 64)
if err != nil {
fmt.Printf("转换失败: %v\n", err)
} else {
fmt.Printf("字符串 '%s' 转换为浮点数: %.5f\n", floatStr, floatNum)
}
// 布尔值转换
boolStr := "true"
boolVal, err := strconv.ParseBool(boolStr)
if err != nil {
fmt.Printf("转换失败: %v\n", err)
} else {
fmt.Printf("字符串 '%s' 转换为布尔值: %t\n", boolStr, boolVal)
}
}
类型推断
Go 语言支持类型推断,编译器可以根据值自动推断变量的类型:
package main
import "fmt"
func main() {
// 编译器会自动推断类型
name := "张三" // string
age := 25 // int
height := 1.75 // float64
isStudent := true // bool
fmt.Printf("name: %s, 类型: %T\n", name, name)
fmt.Printf("age: %d, 类型: %T\n", age, age)
fmt.Printf("height: %.2f, 类型: %T\n", height, height)
fmt.Printf("isStudent: %t, 类型: %T\n", isStudent, isStudent)
// 复杂类型推断
numbers := []int{1, 2, 3, 4, 5}
fmt.Printf("numbers: %v, 类型: %T\n", numbers, numbers)
person := map[string]interface{}{
"name": "李四",
"age": 30,
}
fmt.Printf("person: %v, 类型: %T\n", person, person)
// 函数返回值的类型推断
result := add(10, 20)
fmt.Printf("result: %d, 类型: %T\n", result, result)
}
func add(a, b int) int {
return a + b
}
类型断言
当你知道一个接口变量的具体类型时,可以使用类型断言:
package main
import "fmt"
func main() {
var value interface{} = "Hello, World!"
// 类型断言
str, ok := value.(string)
if ok {
fmt.Printf("值是字符串: %s\n", str)
} else {
fmt.Println("值不是字符串")
}
// 不安全的类型断言(如果类型不匹配会panic)
// str2 := value.(string)
// fmt.Println(str2)
// 类型选择
switch v := value.(type) {
case string:
fmt.Printf("字符串: %s\n", v)
case int:
fmt.Printf("整数: %d\n", v)
case float64:
fmt.Printf("浮点数: %.2f\n", v)
default:
fmt.Printf("未知类型: %T\n", v)
}
}
变量和函数名(重点)
- 使用驼峰命名法(camelCase)
- 首字母小写表示私有,首字母大写表示公有
package main
import "fmt"
// 公有变量(可以被其他包访问)
var GlobalVariable = "全局变量"
// 私有变量(只能在包内访问)
var localVariable = "局部变量"
// 公有函数
func PublicFunction() {
fmt.Println("这是公有函数")
}
// 私有函数
func privateFunction() {
fmt.Println("这是私有函数")
}
func main() {
// 局部变量命名
userName := "张三"
userAge := 25
isActive := true
fmt.Printf("用户名: %s, 年龄: %d, 激活: %t\n", userName, userAge, isActive)
PublicFunction()
privateFunction()
}
常量命名
常量通常使用全大写字母,单词之间用下划线分隔:
package main
import "fmt"
const (
MAX_SIZE = 100
DEFAULT_TIMEOUT = 30
API_VERSION = "v1.0"
DEBUG_MODE = true
)
func main() {
fmt.Printf("最大大小: %d\n", MAX_SIZE)
fmt.Printf("默认超时: %d\n", DEFAULT_TIMEOUT)
fmt.Printf("API版本: %s\n", API_VERSION)
fmt.Printf("调试模式: %t\n", DEBUG_MODE)
}
特殊命名约定
接口命名:
- 单方法接口通常以 -er 结尾
- 多方法接口使用描述性名称
package main
import "fmt"
// 单方法接口
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
// 多方法接口
type Database interface {
Connect() error
Query(string) ([]map[string]interface{}, error)
Close() error
}
// 实现接口的结构体
type FileReader struct {
filename string
}
func (f *FileReader) Read(data []byte) (int, error) {
// 模拟读取文件
return 0, nil
}
func main() {
var reader Reader = &FileReader{filename: "test.txt"}
fmt.Printf("Reader: %T\n", reader)
}
错误变量命名:
- 错误变量通常以 Err 开头
- 错误类型通常以 Error 结尾
package main
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("未找到")
ErrInvalidInput = errors.New("无效输入")
ErrTimeout = errors.New("超时")
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s: %s", e.Field, e.Message)
}
func main() {
err := &ValidationError{
Field: "email",
Message: "格式不正确",
}
fmt.Printf("错误: %v\n", err)
fmt.Printf("错误: %v\n", ErrNotFound)
}
命名最佳实践
package main
import (
“fmt”
“time”
)
// 好的命名示例
func calculateUserAge(birthYear int) int {
currentYear := time.Now().Year()
return currentYear – birthYear
}
func isValidEmail(email string) bool {
// 简化的邮箱验证逻辑
return len(email) > 0 && email[0] != ‘@’
}
func getUserFullName(firstName, lastName string) string {
return fmt.Sprintf(“%s %s”, firstName, lastName)
}
// 结构体命名
type UserProfile struct {
FirstName string
LastName string
Email string
BirthYear int
IsActive bool
CreatedAt time.Time
LastLoginAt *time.Time // 指针类型,可以为nil
}
// 方法命名
func (u *UserProfile) GetAge() int {
return calculateUserAge(u.BirthYear)
}
func (u *UserProfile) GetFullName() string {
return getUserFullName(u.FirstName, u.LastName)
}
func (u *UserProfile) IsEmailValid() bool {
return isValidEmail(u.Email)
}
func main() {
user := &UserProfile{
FirstName: “张”,
LastName: “三”,
Email: “zhangsan@example.com”,
BirthYear: 1990,
IsActive: true,
CreatedAt: time.Now(),
}
fmt.Printf("用户: %s\n", user.GetFullName())
fmt.Printf("年龄: %d\n", user.GetAge())
fmt.Printf("邮箱有效: %t\n", user.IsEmailValid())
}
切片的概念与创建
切片是 Go 语言中最强大和常用的数据结构之一,它就像一个可以自动调节大小的魔法容器。与数组不同,切片的长度是可变的,这让它在处理动态数据时更加灵活。
切片的基本概念
package main
import "fmt"
func main() {
fmt.Println("=== 算法导航用户收藏列表 ===")
// 方式1:直接声明和初始化
favoriteAlgorithms := []string{"快速排序", "二分查找", "动态规划"}
fmt.Printf("初始收藏: %v\n", favoriteAlgorithms)
fmt.Printf("长度: %d, 容量: %d\n", len(favoriteAlgorithms), cap(favoriteAlgorithms))
// 方式2:使用make函数创建
userScores := make([]int, 5) // 长度为5,容量为5
fmt.Printf("初始分数: %v\n", userScores)
userLevels := make([]int, 3, 10) // 长度为3,容量为10
fmt.Printf("用户等级: %v\n", userLevels)
fmt.Printf("长度: %d, 容量: %d\n", len(userLevels), cap(userLevels))
// 方式3:从数组创建切片
allCourses := [6]string{"Go入门", "Go进阶", "微服务", "云原生", "性能优化", "项目实战"}
beginnerCourses := allCourses[0:2] // 获取前两门课程
advancedCourses := allCourses[2:] // 获取后面的课程
fmt.Printf("所有课程: %v\n", allCourses)
fmt.Printf("入门课程: %v\n", beginnerCourses)
fmt.Printf("进阶课程: %v\n", advancedCourses)
// 方式4:创建空切片
var emptySlice []string
nilSlice := []string{}
makeSlice := make([]string, 0)
fmt.Printf("空切片比较:\n")
fmt.Printf("var声明: %v (nil: %t)\n", emptySlice, emptySlice == nil)
fmt.Printf("字面量: %v (nil: %t)\n", nilSlice, nilSlice == nil)
fmt.Printf("make创建: %v (nil: %t)\n", makeSlice, makeSlice == nil)
}
基本操作:增删改查
package main
import "fmt"
func main() {
fmt.Println("=== 剪切助手剪切板历史管理 ===")
// 初始化剪切板历史
var clipboardHistory []string
// 添加操作
clipboardHistory = append(clipboardHistory, "编程导航官网")
clipboardHistory = append(clipboardHistory, "Go语言教程")
clipboardHistory = append(clipboardHistory, "面试鸭网站")
fmt.Printf("剪切板历史: %v\n", clipboardHistory)
// 批量添加
newItems := []string{"老鱼简历", "算法导航", "剪切助手下载"}
clipboardHistory = append(clipboardHistory, newItems...)
fmt.Printf("批量添加后: %v\n", clipboardHistory)
// 查询操作
if len(clipboardHistory) > 0 {
fmt.Printf("最新项目: %s\n", clipboardHistory[len(clipboardHistory)-1])
fmt.Printf("最早项目: %s\n", clipboardHistory[0])
}
// 修改操作
if len(clipboardHistory) > 2 {
clipboardHistory[2] = "面试鸭题库" // 更新第3个项目
fmt.Printf("更新后: %v\n", clipboardHistory)
}
// 删除操作 - 删除指定索引的元素
indexToDelete := 1
if indexToDelete < len(clipboardHistory) {
clipboardHistory = append(clipboardHistory[:indexToDelete],
clipboardHistory[indexToDelete+1:]...)
fmt.Printf("删除索引%d后: %v\n", indexToDelete, clipboardHistory)
}
// 清空操作
clipboardHistory = clipboardHistory[:0] // 保持容量,长度归零
fmt.Printf("清空后: %v (长度: %d, 容量: %d)\n",
clipboardHistory, len(clipboardHistory), cap(clipboardHistory))
}
切片截取和复制
package main
import "fmt"
func main() {
fmt.Println("=== 编程导航课程管理系统 ===")
allCourses := []string{
"Go语言基础", "数据结构", "算法设计", "数据库原理",
"Web开发", "微服务架构", "云原生技术", "性能优化",
"项目实战", "面试准备",
}
fmt.Printf("全部课程 (%d门): %v\n", len(allCourses), allCourses)
// 切片截取操作
basicCourses := allCourses[0:4] // 基础课程
advancedCourses := allCourses[4:8] // 进阶课程
practicalCourses := allCourses[8:] // 实战课程
fmt.Printf("基础课程: %v\n", basicCourses)
fmt.Printf("进阶课程: %v\n", advancedCourses)
fmt.Printf("实战课程: %v\n", practicalCourses)
// 复制切片
fmt.Println("\n=== 切片复制操作 ===")
// 方法1:使用copy函数
coursesCopy1 := make([]string, len(basicCourses))
copy(coursesCopy1, basicCourses)
// 方法2:使用append
coursesCopy2 := append([]string{}, basicCourses...)
// 方法3:手动复制
coursesCopy3 := make([]string, len(basicCourses))
for i, course := range basicCourses {
coursesCopy3[i] = course
}
fmt.Printf("原始切片: %v\n", basicCourses)
fmt.Printf("copy函数: %v\n", coursesCopy1)
fmt.Printf("append方法: %v\n", coursesCopy2)
fmt.Printf("手动复制: %v\n", coursesCopy3)
// 验证是否为深拷贝
coursesCopy1[0] = "Go语言进阶"
fmt.Printf("\n修改副本后:")
fmt.Printf("原始切片: %v\n", basicCourses)
fmt.Printf("修改的副本: %v\n", coursesCopy1)
// 部分复制
fmt.Println("\n=== 部分复制演示 ===")
source := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
dest := make([]int, 5)
n := copy(dest, source[2:7]) // 复制source的第3-7个元素
fmt.Printf("源切片: %v\n", source)
fmt.Printf("目标切片: %v\n", dest)
fmt.Printf("复制了 %d 个元素\n", n)
}
映射(Map)
映射的基本概念
映射就像现实生活中的各种对应关系:身份证号对应个人信息、手机号对应联系人、商品编号对应商品详情。在编程世界中,映射提供了一种通过键(key)快速查找值(value)的数据结构。
在 Go 语言中,映射的类型表示为 map[KeyType]ValueType,其中 KeyType 是键的类型,ValueType 是值的类型。键类型必须是可比较的类型,比如字符串、数字、布尔值,但不能是切片、映射或函数。
package main
import "fmt"
func main() {
// 创建一个简单的映射:学生姓名到成绩的映射
studentGrades := map[string]int{
"张三": 95,
"李四": 87,
"王五": 92,
"赵六": 78,
}
fmt.Printf("张三的成绩:%d 分\n", studentGrades["张三"])
fmt.Printf("李四的成绩:%d 分\n", studentGrades["李四"])
// 映射的长度
fmt.Printf("班级人数:%d 人\n", len(studentGrades))
}
映射的特点
无序性:映射中的元素没有固定的顺序,每次遍历的顺序可能都不同。这是 Go 语言有意设计的,为了防止程序依赖于映射的遍历顺序。
引用类型:映射是引用类型,这意味着当你将一个映射赋值给另一个变量时,它们指向同一个底层数据结构。
package main
import "fmt"
func main() {
original := map[string]int{"编程导航": 100, "面试鸭": 200}
copy := original
// 修改 copy 会影响 original
copy["老鱼简历"] = 300
fmt.Printf("original: %v\n", original) // 包含新添加的元素
fmt.Printf("copy: %v\n", copy)
}

Go 语言提供了多种创建映射的方式,就像烹饪有多种准备食材的方法一样,每种方式都有其适用的场景。
使用字面量创建
这是最直观的方式,就像一开始就把所有的书按类别摆放在书架上:
package main
import "fmt"
func main() {
// 网站访问统计
siteStats := map[string]int{
"编程导航": 50000,
"面试鸭": 30000,
"老鱼简历": 20000,
"算法导航": 15000,
}
// 用户信息映射
userInfo := map[string]interface{}{
"name": "程序员鱼皮",
"age": 25,
"email": "yupi@codefather.com",
"isVip": true,
"skills": []string{"Go", "Java", "Python"},
}
fmt.Printf("编程导航访问量:%d\n", siteStats["编程导航"])
fmt.Printf("用户姓名:%s\n", userInfo["name"])
}
使用 make 函数创建
当你需要创建一个空映射,然后逐步添加元素时,make 函数就像准备了一个空的容器:
package main
import "fmt"
func main() {
// 创建空映射
inventory := make(map[string]int)
// 逐步添加商品库存
inventory["MacBook Pro"] = 10
inventory["iPhone 15"] = 25
inventory["AirPods"] = 50
inventory["iPad"] = 15
fmt.Printf("当前库存:%v\n", inventory)
// 也可以指定初始容量(可选)
cache := make(map[string]string, 100)
cache["token_123"] = "user_data_encrypted"
fmt.Printf("缓存数据:%v\n", cache)
}
声明后初始化
有时候你需要先声明映射变量,稍后再初始化:
package main
import "fmt"
func main() {
var config map[string]string
// 必须先初始化,否则向 nil 映射写入会 panic
config = make(map[string]string)
// 或者直接赋值
config = map[string]string{
"database_host": "localhost",
"database_port": "5432",
"redis_host": "127.0.0.1",
"redis_port": "6379",
}
fmt.Printf("数据库配置:%s:%s\n",
config["database_host"], config["database_port"])
}
增加和修改元素
在映射中,增加新元素和修改现有元素使用相同的语法:
package main
import "fmt"
func main() {
// 创建一个空的用户积分映射
userPoints := make(map[string]int)
// 添加新用户
userPoints["鱼皮"] = 1000
userPoints["小明"] = 500
fmt.Printf("初始积分:%v\n", userPoints)
// 修改现有用户积分
userPoints["鱼皮"] = 1500 // 鱼皮积分增加了
userPoints["小红"] = 800 // 添加新用户小红
fmt.Printf("更新后积分:%v\n", userPoints)
// 批量操作的示例
newUsers := map[string]int{
"编程小助手": 300,
"算法大师": 2000,
"代码审查员": 1200,
}
// 将新用户合并到现有映射中
for name, points := range newUsers {
userPoints[name] = points
}
fmt.Printf("合并后积分:%v\n", userPoints)
}
查询元素
查询映射中的元素有两种方式:直接访问和安全访问。
package main
import "fmt"
func main() {
products := map[string]float64{
"MacBook": 9999.99,
"iPhone": 5999.00,
"AirPods": 1299.00,
"AppleWatch": 2499.00,
}
// 直接访问(如果键不存在,返回零值)
price := products["MacBook"]
fmt.Printf("MacBook 价格:%.2f 元\n", price)
// 访问不存在的键
unknownPrice := products["iPad"]
fmt.Printf("iPad 价格:%.2f 元\n", unknownPrice) // 输出 0.00
// 安全访问(推荐方式)
if price, exists := products["iPhone"]; exists {
fmt.Printf("iPhone 有货,价格:%.2f 元\n", price)
} else {
fmt.Println("iPhone 暂时缺货")
}
// 检查商品是否在售
checkProduct := func(name string) {
if price, exists := products[name]; exists {
fmt.Printf("✅ %s 在售,价格:%.2f 元\n", name, price)
} else {
fmt.Printf("❌ %s 暂未上架\n", name)
}
}
checkProduct("AirPods")
checkProduct("iPad")
checkProduct("剪切助手VIP")
}
映射的遍历方法
遍历映射就像逐一检查仓库中的每个货物,Go 语言提供了灵活的遍历方式来满足不同的需求。
基本遍历方式
package main
import "fmt"
func main() {
websiteTraffic := map[string]int{
"编程导航": 100000,
"面试鸭": 80000,
"老鱼简历": 60000,
"算法导航": 45000,
"剪切助手": 30000,
}
fmt.Println("=== 网站流量统计 ===")
// 遍历键值对
for site, traffic := range websiteTraffic {
fmt.Printf("%-10s: %6d 访问量\n", site, traffic)
}
fmt.Println("\n=== 只遍历网站名称 ===")
// 只遍历键
for site := range websiteTraffic {
fmt.Printf("- %s\n", site)
}
fmt.Println("\n=== 只遍历访问量 ===")
// 只遍历值
for _, traffic := range websiteTraffic {
fmt.Printf("访问量:%d\n", traffic)
}
}
有序遍历
由于映射本身是无序的,如果需要有序遍历,需要先获取键,排序后再遍历:
package main
import (
"fmt"
"sort"
)
func main() {
studentScores := map[string]int{
"张三": 95,
"李四": 87,
"王五": 92,
"赵六": 78,
"钱七": 89,
}
// 按姓名排序遍历
fmt.Println("=== 按姓名排序 ===")
var names []string
for name := range studentScores {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s: %d 分\n", name, studentScores[name])
}
// 按分数排序遍历
fmt.Println("\n=== 按分数排序(从高到低)===")
type Student struct {
Name string
Score int
}
var students []Student
for name, score := range studentScores {
students = append(students, Student{Name: name, Score: score})
}
// 按分数降序排序
sort.Slice(students, func(i, j int) bool {
return students[i].Score > students[j].Score
})
for i, student := range students {
fmt.Printf("第%d名: %s - %d 分\n", i+1, student.Name, student.Score)
}
}
映射的零值与判断
理解映射的零值行为对于编写健壮的程序至关重要,就像了解汽车在不同路况下的表现一样。
映射的零值
映射的零值是 nil,对 nil 映射的读取是安全的,但写入会引发 panic:
package main
import "fmt"
func main() {
var userCache map[string]string
fmt.Printf("映射是否为 nil: %t\n", userCache == nil)
// 读取 nil 映射是安全的,返回零值
value := userCache["key"]
fmt.Printf("从 nil 映射读取值: '%s'\n", value)
// 检查 nil 映射的长度
fmt.Printf("nil 映射长度: %d\n", len(userCache))
// 以下操作会引发 panic,注释掉以避免程序崩溃
// userCache["key"] = "value" // panic: assignment to entry in nil map
// 正确的做法:先初始化
userCache = make(map[string]string)
userCache["user1"] = "鱼皮"
fmt.Printf("初始化后的映射: %v\n", userCache)
}
检查键是否存在
在实际编程中,经常需要区分”键不存在”和”键存在但值为零值”:
package main
import "fmt"
func main() {
// 用户在线状态映射
userStatus := map[string]bool{
"鱼皮": true, // 在线
"编程助手": false, // 离线但已注册
"算法新手": true, // 在线
}
// 检查用户状态的函数
checkUserStatus := func(username string) {
if status, exists := userStatus[username]; exists {
if status {
fmt.Printf("✅ %s 当前在线\n", username)
} else {
fmt.Printf("💤 %s 当前离线\n", username)
}
} else {
fmt.Printf("❓ %s 未注册\n", username)
}
}
// 测试不同情况
checkUserStatus("鱼皮") // 在线用户
checkUserStatus("编程助手") // 离线但已注册的用户
checkUserStatus("访客") // 未注册用户
// 实际应用:缓存系统
cache := map[string]string{
"user:123": "张三",
"user:456": "", // 空字符串也是有效值
}
getValue := func(key string) {
if value, exists := cache[key]; exists {
fmt.Printf("缓存命中 [%s]: '%s'\n", key, value)
} else {
fmt.Printf("缓存未命中 [%s]\n", key)
}
}
getValue("user:123") // 有值
getValue("user:456") // 空值但存在
getValue("user:789") // 不存在
}
面向对象编程
结构体与方法
基本定义语法
让我们从一个简单的例子开始,定义一个表示用户信息的结构体:
package main
import "fmt"
// 定义用户结构体
type User struct {
ID int
Name string
Email string
Age int
IsActive bool
}
func main() {
// 创建结构体实例
var user User
user.ID = 1
user.Name = "程序员鱼皮"
user.Email = "yupi@codefather.com"
user.Age = 26
user.IsActive = true
fmt.Printf("用户信息: %+v\n", user)
fmt.Printf("用户姓名: %s\n", user.Name)
fmt.Printf("用户邮箱: %s\n", user.Email)
}
匿名结构体
有时候我们需要临时使用一个结构体,不想单独定义类型,这时可以使用匿名结构体:
package main
import "fmt"
func main() {
// 定义并初始化匿名结构体
course := struct {
Title string
Duration int
Teacher string
}{
Title: "Go 语言入门教程",
Duration: 120,
Teacher: "程序员鱼皮",
}
fmt.Printf("课程信息: %+v\n", course)
// 在函数参数中使用匿名结构体
processConfig(struct {
Host string
Port int
}{
Host: "localhost",
Port: 8080,
})
}
func processConfig(config struct {
Host string
Port int
}) {
fmt.Printf("服务器配置: %s:%d\n", config.Host, config.Port)
}
Go 语言提供了多种初始化结构体的方式,选择合适的方式可以让代码更加清晰和简洁。bbiHS23HojMEgrsKCKA52+QKtHIedGy+qY0RPDJuNu4=
字段逐一赋值
这是最基本的初始化方式,适合在创建结构体后逐步设置字段:
package main
import "fmt"
type Article struct {
Title string
Content string
Author string
Views int
}
func main() {
var article Article
article.Title = "Go 语言结构体详解"
article.Content = "结构体是 Go 语言中重要的数据类型..."
article.Author = "程序员鱼皮"
article.Views = 1500
fmt.Printf("文章信息: %+v\n", article)
}
使用字面量初始化
字面量初始化是最常用的方式,可以在创建时就设置所有字段:
package main
import "fmt"
type Course struct {
ID int
Title string
Description string
Price float64
Students int
}
func main() {
// 按字段顺序初始化(不推荐,因为字段顺序改变会导致错误)
course1 := Course{1, "算法导航", "可视化算法学习平台", 0.0, 1000}
// 按字段名初始化(推荐)
course2 := Course{
ID: 2,
Title: "面试鸭",
Description: "专业的面试刷题平台",
Price: 99.9,
Students: 2500,
}
// 部分字段初始化,其他字段为零值
course3 := Course{
Title: "老鱼简历",
Price: 29.9,
}
fmt.Printf("课程1: %+v\n", course1)
fmt.Printf("课程2: %+v\n", course2)
fmt.Printf("课程3: %+v\n", course3)
}
使用 new 函数
new 函数会分配内存并返回指向该类型零值的指针:
package main
import "fmt"
type Comment struct {
ID int
Content string
Author string
Likes int
}
func main() {
// 使用 new 创建指针
comment := new(Comment)
comment.ID = 1
comment.Content = "这篇教程写得太好了!"
comment.Author = "张三"
comment.Likes = 25
fmt.Printf("评论信息: %+v\n", *comment)
fmt.Printf("评论内容: %s\n", comment.Content)
// 也可以直接创建指针
comment2 := &Comment{
ID: 2,
Content: "期待更多优质教程",
Author: "李四",
Likes: 18,
}
fmt.Printf("评论2信息: %+v\n", *comment2)
}
构造函数模式
虽然 Go 语言没有构造函数,但我们可以创建工厂函数来初始化结构体:
package main
import (
"fmt"
"time"
)
type User struct {
ID int
Username string
Email string
CreatedAt time.Time
IsActive bool
}
// 用户构造函数
func NewUser(username, email string) *User {
return &User{
ID: generateUserID(),
Username: username,
Email: email,
CreatedAt: time.Now(),
IsActive: true,
}
}
// 带验证的构造函数
func NewUserWithValidation(username, email string) (*User, error) {
if username == "" {
return nil, fmt.Errorf("用户名不能为空")
}
if email == "" {
return nil, fmt.Errorf("邮箱不能为空")
}
return &User{
ID: generateUserID(),
Username: username,
Email: email,
CreatedAt: time.Now(),
IsActive: true,
}, nil
}
// 模拟生成用户ID
func generateUserID() int {
return int(time.Now().Unix())
}
func main() {
// 使用构造函数创建用户
user1 := NewUser("程序员鱼皮", "yupi@codefather.com")
fmt.Printf("用户1: %+v\n", user1)
// 使用带验证的构造函数
user2, err := NewUserWithValidation("张三", "zhangsan@example.com")
if err != nil {
fmt.Printf("创建用户失败: %v\n", err)
} else {
fmt.Printf("用户2: %+v\n", user2)
}
// 验证失败的例子
user3, err := NewUserWithValidation("", "invalid@example.com")
if err != nil {
fmt.Printf("创建用户失败: %v\n", err)
}
}

指针接收者方法
如果方法需要修改接收者的状态,应该使用指针接收者:
package main
import "fmt"
type Counter struct {
Count int
Name string
}
// 值接收者方法(不会修改原始数据)
func (c Counter) GetCount() int {
return c.Count
}
// 指针接收者方法(可以修改原始数据)
func (c *Counter) Increment() {
c.Count++
}
func (c *Counter) IncrementBy(amount int) {
c.Count += amount
}
func (c *Counter) Reset() {
c.Count = 0
}
func (c *Counter) SetName(name string) {
c.Name = name
}
func main() {
counter := Counter{
Count: 0,
Name: "页面访问计数器",
}
fmt.Printf("初始状态: %+v\n", counter)
// 调用指针接收者方法
counter.Increment()
fmt.Printf("增加1后: %+v\n", counter)
counter.IncrementBy(5)
fmt.Printf("增加5后: %+v\n", counter)
counter.SetName("用户注册计数器")
fmt.Printf("修改名称后: %+v\n", counter)
counter.Reset()
fmt.Printf("重置后: %+v\n", counter)
}
改成
func (account *BankAccount) Deposit(amount float64) float64 {
account.Balance += amount
return account.Balance
}

值接收者 vs 指针接收者
选择值接收者还是指针接收者需要考虑以下因素:
package main
import "fmt"
type Document struct {
Title string
Content string
Size int
}
// 值接收者:适用于不需要修改接收者的方法
func (d Document) GetTitle() string {
return d.Title
}
func (d Document) GetSize() int {
return len(d.Content)
}
func (d Document) IsEmpty() bool {
return d.Content == ""
}
// 指针接收者:适用于需要修改接收者的方法
func (d *Document) SetTitle(title string) {
d.Title = title
}
func (d *Document) AppendContent(content string) {
d.Content += content
d.Size = len(d.Content)
}
func (d *Document) Clear() {
d.Content = ""
d.Size = 0
}
// 指针接收者:适用于大型结构体,避免复制开销
func (d *Document) ProcessLargeDocument() {
// 处理大型文档的逻辑
fmt.Printf("处理文档: %s, 大小: %d 字节\n", d.Title, d.Size)
}
func main() {
doc := Document{
Title: "编程导航使用指南",
Content: "编程导航是一个...",
}
// 调用值接收者方法
fmt.Printf("文档标题: %s\n", doc.GetTitle())
fmt.Printf("文档大小: %d 字节\n", doc.GetSize())
fmt.Printf("文档是否为空: %t\n", doc.IsEmpty())
// 调用指针接收者方法
doc.SetTitle("编程导航完整指南")
doc.AppendContent("包含各种编程资源...")
fmt.Printf("修改后的文档: %+v\n", doc)
doc.ProcessLargeDocument()
}
接口与多态
定义一个简单接口:
type Shape interface {
Area() float64
Perimeter() float64
}
Shape是一个接口,定义了两个方法:Area和Perimeter。- 任意类型只要实现了这两个方法,就被认为实现了
Shape接口。
package main
import (
"fmt"
"math"
)
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定义一个结构体
type Circle struct {
Radius float64
}
// Circle 实现 Shape 接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func main() {
c := Circle{Radius: 5}
var s Shape = c // 接口变量可以存储实现了接口的类型
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
空接口
空接口 interface{} 是 Go 的特殊接口,表示所有类型的超集。
- 任意类型都实现了空接口。
- 常用于需要存储任意类型数据的场景,如泛型容器、通用参数等。
package main
import "fmt"
func printValue(val interface{}) {
fmt.Printf("Value: %v, Type: %T\n", val, val)
}
func main() {
printValue(42) // int
printValue("hello") // string
printValue(3.14) // float64
printValue([]int{1, 2}) // slice
}
类型断言
类型断言用于从接口类型中提取其底层值。
基本语法:
value := iface.(Type)
face是接口变量。Type是要断言的具体类型。- 如果类型不匹配,会触发
panic。
package main
import "fmt"
func main() {
var i interface{} = "hello"
str := i.(string) // 类型断言
fmt.Println(str) // 输出:hello
}
带检查的类型断言
为了避免 panic,可以使用带检查的类型断言:
value, ok := iface.(Type)
ok是一个布尔值,表示断言是否成功。- 如果断言失败,
value为零值,ok为false。
package main
import "fmt"
func main() {
var i interface{} = 42
if str, ok := i.(string); ok {
fmt.Println("String:", str)
} else {
fmt.Println("Not a string")
}
}
Not a string
类型选择(type switch)
type switch 是 Go 中的语法结构,用于根据接口变量的具体类型执行不同的逻辑。
package main
import "fmt"
func printType(val interface{}) {
switch v := val.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
case float64:
fmt.Println("Float:", v)
default:
fmt.Println("Unknown type")
}
}
func main() {
printType(42)
printType("hello")
printType(3.14)
printType([]int{1, 2, 3})
}
Integer: 42
String: hello
Float: 3.14
Unknown type
接口组合
接口可以通过嵌套组合,实现更复杂的行为描述。
package main
import "fmt"
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
type ReadWriter interface {
Reader
Writer
}
type File struct{}
func (f File) Read() string {
return "Reading data"
}
func (f File) Write(data string) {
fmt.Println("Writing data:", data)
}
func main() {
var rw ReadWriter = File{}
fmt.Println(rw.Read())
rw.Write("Hello, Go!")
}
接口的零值
接口的零值是 nil。
当接口变量的动态类型和动态值都为 nil 时,接口变量为 nil。
接口零值示例:
package main
import "fmt"
func main() {
var i interface{}
fmt.Println(i == nil) // 输出:true
}
Go 语言的错误处理哲学
Go 语言的错误处理方式与其他编程语言有着本质的不同。它摒弃了传统的异常机制,转而采用”错误即值”的理念,让错误处理变得显式和可控
错误即值的理念
在 Go 语言中,错误不是异常,而是普通的值。就像函数可以返回字符串或整数一样,它也可以返回错误
package main
import (
"fmt"
"strconv"
)
// 模拟面试鸭的用户分数验证
func validateScore(scoreStr string) (int, error) {
// 尝试将字符串转换为整数
score, err := strconv.Atoi(scoreStr)
if err != nil {
return 0, fmt.Errorf("分数格式错误: %s", scoreStr)
}
// 检查分数范围
if score < 0 || score > 100 {
return 0, fmt.Errorf("分数超出范围: %d,有效范围是0-100", score)
}
return score, nil
}
func main() {
fmt.Println("=== 面试鸭分数验证系统 ===")
testScores := []string{"85", "105", "abc", "90", "-10"}
for _, scoreStr := range testScores {
score, err := validateScore(scoreStr)
if err != nil {
fmt.Printf("❌ 输入 '%s': %v\n", scoreStr, err)
} else {
fmt.Printf("✅ 输入 '%s': 有效分数 %d\n", scoreStr, score)
}
}
}
error 接口的本质
Go 语言中的 error 是一个内置接口,定义非常简单:
type error interface {
Error() string
}
任何实现了 Error() 方法的类型都可以作为错误使用:
package main
import "fmt"
// 编程导航的课程错误类型
type CourseError struct {
CourseID int
Message string
Code string
}
func (ce *CourseError) Error() string {
return fmt.Sprintf("课程错误 [%s]: 课程ID %d - %s", ce.Code, ce.CourseID, ce.Message)
}
// 用户权限错误类型
type PermissionError struct {
UserID int
Operation string
RequiredRole string
}
func (pe *PermissionError) Error() string {
return fmt.Sprintf("权限不足: 用户 %d 尝试执行 '%s',需要 %s 权限",
pe.UserID, pe.Operation, pe.RequiredRole)
}
// 模拟课程访问检查
func checkCourseAccess(userID, courseID int, userRole string) error {
// 检查课程是否存在
if courseID <= 0 || courseID > 1000 {
return &CourseError{
CourseID: courseID,
Message: "课程不存在",
Code: "COURSE_NOT_FOUND",
}
}
// 检查付费课程权限
if courseID > 100 && userRole != "premium" {
return &PermissionError{
UserID: userID,
Operation: "访问付费课程",
RequiredRole: "premium",
}
}
return nil
}
func main() {
fmt.Println("=== 编程导航课程访问检查 ===")
testCases := []struct {
userID int
courseID int
userRole string
}{
{1001, 50, "free"}, // 正常访问免费课程
{1002, 200, "free"}, // 免费用户访问付费课程
{1003, 1500, "premium"}, // 访问不存在的课程
{1004, 200, "premium"}, // 付费用户正常访问
}
for _, tc := range testCases {
fmt.Printf("用户 %d (角色: %s) 访问课程 %d: ", tc.userID, tc.userRole, tc.courseID)
if err := checkCourseAccess(tc.userID, tc.courseID, tc.userRole); err != nil {
fmt.Printf("❌ %v\n", err)
// 根据错误类型进行不同处理
switch e := err.(type) {
case *CourseError:
fmt.Printf(" -> 建议:检查课程ID或联系管理员\n")
case *PermissionError:
fmt.Printf(" -> 建议:升级到付费会员\n")
}
} else {
fmt.Printf("✅ 访问成功\n")
}
fmt.Println()
}
}
错误创建与处理模式
Go 语言提供了多种创建和处理错误的方式,从简单的错误消息到复杂的错误类型,应对不同的使用场景。
基本错误创建方式
package main
import (
"errors"
"fmt"
"os"
"time"
)
// 算法导航的文件操作示例
type FileManager struct {
basePath string
}
func NewFileManager(basePath string) *FileManager {
return &FileManager{basePath: basePath}
}
func (fm *FileManager) ReadFile(filename string) (string, error) {
// 方式1: 使用 errors.New 创建简单错误
if filename == "" {
return "", errors.New("文件名不能为空")
}
// 方式2: 使用 fmt.Errorf 创建格式化错误
if len(filename) > 100 {
return "", fmt.Errorf("文件名过长: %d 字符,最大允许100字符", len(filename))
}
fullPath := fm.basePath + "/" + filename
// 模拟文件读取
if filename == "non_existent.txt" {
return "", fmt.Errorf("文件不存在: %s", fullPath)
}
if filename == "permission_denied.txt" {
return "", &os.PathError{
Op: "open",
Path: fullPath,
Err: errors.New("permission denied"),
}
}
// 模拟成功读取
return fmt.Sprintf("算法导航文件内容: %s (读取时间: %s)",
filename, time.Now().Format("15:04:05")), nil
}
func (fm *FileManager) WriteFile(filename, content string) error {
if filename == "" {
return errors.New("文件名不能为空")
}
if content == "" {
return errors.New("文件内容不能为空")
}
// 模拟磁盘空间不足
if len(content) > 1000 {
return fmt.Errorf("磁盘空间不足: 需要 %d 字节,可用空间不足", len(content))
}
fmt.Printf("文件写入成功: %s (%d 字节)\n", filename, len(content))
return nil
}
// 批量处理文件
func (fm *FileManager) ProcessFiles(operations []struct {
op string
filename string
content string
}) []error {
var errors []error
for i, operation := range operations {
fmt.Printf("操作 %d: %s %s\n", i+1, operation.op, operation.filename)
var err error
switch operation.op {
case "read":
_, err = fm.ReadFile(operation.filename)
case "write":
err = fm.WriteFile(operation.filename, operation.content)
default:
err = fmt.Errorf("不支持的操作: %s", operation.op)
}
if err != nil {
fmt.Printf(" ❌ 失败: %v\n", err)
errors = append(errors, err)
} else {
fmt.Printf(" ✅ 成功\n")
errors = append(errors, nil)
}
}
return errors
}
func main() {
fmt.Println("=== 算法导航文件管理系统 ===")
manager := NewFileManager("/data/algorithms")
// 测试各种操作
operations := []struct {
op string
filename string
content string
}{
{"read", "bubble_sort.go", ""},
{"read", "", ""}, // 空文件名
{"read", "non_existent.txt", ""}, // 不存在的文件
{"read", "permission_denied.txt", ""}, // 权限错误
{"write", "quick_sort.go", "package main\n\nfunc quickSort() {\n // 快速排序实现\n}"},
{"write", "large_file.txt", string(make([]byte, 2000))}, // 文件过大
{"write", "", "content"}, // 空文件名
{"delete", "test.txt", ""}, // 不支持的操作
}
allErrors := manager.ProcessFiles(operations)
// 统计结果
successCount := 0
for _, err := range allErrors {
if err == nil {
successCount++
}
}
fmt.Printf("\n=== 处理结果统计 ===\n")
fmt.Printf("总操作数: %d\n", len(operations))
fmt.Printf("成功: %d\n", successCount)
fmt.Printf("失败: %d\n", len(operations)-successCount)
fmt.Printf("成功率: %.1f%%\n", float64(successCount)/float64(len(operations))*100)
}
Go 并发
并发是指程序同时执行多个任务的能力。
Go 语言支持并发,通过 goroutines 和 channels 提供了一种简洁且高效的方式来实现并发。
Goroutines:
- Go 中的并发执行单位,类似于轻量级的线程。
- Goroutine 的调度由 Go 运行时管理,用户无需手动分配线程。
- 使用
go关键字启动 Goroutine。 - Goroutine 是非阻塞的,可以高效地运行成千上万个 Goroutine。
Channel:
- Go 中用于在 Goroutine 之间通信的机制。
- 支持同步和数据共享,避免了显式的锁机制。
- 使用
chan关键字创建,通过<-操作符发送和接收数据。
Scheduler(调度器):
Go 的调度器基于 GMP 模型,调度器会将 Goroutine 分配到系统线程中执行,并通过 M 和 P 的配合高效管理并发。
- G:Goroutine。
- M:系统线程(Machine)。
- P:逻辑处理器(Processor)。
Goroutine
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
例如:
go f(x, y, z)
开启一个新的 goroutine:
f(x, y, z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
package main
import (
"fmt"
"time"
)
func sayHello() {
for i := 0; i < 5; i++ {
fmt.Println("Hello")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sayHello() // 启动 Goroutine
for i := 0; i < 5; i++ {
fmt.Println("Main")
time.Sleep(100 * time.Millisecond)
}
}
执行以上代码,你会看到输出的 Main 和 Hello。输出是没有固定先后顺序,因为它们是两个 goroutine 在执行:
Main Hello Main Hello
通道(Channel)
通道(Channel)是用于 Goroutine 之间的数据传递。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
使用 make 函数创建一个 channel,使用 <- 操作符发送和接收数据。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
输出结果为:
-5 17 12
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
执行输出结果为:
1 2
Go 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
执行输出结果为:
0 1 1 2 3 5 8 13 21 34
time 包的基本使用
Go 语言的 time 包是处理时间的标准库,就像一个功能齐全的时间工具箱。无论是获取当前时间,还是进行复杂的时间计算,这个包都能满足你的需求。RtEQkusw+geaNN5XakyyhWKey8P0+Na6uEHLKJ7ocJI=
获取当前时间
最基本的操作就是获取当前时间,这在日志记录、用户活动跟踪等场景中非常常用:
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间
now := time.Now()
fmt.Printf("当前时间: %v\n", now)
// 面试鸭用户登录时间记录
userLoginTime := time.Now()
fmt.Printf("用户登录时间: %v\n", userLoginTime)
// 获取时间的各个组成部分
year, month, day := now.Date()
hour, minute, second := now.Clock()
fmt.Printf("日期: %d年%d月%d日\n", year, int(month), day)
fmt.Printf("时间: %d时%d分%d秒\n", hour, minute, second)
// 获取星期几
weekday := now.Weekday()
fmt.Printf("今天是: %s\n", weekday)
// 获取一年中的第几天
yearday := now.YearDay()
fmt.Printf("今年的第%d天\n", yearday)
}
Go 语言特有特性
efer:优雅的资源管理
在日常生活中,我们经常需要”善后”工作:出门记得锁门,用完水记得关水龙头,借了书记得还书。编程中也是如此,打开文件后要关闭,申请内存后要释放,获取锁后要解锁。Go 语言的 defer 机制让这种”善后”工作变得优雅而自然。pj5A64H2SgIJrYrLQ8Gb1IjvI88FQigGQSUZPjYaMg4=
defer 的基本使用
defer 关键字可以让函数调用延迟到包含它的函数返回之前执行。就像在函数门口贴了一张便利贴,提醒自己在离开时要做的事情。
package main
import (
"fmt"
"os"
)
func readConfigFile() {
fmt.Println("程序员鱼皮开始读取配置文件...")
// 打开文件
file, err := os.Open("config.txt")
if err != nil {
fmt.Println("无法打开配置文件:", err)
return
}
// 使用 defer 确保文件会被关闭
defer func() {
fmt.Println("鱼皮正在关闭配置文件...")
file.Close()
}()
// 读取文件内容(这里简化处理)
fmt.Println("正在读取配置...")
fmt.Println("配置读取完成")
// 即使函数在这里返回,defer 的代码仍会执行
}
func main() {
readConfigFile()
fmt.Println("程序结束")
}
defer 的执行顺序
defer 语句的执行顺序遵循”后进先出”(LIFO)的原则,就像叠盘子一样,最后放上去的盘子最先被拿走
package main
import "fmt"
func demonstrateDeferOrder() {
fmt.Println("编程导航系统初始化...")
defer fmt.Println("步骤1:关闭数据库连接")
defer fmt.Println("步骤2:清理缓存")
defer fmt.Println("步骤3:保存用户状态")
fmt.Println("执行主要业务逻辑")
fmt.Println("业务逻辑执行完成")
}
func main() {
demonstrateDeferOrder()
// 输出顺序:
// 编程导航系统初始化...
// 执行主要业务逻辑
// 业务逻辑执行完成
// 步骤3:保存用户状态
// 步骤2:清理缓存
// 步骤1:关闭数据库连接
}
defer 的实用场景
defer 在资源管理、错误处理和计时统计等场景中特别有用:
package main
import (
“fmt”
“sync”
“time”
)
// 计时统计示例
func measureExecutionTime(taskName string) func() {
start := time.Now()
fmt.Printf(“面试鸭开始执行任务:%s\n”, taskName)
return func() {
duration := time.Since(start)
fmt.Printf("任务 %s 执行完成,耗时:%v\n", taskName, duration)
}
}
// 使用 mutex 的示例
func processUserData(mu *sync.Mutex, userID int) {
mu.Lock()
defer mu.Unlock() // 确保锁一定会被释放
fmt.Printf("处理用户 %d 的数据中...\n", userID)
time.Sleep(100 * time.Millisecond) // 模拟处理时间
fmt.Printf("用户 %d 数据处理完成\n", userID)
}
func main() {
// 计时示例
done := measureExecutionTime(“数据分析”)
defer done()
// 模拟一些工作
time.Sleep(200 * time.Millisecond)
// 并发安全示例
var mu sync.Mutex
processUserData(&mu, 1001)
}
panic 和 recover:Go 式的异常处理
Go 语言没有传统的 try-catch 异常处理机制,而是采用了独特的 panic 和 recover 机制。这就像是为程序准备了一个”紧急刹车”和”安全气囊”系统。
理解 panic
panic 是一种运行时错误,当程序遇到无法继续执行的情况时会触发。就像汽车遇到紧急情况时会急刹车一样。
package main
import "fmt"
func riskyOperation(x int) {
fmt.Printf("老鱼简历系统正在处理用户 %d\n", x)
if x < 0 {
panic("用户ID不能为负数!")
}
if x == 0 {
panic("用户ID不能为零!")
}
fmt.Printf("用户 %d 处理成功\n", x)
}
func demonstratePanic() {
fmt.Println("开始处理用户数据...")
// 正常情况
riskyOperation(1001)
// 这会触发 panic
riskyOperation(-1)
// 这行代码不会执行,因为上面已经 panic 了
fmt.Println("所有用户处理完成")
}
func main() {
demonstratePanic()
}
使用 recover 捕获 panic
recover 函数可以捕获 panic,阻止程序崩溃,就像安全气囊在车祸时保护乘客一样。但是 recover 只能在 defer 函数中使用。
package main
import "fmt"
func safeOperation(x int) (result string, err error) {
// 使用 defer 和 recover 来捕获可能的 panic
defer func() {
if r := recover(); r != nil {
result = ""
err = fmt.Errorf("操作失败: %v", r)
}
}()
// 可能引发 panic 的操作
if x < 0 {
panic("算法导航系统:输入值不能为负数")
}
result = fmt.Sprintf("处理成功,结果:%d", x*x)
return result, nil
}
func robustSystem() {
testCases := []int{5, -3, 0, 10}
for _, value := range testCases {
result, err := safeOperation(value)
if err != nil {
fmt.Printf("处理值 %d 时出错:%v\n", value, err)
} else {
fmt.Printf("处理值 %d 成功:%s\n", value, result)
}
}
}
func main() {
fmt.Println("剪切助手稳定版本测试开始...")
robustSystem()
fmt.Println("测试完成,系统运行正常")
}
最佳实践:何时使用 panic
panic 不应该用作常规的错误处理机制,而应该保留给真正的”紧急情况”:
package main
import (
"errors"
"fmt"
)
// 好的做法:返回错误而不是 panic
func validateUserInput(email string) error {
if email == "" {
return errors.New("邮箱不能为空")
}
if !isValidEmail(email) {
return errors.New("邮箱格式不正确")
}
return nil
}
// 简化的邮箱验证
func isValidEmail(email string) bool {
return len(email) > 0 && len(email) < 100
}
// 应该使用 panic 的场景:程序内部错误
func mustInitializeConfig() *Config {
config, err := loadConfig()
if err != nil {
// 配置文件是程序运行的前提,加载失败应该 panic
panic(fmt.Sprintf("无法加载配置文件: %v", err))
}
return config
}
type Config struct {
DatabaseURL string
Port int
}
func loadConfig() (*Config, error) {
// 模拟配置加载
return &Config{
DatabaseURL: "localhost:5432",
Port: 8080,
}, nil
}
func main() {
// 处理用户输入错误
if err := validateUserInput(""); err != nil {
fmt.Printf("用户输入错误:%v\n", err)
}
// 初始化系统配置
config := mustInitializeConfig()
fmt.Printf("配置加载成功:%+v\n", config)
}
空接口与类型断言:动态类型的魅力
Go 语言是静态类型语言,但通过空接口 interface{} 和类型断言,它也具备了一定的动态类型能力。这就像是在严格的规则中开了一扇灵活性的窗户。
空接口
空接口 interface{} 可以表示任何类型,就像一个万能的容器:







