[Advance]GoLang Learn Data Day 4

AdvanceGoLang Learn Data Day 4

原贴地址:https://www.cnblogs.com/Reisentyan/p/20224502

GoLang似乎是很有意思的东西,写起来手感比py好多了。最近有奇思妙想,决定做一个好玩的东西出来,顺便就作为自己的简历的项目了。

个人感觉,Go语言比C++难一点,虽然有可能是错觉啦。

函数的进阶:闭包与变量赋值

1. 闭包与递归的本质界定

两者分属不同的计算机科学范畴,解决不同维度的工程问题:

  • 递归(属控制流范畴): 指函数在执行流中直接或间接调用自身,核心在于逻辑的重复与分治,依赖严格的终止条件以避免调用栈溢出。
  • 闭包(属状态管理范畴): 指函数与其被创建时所处的词法环境的结合体。其核心在于状态的持久化,允许函数脱离其原始执行上下文后,依然能够隐式持有并修改原作用域内的局部变量。

简单的说,闭包里的变量是可持久化的。

2. 函数作为变量赋值的底层原理

函数可以作为变量进行赋值和使用的底层原理,其核心在于:"函数本质上是内存中的一段可执行指令,而变量存储的是这段指令的入口地址"

具体可通过以下四个技术维度进行解析:

  1. 内存驻留: 源代码经编译器转化为机器指令后,会被操作系统统一加载到进程内存的代码段中。每个函数在代码段中都有一个唯一的起始内存地址,即"入口点"。
  2. 指针本质: 当我们将函数赋值给某个变量时,底层并没有复制任何代码逻辑,而是将该函数在代码段中的入口地址(内存指针)赋值给了这个变量。此时,该变量实质上是一个函数指针(Function Pointer)
  3. 跳转执行: 当通过变量名加上括号(如 f())发起调用时,编译器会将其翻译为 CPU 的间接跳转指令。CPU 读取该内存地址,建立新的函数栈帧以传递参数,并开始执行该地址处的机器指令。
  4. 闭包的底层扩展: 为了支持"闭包",函数变量的底层实现通常是一个复杂的结构体。它包含两部分:指令指针 (指向函数执行逻辑)和环境指针(指向该函数所捕获的外部局部变量)。

结论: 函数能作为变量赋值,是因为在计算机底层架构中,"数据"和"指令"在内存层面是统一的。变量不仅可以存储数据的值,同样也可以存储指令序列的内存地址。

切片和数组的关系

1. 传参引发的问题

go 复制代码
func getall(arr []int) int {
	var res = 0
	for _, va := range arr {
		res += va
	}
	return res
}

func main(){
	var number1 = [...]int{1}
	var number2 = [...]int{1, 2}
	fmt.Println(getall(number1) + getall(number2))
}

这段代码是错误的!原因是函数形参是切片类型,而 [...]int 这个东西是个数组,因此不可传递。

  • 解决方法 1: 直接把 [...] 里的 ... 删掉,使其变成切片,这样就可以使用了。
  • 解决方法 2: 把数组变成切片再传入函数就可以了:getall(number1[:])

2. 切片的底层结构与机制

  • 底层结构 (24字节): 切片并非真实数组,而是底层连续内存的引用描述符。固定包含 3 个字段:Data (指针)Len (逻辑长度)Cap (物理容量)。作为参数传递时极具效率,因其仅发生这 24 字节的值拷贝。
  • 动态扩容机制: append 操作未触顶时(Len < Cap),直接写入新值;触顶时(Len == Cap),触发扩容堆分配机制,申请更大内存并拷贝旧数据。
  • 内存共享副作用: 极易引发状态污染。衍生新切片默认共享底层内存;append 后是否共享取决于是否触发扩容。工程上建议强制使用原变量接收返回值 ,或使用 copy() 进行深拷贝。

切片就像 C++ 的 vector,但是也不完全相同。由于切片类似一个视窗,并非原始数据,因此增删查改的操作非常快。内部有 len,cap,ptr,总计 24 字节。

附上 Slice 的一个快速初始化操作:

