开始找实际项目
项目一步步的学着敲着来
选取原则: 最近更新 + 代码量少 + 能运行有截图有示例
第一步是 gitee.com/hhxsv5/go-r… go 的Redis 大键分析然后写入csv 这个应该很简单,作为第一下的完整项目
第二步是这个 gitee.com/shirdonl/ca… go的验证码页面服务,这个只有一个页面,入门web服务简单点
gitee.com/gzydong/Lum… go 的网页版在线聊天,这个非常完善,值得学习,最好是能打包成mac 软件
gitee.com/b0cloud/b0p… go 的多端网盘服务,这个估计比较麻烦,最后再看
gitee.com/mirrors/Bad… 一个可嵌入,持久,简单,快速的键值(KV)存储,纯 go 编写
gitee.com/wanyuxiang0… mgface.com-使用go语言来实现分布式对象存储
gitee.com/dubbogo/dub… Apache Dubbo go语言实现
gitee.com/jqncc/OAM 也是web的增删改查 GO-OAM是基于go语言开发的web版运维资源管理系统,算是简版的CMDB,将各种难记的账号、密码、主机、文档、应用等资源管理起来,以项目方式整合
gitee.com/nway/websql 也是web的增删改查 web版数据库管理工具,由go语言编写,可以做到无依赖跨平台,编译后无需安装运行环境,支持本地模式和远程部署模式,支持基于操作系统的人脸/指纹识别登录。开源协议宽松,可以自由使用,以满足个人和企业需求。
gitee.com/mirrors/git… 这个不知道是什么,可能不需要太关注 Gitea的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用Go作为后端语言,这使我们只要生成一个可执行程序即可。
另外剩下的就是
github.com/ouqiang/goc… 最经典部分,而且是亲身使用过 非常好用的一个定时任务工具
github.com/ouqiang/sha… ssr 的go
github.com/ouqiang/sup… Supervisor是*nix环境下的进程管理工具, 可以把前台进程转换为守护进程, 当进程异常退出时自动重启. supervisor-event-listener监听进程异常退出事件, 并发送通知.
或者直接在 github.com/topics/go 这里查看最热的代码仓库
阅读GolangTraining的笔记
字符串格式化中 %x %X 的区别就是一个是小写字母,一个是大写字母来表示16进制,比如 1a3f和 1A3F的区别
%q 就是代表单个字符的字面量, %q 也能当做%s的输出,就是会附带上自动转义,比如
str := "This is a "string" with $igns and \chars"
fmt.Printf("The quoted string: %q\n", str)
fmt.Printf("The quoted string: %s\n", str)
这样是输出
The quoted string: "This is a \"string\" with $igns and \\chars"
The quoted string: This is a "string" with $igns and \chars
%s 是输出字符并且不做任何转义,不管你的字符中包含什么内容,都将会原样输出,类似于 反引号的效果
还有一种是 %v 的输出
%v 是用默认的格式进行输出,根据你的变量是什么,就输出什么样的格式
-
**
布尔型**:输出为true或false。 -
整数型:以十进制形式输出。
-
浮点数:以浮点数形式输出,精度由编译器决定。
-
复数:输出为
(real ± imagi),其中real是实部,imag是虚部。 -
字符串:直接输出字符串内容,不转义特殊字符。
-
通道:输出通道的描述,通常是类型信息。
-
接口:输出接口内部存储的值(不是接口本身),类似于
%v。 -
指针:输出指针指向的值,而不是指针的内存地址。
-
结构体:输出结构体的字段,格式为
{field1:value1 field2:value2 ...}。 -
切片:输出切片中的所有元素,格式为
[value1 value2 ...]。
我感觉最重要的就是 数组和字典的输出,能直观的看得到
h := []int{1, 2, 3, 4, 5}
i := map[int]int{
1: 1,
2: 2,
3: 3,
}
就是输出
h := []int{1, 2, 3, 4, 5}
i := map[int]int{
1: 1,
2: 2,
3: 3,
}
如果需要更精确的控制格式,可以使用其他的占位符,%v 经常用于那些你不知道变量类型或者不关心变量类型是什么的时候,他提供了一种方便的格式来获取变量的字符串表达的形式
另外还有 %T 的占位符,就是类似于type的意思,用于输出变量的类型
同样的 %v 还给了一个样例,
fmt.Printf("nilPtr is nil? %v\n", nilPtr == nil) // 输出: nilPtr is nil? true
这样 true false 也能输出,不过本来这就是bool类型,%v 感觉就像是verbose 单词一样
03
变量的定义,如果是一个类型可以写到一行 var a,b,c int 或者同时赋值 var a, b ,c int = 1,2,3
对于字符串来说,居然还是可以
var message = "ok"
直接这样推断赋值,而不需要 var message string message = "ok'
同样的推断,还能用于int上,比如刚才的a b c 同时赋值1,2,3也可以写成
var a,b,c = 1,2,3
不需要后面声明类型了
或者可以同时推断不同的类型
var a, b, c, d, e, f = 1, false, 3, "ok", []int{1, 2, 3}, map[int]int{1: 2, 3: 4}
这样子的和 := 相比就能一次赋值多个了,不需要 := 很多行
刚才说错了,直接的 := 也可以复制多次
a, b, c := 1, false, 3
如果使用了var 那就不需要使用: 使用了: 就不需要使用 var
04
如果在同一个包里面的变量,那么可以在一个文件里面定义变量,然后另外一个文件里面直接引用,无论函数内外都能引用,效果类似于全局变量的效果
所以感觉是可以把全局变量,单独写在一个包的一个单独的go文件里面的
比如这里,虽然在printer里面没有任何关于MyName的定义,但是那是因为在name.go单独的文件里面定义了,而他们又是属于同一个包下的,所以可以当成全局变量来使用
对于这种写法
func main() {
x := 42
fmt.Println(x)
{
fmt.Println(x)
y := "The credit belongs with the one who is in the ring."
fmt.Println(y)
}
//fmt.Println(y) // outside scope of y
}
他不是定义了一个函数,而是定义了一个代码块,固定了一个作用域,注释掉的那一行的y就取不到,因为在作用域外了,嵌套代码块并不是创建新的函数,而是提供了一种定义局部作用域的方式
代码块适用于组织代码和定义作用域的语法结果
对于这样一段代码,他的输出居然是1 和 2 x 居然被影响到了
func main() {
x := 0
increment := func() int {
x++
return x
}
fmt.Println(increment())
fmt.Println(increment())
}
这是因为increment是一个闭包,变量x是被闭包捕获到了的变量,捕获到了,就会影响到这个变量。 在go中,当你在韩硕或者闭包中对一个变量进行修改,如果这个变量是在外部作用域声明的,且闭包捕获到了这个变量,那么对这个变量的所有修改都是对原始变量的修改
如果改成参数传递的话,就是正常的输出 1 1 了
func main() {
//x := 0
//increment := func() int {
// x++
// return x
//}
//fmt.Println(increment())
//fmt.Println(increment())
x := 0
increment := func(x int) int {
x++
return x
}
fmt.Println(increment(x))
fmt.Println(increment(x))
}
这里还有一个点,函数的签名是
func(x int) int
之前是看过能吧返回值写到函数定义里面的,于是想写成
func(x int) (n int) 结果发现不行,这是因为没有充分理解命名值返回,所谓的命名值返回,最大的好处就是能直接从函数定义里面,看到返回的内容,而不需要看里面的细节的return,认为是省略return的一种写法,以下是几个典型的例子
package main
import (
"fmt"
)
func say(a int) (b int) {
b = a + 1
return
}
func main() {
fmt.Println(say(1))
}
以上代码不是一次性写出来了,改了几次,主要改动的地方是:
- 如果想要跑的话,包名也必须是main 才行,不然没有run 出来,新建go文件的时候,自动创建的包名是文件夹的名字了不是package main 导致没有找到run出来
- 写say的时候,我想着因为指定了b会返回 return就不写了,结果发现必须强制让你写return,也就是说,你虽然做了 命名值返回,但是结尾的return还是要写,那么请问这有什么必要呢,return也没有省略掉啊
更加明显的效果大概是这样子的,比如 max()函数
func maxValue(a, b int) (ans int) {
if a > b {
ans = a
} else {
ans = b
}
return
}
func main() {
fmt.Println(maxValue(1, 2))
}
但是还有一点更奇怪的是,if else 强制式Java这种写法,C风格的if else 无法使用 会报语法错误
if a > b {
ans = a
}
else {
ans = b
}
unexpected else, expected } 反正就是 if else 强制使用大括号且必须跟着不能换行使用
另外命名变量还有一种写法是用于错误处理,比如
func divided(a, b float64) (ans float64, err error) {
if b == 0 {
err = fmt.Errorf("cannot divied by zero")
return
}
ans = a / b
return
}
func main() {
fmt.Println(divided(1, 0))
}
这里写的时候有几个要注意的,第一个就是 err 不是 := 而是直接 = 即可,另外错误的类型error 可以直接使用
另外命名变量还有一种写法是延迟执行,在返回前修改命名返回值,比如如下代码
func test() (result int) {
defer func() {
result++
return
}()
return 5
}
func main() {
fmt.Println(test())
}
这个输出居然是6,为什么是6呢,如果把result++ 去掉,输出为5的话,这样就可以解释了 在go中,当函数声明了命名返回值,go 会自动在函数体内创建一个同名的变量,其作用域覆盖整个函数体,这个变量在函数体内部任意地方都是可见的,并且会被初始化为其类型的零值
如果没有result++的话,那么输出就是5,因为执行 return 5 返回值的就是5,而你func 又要返回值,返回值又是result, 那么return 5就相当于 result=5 然后return result
而defer 里面如果有result++的话,那么result就捕获了,注意又是用词的捕获,捕获了result值,然后访问并修改result
命名返回值来构建结果对象
代码样例如下
type User struct {
Name string
Email string
}
func CreateUser(name, email string) (user User, err error) {
user = User{
Name: name,
Email: email,
}
err = nil
return
}
func main() {
fmt.Println(CreateUser("aaa", "bbb@bb.com"))
//fmt.Printf("%v", CreateUser("aaa", "bbb@bb.com"))
}
这里注意一点,user 的创建居然不是 := 和 err 一样是直接等于就可以了
实际上,创建一个类的话,确实是
user := User{ Name: "Alice", Email: "alice@example.com", }
:=的写法,但是这里为什么可以用等于号呢,是因为提前创建了变量,才能用等于呢,因为在定义函数的时候,声明了命名返回值, (user User, err error) 相当于就执行了一个变量声明的操作,所以后面就可以直接使用 = 来赋值, err 可以直接等于也是同理
这种写法的话,等于直接先把要返回的给复制了,然后直接return,就会自然而然的return好,而不需要做什么其他的操作
命名返回值来进行多级错误处理
展示代码如下
func performAction() (status string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recover from panic: %v", r)
}
}()
panic("error in performAction")
status = "success"
return
}
func main() {
fmt.Println(performAction())
status, err := performAction()
fmt.Printf("%v, %v", status, err)
}
输出结果是 recover from panic: error in performAction
如果没有panic 发生,那么就是设置了status="success" 如果发生了,那么status 就是空”“
命名返回值简化多个返回值的函数
func reterieveData() (data []byte, count int, err error) {
data = make([]byte, 1024)
count = 100
//panic("error")
return
}
func main() {
fmt.Println(reterieveData())
}
如果不注释掉panic 就会返回很多个0000 然后是100 然后是nil,如果出现了panic 的话,那么就会报错
panic: error
goroutine 1 [running]:
main.reterieveData(...)
GolangTraining/04_scope/02_block-scope/02_closure/03_06/main.go:9
main.main()
GolangTraining/04_scope/02_block-scope/02_closure/03_06/main.go:14 +0x3b
因为你的异常没有捕获