Go学习案例TodoList
2024-10-11
| 2024-10-15
0  |  Read Time 0 min
type
status
date
slug
summary
tags
category
icon
password
 
完成了一个todoList用来快速掌握Go语言的语法
 
 
 
比如读取的这个地方用的:
bufio.NewReader(os.Stdin)
  • bufio 是 Go 标准库中的一个包,用于提供缓冲 I/O 操作。
  • os.Stdin 是标准输入(通常是键盘)。
  • NewReader 创建一个从标准输入读取的缓冲读取器。
reader.ReadString('\n')
  • ReadString 是 bufio.Reader 的一个方法。
  • 它读取输入直到遇到指定的分隔符(这里是 '\n',即换行符)。
  • 返回读取的字符串和一个错误(这里用 _ 忽略了错误)
 
_ 是空白标识符,用于忽略不需要的返回值(这里是错误)—》可以留意这个错误处理的方式,和java不一样
 
 
 
检查切片是否为空,注意这里用的是len就是实际的长度,不是cap了,区分一下。
 
for _,todo 因为这里没有用i来做索引,就是遍历todo所以才用的 _ 来处理
 
 
 
 
  • 声明一个整型变量 id。
  • fmt.Scanln(&id) 从标准输入读取一个整数。
  • &id 是 id 的内存地址,允许 Scanln 直接修改 id 的值。
  • _ 忽略 Scanln 返回的扫描项数。
  • err 接收可能的错误。
 
 
理解一下这块的指针:
先回顾一下指针的概念,太久没接触过指针,这块已经有点忘了。
 
类比:邮政地址
想象一下,变量就像是房子,而指针就像是这个房子的地址:1. 房子(变量):存储实际的东西(数据)。
  • 地址(指针):告诉你房子在哪里(内存地址)。
基本概念:
  • 变量:存储数据的地方。
  • 指针:存储另一个变量的内存地址。
 
 
拿一个样例来理解更好:
 
  • x := 10: 创建一个整数变量 x 并赋值 10。
  • var p *int = &x:
&x 获取 x 的内存地址。
p 是一个指针变量,存储 x 的地址。
  • p = 20:
p 表示"p 指向的值"。
这行代码通过指针修改 x 的值。
  • changeValue(&x):
传递 x 的地址给函数。
函数可以直接修改 x 的值。
 
 
& 符号(取地址操作符):
用在变量前面
意思是"获取这个变量的内存地址"
例如:&x 表示 x 的内存地址
符号有两种用途:
a. 声明指针类型:
用在类型前面
例如:var p *int 声明 p 为整数指针
b. 解引用操作符:
用在指针变量前面
意思是"获取这个地址上存储的值"
例如:*p 表示 p 指向的值
 
 

复杂一点的版本

基于上述版本,加深一些复杂度,以此可以帮助理解更多go的特性。
 
这里主要是封装
和上面相比
 
 
 
这里要理解一个事情 就是这个函数的为什么可以这么写?(tl *TodoList)
对比理解:就是java类中的方法的意思
 
 
 
 
定义方式:
  • Go 在结构体外部定义方法,但通过接收者与结构体关联。
  • Java 在类的内部直接定义方法。
 
  • 接收者 vs this:
  • Go 使用显式的接收者(如 (p Person) 或 (p *Person))。
  • Java 隐式使用 this 关键字引用当前对象。
 
 
  • 指针接收者 vs 引用:
  • Go 的指针接收者(如 (p *Person))类似于 Java 中通过引用修改对象状态。
  • Java 默认通过引用传递对象,不需要显式指定。
 
 
  • 方法调用:
  • 两种语言的方法调用语法非常相似。
  • 封装:
  • Go 通过大小写控制可见性(大写开头为公开)。
  • Java 使用 public、private 等关键字控制可见性
 
然后这里用上* 指针接收,就是类似于c那种,可以对结构体的东西进行修改,所以就是要这样做。
举个例子:
 
 
最终效果一样。只是过程用了封装
 