go 复制代码
go// 初始化一个大小为 100100 且元素全为 1 的切片
var slicenum = make([]int, 100100)
for p := range slicenum {
    slicenum[p] = 1
}

接口

1. 接口核心机制

  • 隐式实现: 摒弃传统的显式继承,类型只需实现某接口定义的所有方法签名,即隐式判定实现了此接口。解耦了定义与具体实现。
  • 空接口: 不包含任何方法声明的接口(interface{}any),所有数据类型均自动实现空接口。
  • 类型断言: 针对接口变量,可通过 v, ok := x.(T)switch v := x.(type) 进行动态类型检查与向下转型。
  • 工程准则: 接收接口,返回结构体。

2. 多态实战代码

go 复制代码
package main

import "fmt"

// 1. 定义接口(提需求/定规则)
type Speaker interface {
	Speak() string
}

// 2. 定义结构体(结构体完全不知道 Speaker 接口的存在,只管存数据)
type Dog struct { Name string }
type Cat struct { Name string }

// 3. 绑定方法(隐式实现)
func (d Dog) Speak() string {
	return d.Name + ":旺旺"
}
func (c Cat) Speak() string {
	return c.Name + ":咪咪"
}

// 4. 多态函数(参数类型写接口,屏蔽底层细节)
func MakeSound(s Speaker) string {
	// s 究竟是狗还是猫?不重要,底层会自动找到对应的真实方法并执行
	return s.Speak()
}

func main() {
	gou := Dog{Name: "旺财"}
	mao := Cat{Name: "汤圆"}

	fmt.Println(MakeSound(gou)) // 输出:旺财:旺旺
	fmt.Println(MakeSound(mao)) // 输出:汤圆:咪咪
}

泛型

下面给出一段泛型的代码:

go 复制代码
package main

import "fmt"

//[T any]是泛型声明,代表T可以是任意类型
//func PrintSlice[T any](arr []T) {
func PrintSlice[T int | string](arr []T) {
	for _, v := range arr {
		fmt.Print(v, " ")
	}
	fmt.Println()
}

func main() { //泛型编程
	PrintSlice([]int{1, 2, 3, 4, 5})
	PrintSlice([]string{"小王", "小李"})	
}

Go 的泛型底层采用 GC Shape Stenciling字典传递 相结合的混合机制:

  • 编译器会根据实际传入的类型局部实例化机器码。
  • 为避免代码膨胀,内存布局特征完全一致的类型(GC Shape)将共享同一份底层汇编代码。
  • 调用时隐式注入类型字典指针,以便在运行时动态获取真实类型的特性。

错误处理

这里给出一段基本的错误处理代码:

go 复制代码
package main

import (
	"errors"
	"fmt"
)

// 模拟一个扣款函数
func pay(balance int, amount int) (int, error) {
	if amount > balance {
		//制造一个错误,正常结果返回0
		return 0, errors.New("余额不足")
	}
	//没出错就返回空指针
	return balance - amount, nil
}

func main() {
	newBalance, err := pay(100, 500)

	if err != nil {
		fmt.Println("交易失败,原因:", err)
		return
	}

	fmt.Println("交易成功,余额:", newBalance)
}

引入errors,判断err是否为空(nil)就行了。

假设 有错误极为致命,需要立即退出程序,使用panic(恐慌),这个不需要errors

同样的,下面给出一段基本的代码:

go 复制代码
package main

import (
	"fmt"
)

// 模拟一个扣款函数
func pay(balance int, amount int) (int, error) {
	if amount > balance {
		//制造一个错误,正常结果返回0
		panic("致命错误,没钱了")
		//return 0, errors.New("余额不足")
	}
	//没出错就返回空指针
	return balance - amount, nil
}

func main() {
	newBalance, err := pay(100, 500)

    //使用panic之后,再也不会执行到下面了
	if err != nil {
		fmt.Println("交易失败,原因:", err)
		return
	}

	fmt.Println("交易成功,余额:", newBalance)
}

大部分情况 ,我们都不希望程序全部退出,因此使用defer+recover可以保证主程序依旧运行

