GO 语言基础

目录

函数:多返回值、命名返回、可变参数

[指针:传值 vs 传指针](#指针:传值 vs 传指针)

结构体、方法(值/指针接收者)

切片:创建、追加、截取、共享底层数组

map:增删查、遍历、初始化

接口:隐式实现、空接口、小接口

[错误处理:error、%w、Is / As](#错误处理:error、%w、Is / As)


函数:多返回值、命名返回、可变参数

Go 复制代码
package main

import "fmt"

// 普通函数
func add(a, b int) int {
	return a + b
}

// 多返回值:Go 里极常见,尤其是 (结果, error)
func div(a, b int) (int, error) {
	if b == 0 {
		return 0, fmt.Errorf("除数不能为 0")
	}
	return a / b, nil
}

// 命名返回值:return 时可裸 return(初学建议少用,可读性一般)
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return // 等价于 return x, y
}

// 可变参数:内部当切片用
func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func main() {
	fmt.Println(add(1, 2))

	result, err := div(10, 2)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("商 =", result)

	a, b := split(17)
	fmt.Println(a, b)

	fmt.Println(sum(1, 2, 3, 4))

	// 把切片展开传给可变参数
	arr := []int{1, 2, 3}
	fmt.Println(sum(arr...))
}

工作习惯

Go 复制代码
result, err := someCall()
if err != nil {
    return err // 或包装后返回
}
// 使用 result

指针:传值 vs 传指针

Go 复制代码
package main

import "fmt"

// 传值:函数内改的是副本,外层不变
func addOneByValue(n int) {
	n = n + 1
}

// 传指针:可以修改外层变量
func addOneByPointer(n *int) {
	*n = *n + 1 // * 解引用:取指针指向的值并赋值
}

type User struct {
	Name string
	Age  int
}

// 大结构体常用指针,避免拷贝;也可在函数内修改字段
func birthday(u *User) {
	if u == nil {
		return // 使用指针前先判空
	}
	u.Age++
}

func main() {
	x := 10
	addOneByValue(x)
	fmt.Println(x) // 仍是 10

	addOneByPointer(&x) // & 取地址
	fmt.Println(x)      // 变成 11

	p := &x
	fmt.Println(p)  // 地址
	fmt.Println(*p) // 通过指针读值:11

	u := User{Name: "Tom", Age: 20}
	birthday(&u)
	fmt.Println(u.Age) // 21
}

何时用指针

  • 需要在函数内修改原变量
  • 结构体较大,避免拷贝
  • 表示"可能没有":用 nil(但要注意空指针)

结构体、方法(值/指针接收者)

Go 复制代码
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

// 值接收者:方法内改动不影响原对象(操作的是副本)
func (p Person) Greeting() string {
	return "Hi, I am " + p.Name
}

// 指针接收者:可修改原对象;也避免大对象拷贝
func (p *Person) GrowOlder() {
	p.Age++
}

// 即使是值接收者,Go 允许用指针调用;反之亦然(有规则)
// 经验法则:要改字段 → 用指针接收者;方法较多时保持风格统一

func NewPerson(name string, age int) *Person {
	// 构造函数惯例:返回指针
	return &Person{Name: name, Age: age}
}

func main() {
	// 多种初始化方式
	var p1 Person                 // 零值:Name="", Age=0
	p2 := Person{Name: "Alice", Age: 18}
	p3 := Person{Name: "Bob"}     // Age 为 0
	p4 := &Person{Name: "Carl", Age: 30}

	fmt.Println(p1, p2, p3, p4.Greeting())

	p2.GrowOlder() // 编译器会自动取地址:(&p2).GrowOlder()
	fmt.Println(p2.Age)

	p := NewPerson("Daisy", 22)
	fmt.Println(p.Greeting(), p.Age)
}

嵌入(组合,类似"继承"的替代)

Go 复制代码
type Animal struct {
	Name string
}

func (a Animal) Speak() {
	fmt.Println(a.Name, "makes a sound")
}

type Dog struct {
	Animal // 匿名嵌入:提升字段和方法
	Breed  string
}

func demoEmbed() {
	d := Dog{
		Animal: Animal{Name: "Buddy"},
		Breed:  "Husky",
	}
	d.Speak()      // 可直接调用嵌入类型的方法
	fmt.Println(d.Name) // 可直接访问嵌入字段
}

切片:创建、追加、截取、共享底层数组

切片是工作中最常用的序列类型(数组较少直接用)。

Go 复制代码
package main

import "fmt"

func main() {
	// ---------- 创建 ----------
	var s1 []int          // nil 切片,len=0, cap=0
	s2 := []int{1, 2, 3}  // 字面量
	s3 := make([]int, 3)  // len=3, cap=3,元素为 0
	s4 := make([]int, 3, 5) // len=3, cap=5

	fmt.Println(s1 == nil) // true
	fmt.Println(len(s2), cap(s2))
	fmt.Println(s3, s4)

	// ---------- 追加 ----------
	s2 = append(s2, 4)
	s2 = append(s2, 5, 6)
	fmt.Println(s2)

	// ---------- 截取:s[low:high],不含 high ----------
	a := []int{0, 1, 2, 3, 4, 5}
	b := a[1:4] // [1 2 3]
	fmt.Println(b)

	// ★ 关键:截取后与原切片可能共享底层数组
	b[0] = 99
	fmt.Println(a) // a[1] 也变成 99

	// 若要独立拷贝,用 copy
	c := make([]int, len(b))
	copy(c, b)
	c[0] = 100
	fmt.Println(b, c) // b 不受影响

	// ---------- 删除下标 i 的元素(常用技巧)----------
	i := 2
	a = append(a[:i], a[i+1:]...)
	fmt.Println(a)

	// ---------- 遍历 ----------
	for idx, v := range s2 {
		fmt.Println(idx, v)
	}
}

必记坑

  1. append 可能触发扩容,返回新切片,必须接住:s = append(s, x)
  2. 截取共享底层数组,改子切片可能影响原切片
  3. nil 切片可以 append,不必先 make(读/写下标前要确保有长度)

map:增删查、遍历、初始化

Go 复制代码
package main

import "fmt"

func main() {
	// ---------- 初始化 ----------
	var m1 map[string]int          // nil map:可读不可写
	m2 := map[string]int{}         // 空 map,可写
	m3 := make(map[string]int)     // 推荐
	m4 := map[string]int{
		"alice": 90,
		"bob":   80,
	}

	_ = m1
	fmt.Println(m2, m3, m4)

	// ---------- 增 / 改 ----------
	m3["tom"] = 95
	m3["tom"] = 96 // 改

	// ---------- 查 ----------
	score, ok := m3["tom"]
	if ok {
		fmt.Println("找到:", score)
	} else {
		fmt.Println("不存在")
	}

	// 直接取值:不存在时得到值类型零值(int 为 0),所以重要查询要用 ok
	fmt.Println(m3["missing"]) // 0

	// ---------- 删 ----------
	delete(m3, "tom")

	// ---------- 遍历(无序!)----------
	for k, v := range m4 {
		fmt.Println(k, v)
	}

	// ---------- 引用类型注意 ----------
	m5 := m4
	m5["alice"] = 100
	fmt.Println(m4["alice"]) // 100:map 赋值是引用语义
}

必记坑

  • nil map 不能写:先 make 或字面量初始化
  • map 不是并发安全的,多 goroutine 写要用锁或 sync.Map
  • 遍历顺序每次可能不同

接口:隐式实现、空接口、小接口

Go 复制代码
package main

import "fmt"

// 接口:只定义行为(方法集合)
type Speaker interface {
	Speak() string
}

type Dog struct {
	Name string
}

// Dog 实现了 Speak,无需写 implements
func (d Dog) Speak() string {
	return d.Name + ": woof"
}

type Cat struct {
	Name string
}

func (c Cat) Speak() string {
	return c.Name + ": meow"
}

// 面向接口编程:参数用接口,而不是具体类型
func announce(s Speaker) {
	fmt.Println(s.Speak())
}

func main() {
	announce(Dog{Name: "Buddy"})
	announce(Cat{Name: "Mimi"})

	// ---------- 空接口:可装任何类型(Go 1.18+ 更推荐 any)----------
	var anyValue any // 等价于 interface{}
	anyValue = 1
	anyValue = "hello"
	anyValue = Dog{Name: "X"}
	fmt.Println(anyValue)

	// 类型断言
	str, ok := anyValue.(string)
	if ok {
		fmt.Println(str)
	}

	// 类型 switch
	switch v := anyValue.(type) {
	case int:
		fmt.Println("int", v)
	case string:
		fmt.Println("string", v)
	case Dog:
		fmt.Println("dog", v.Name)
	default:
		fmt.Println("other", v)
	}
}

工作中的小接口思维

Go 复制代码
// 好:接口小,只暴露需要的方法
type UserRepository interface {
	FindByID(id int64) (*User, error)
	Save(u *User) error
}

// handler/service 依赖接口,测试时可 mock

错误处理:error%wIs / As

Go 复制代码
package main

import (
	"errors"
	"fmt"
)

// 哨兵错误:包级可导出变量,便于 errors.Is 判断
var ErrNotFound = errors.New("not found")

type ValidationError struct {
	Field string
	Msg   string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

func findUser(id int) (string, error) {
	if id <= 0 {
		return "", &ValidationError{Field: "id", Msg: "must be positive"}
	}
	if id == 404 {
		return "", ErrNotFound
	}
	return "alice", nil
}

func serviceGetUser(id int) (string, error) {
	name, err := findUser(id)
	if err != nil {
		// %w 包装错误,保留错误链,便于上层 Is/As
		return "", fmt.Errorf("serviceGetUser: %w", err)
	}
	return name, nil
}

func main() {
	_, err := serviceGetUser(404)
	if err != nil {
		// Is:判断错误链中是否包含某个哨兵错误
		if errors.Is(err, ErrNotFound) {
			fmt.Println("业务:用户不存在")
		}

		// As:判断错误链中是否有某类型,并取出
		var ve *ValidationError
		if errors.As(err, &ve) {
			fmt.Println("校验失败:", ve.Field, ve.Msg)
		}

		fmt.Println("完整错误:", err)
	}
}

工作习惯

  • 用返回值传错误,不要用 panic 当业务失败
  • 包装时用 %w,日志里可打印完整链
  • 上层用 Is/As 做分支,不要靠字符串比较
相关推荐
键盘会跳舞1 小时前
C++:std::tuple 源码级深度拆解——变参模板、SFINAE与模板元编程核心技巧
开发语言·c++·sfinae·变参模板
东华万里2 小时前
第40篇C++核心基础与工程实践:从底层逻辑到避坑指南
开发语言·c++·面试·大学生专区
wuyk5552 小时前
第 6 章 FOC 完整系统整合:从算法到真机跑起来
c语言·开发语言·stm32·单片机·嵌入式硬件
luj_17682 小时前
元设计的诱惑与现实
c语言·开发语言·c++·经验分享·算法
小星星闪亮登场2 小时前
ST表--倍增思想
开发语言·数据结构·c++·算法·思维
东华万里2 小时前
第40篇 C++核心基础与工程实践:从底层逻辑到避坑指南
开发语言·c++·大学生专区
程序员雷欧2 小时前
AQS深度解析
开发语言·python
马可家的菠萝2 小时前
Vue3 + Canvas 手绘笔记工程化实践:别把画布只当成一张 PNG
前端·vue.js·算法