主要是学会用* 来修改结构体
notion image
 
 
 

错误处理

意思是如果传空的,可能返回error类型的。没问题的就直接nil,就是没错的意思
 
fmt.Errorf 是 Go 标准库 fmt 包中的一个函数,用于创建格式化的错误消息。它的工作原理类似于 fmt.Sprintf,但是它返回一个 error 类型而不是字符串。
 
错误处理,主要是为了定位问题,保持函数是这样的格式就好,没事就nil,有事Errorf
 
 

接口

 
接口定义好方法,注意大写就是public的,其他可以用。然后对应的方法也要大写。
 
可以看看这里的用法,其实和用结构体差不多,
 
  • 大写开头的标识符(如 TodoManager)是公开的(public),可以被其他包导入和使用。
  • 小写开头的标识符是私有的(private),只能在定义它的包内部使用。
 
 
对比一下结构体和接口:
 
 
定义和用途: 结构体(Struct):定义数据结构,包含字段。 接口(Interface):定义方法集,不包含数据。
 
实现: 结构体:直接定义字段和方法。 接口:只定义方法签名,由其他类型(如结构体)来实现。
 
数据存储: 结构体:可以存储数据。 接口:不存储数据,只是一个抽象定义。
实例化: 结构体:可以直接实例化。 接口:不能直接实例化,只能声明接口类型的变量,并赋予实现了该接口的值。
灵活性: 结构体:相对固定,一旦定义就不容易改变。 接口:非常灵活,任何类型只要实现了接口定义的所有方法,就被认为实现了该接口。
多态性: 结构体:本身不提供多态性。 接口:是 Go 实现多态的主要方式。
 
 
 

文件持久化

 
用这个json Marshal/ Unmarshal 来序列化和反序列化 写入
直接用os.WirteFile就能写入了
 
然后就是说要导出的一定要加上json的,然后要大写,不然没办法导出去的,我指的是结构那里。还是遵循原则就是 大小写来表示public or not
 
 
 

并发处理

 
先从最基本的例子来跑,以此来理解并发
go就是并发的关键字
 
time.Sleep(1 * time.Second)这里这样写的意思是:
  • Go 程序在主函数返回时会退出,即使还有其他 goroutines 在运行。
  • 如果没有这行代码,主函数可能会在生产者和消费者完成工作之前就结束,导致程序过早退出。
 
然后就是生产者和消费者那两个函数的入参,意思就是说:
  • chan<- T:只能发送 T 类型的值(只写 channel)
  • <-chan T:只能接收 T 类型的值(只读 channel)
 
箭头指向就代表了是入 还是 出
 
 
使用 &wg 来传递 WaitGroup 的地址有几个重要原因:
传递引用而非副本
如果我们不使用指针,每个 worker 函数会得到 WaitGroup 的一个副本,而不是原始的 WaitGroup。
我们希望所有的 goroutine 操作同一个 WaitGroup 实例。 使用指针确保每个 worker 都在修改同一个 WaitGroup 的计数。
 
 
select语句就是运行响应优先准备好的channel。
<-ch1 就是比如msg1接收的意思,有了就处理。
 
 
在 NewTodoList 函数中,创建了一个带缓冲的 channel saveChan。 同时启动了一个 goroutine 运行 saveWorker 方法。这个 goroutine 会一直运行,等待 saveChan 中的信号。 每当需要保存数据时(如添加、完成或删除任务后),会调用 Save 方法。 Save 方法尝试向 saveChan 发送一个信号(空结构体)。如果 channel 已满,它会立即返回而不阻塞。 当 saveWorker 从 channel 接收到信号时,它会调用 saveToFile 方法来实际保存数据。 这种设计的优点是: 保存操作是异步的,不会阻塞主程序的执行。 如果短时间内多次调用 Save,只会触发一次实际的文件写入操作,避免频繁 I/O。
 
  • 开发
  • Go学习之Gin模型工程
    Loading...
    Catalog