同样的,下面给出一段代码:

go 复制代码
package main

import "fmt"

func getaerror() {
	//defer这段代码一定会在当前这段函数结束之后才会执行
	//就算被panic退出也会执行
	defer func() {
		//尝试拦截panic
		if r := recover(); r != nil {
			fmt.Println("拦截到错误,错误是:", r)
		}
	}()

	panic("越界错误")
}

func main() {

	getaerror()
	fmt.Println("主程序依旧执行")
}

defer会在当前所在的函数结束之后执行,recover是专门用来拦截panic的内置函数,使代码从崩溃态变成正常态,defer最后的那个括号是立即调用的意思

高并发编程

1. 协程与多线程的区别

  • C++ 传统多线程:属于**内核态物理线程。由操作系统直接调度,极其笨重。创建时通常预分配 1MB~8MB 内存栈,切换开销极大。
  • Go 协程:属于 用户态轻量级逻辑线程**。由 Go 的 Runtime 全权接管,初始内存栈仅 2KB(支持动态扩容)。
  • 底层支撑 (GMP调度模型) :协程脱离操作系统调度,通过 M:N 的映射机制(将海量 M 个协程,复用调度到 N 个物理线程上),实现极低上下文切换开销。
    • M (Machine):操作系统的物理线程。
    • G (Goroutine):协程任务。
    • P (Processor):逻辑处理器(装满等待执行 G 的队列)。

2. 并发同步器

**** 当主程序结束,所有协程立刻结束。因此我们需要一个工具等一等协程。 wg.Done() 本质上是 Add(-1)。但在不知道有多少个协程(动态数量)的情况下,绝对不能Add(1) 写在协程内部,因为协程启动需要时间,主程序可能直接跑完了!

go 复制代码
package main

import (
	"fmt"
	"sync" // Synchronization(同步)
	"time"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(2) // 有两个并发任务,必须写在外部

	go func() {
		defer wg.Done() // 该协程任务完成前汇报
		fmt.Println("任务 1 正在执行...")
		time.Sleep(1 * time.Second)
	}()

	go func() {
		defer wg.Done() 
		fmt.Println("任务 2 正在执行...")
		time.Sleep(1 * time.Second)
	}()

	fmt.Println("主程序等待任务完成")
	wg.Wait()
	fmt.Println("所有任务完成,安全退出")
}

3. 多线程的管道通信

特别注意,接收通道数据的操作是阻塞的,如果管道没有东西就会永远等待!

go 复制代码
package main

import "fmt"

func main() {
	// 创造一根管道,只能使 int 类型流动
	ch := make(chan int)

	go func() {
		fmt.Println("后台协程计算中...")
		result := 42
		ch <- result // 将数据塞入管道
		fmt.Println("后台协程结束")
	}()

	fmt.Println("主程序等待管道出水")

	// 阻塞等待管道的数据流出
	value := <-ch

	fmt.Println("管道的内容:", value)
}
相关推荐
MaCa .BaKa2 小时前
55-宠物爱心救助领养系统-宠物救助领养系统
java·vue.js·tomcat·maven·springboot·宠物救助领养系统
brycegao3212 小时前
Vue3+Go 全栈项目上线阿里云|从 0 到 1 踩坑全纪录
开发语言·阿里云·golang
ch.ju2 小时前
Java Programming Chapter 4——cite
java·开发语言
小张小张爱学习2 小时前
Spring Boot 多线程并发入门教程:ThreadPoolTaskExecutor + CompletableFuture
java·spring boot·后端
西安邮电大学2 小时前
Redis核心数据结构以及应用场景
java·redis·后端·其他·面试
NiceCloud喜云2 小时前
Claude Code 跑 HyperFrames 实测:本地生成 AI 视频素材全流程
java·运维·人工智能·自动化·json·音视频·飞书
曹牧2 小时前
Oracle:UNIX时间戳
数据库·oracle·unix
XiaoLin laile2 小时前
【无标题】
网络·数据库·人工智能
lili00122 小时前
Claude自动修Bug配置优化与避坑指南
java·人工智能·python·bug·ai编程