Go 语言从入门到实战
《Go 语言从入门到实战》 的学习笔记,欢迎阅读斧正。感觉该专栏整体来说对有些后端编程经验的来说比无后端编程经验的人更友好。
数据类型
运算符
算数运算符
比较运算符
用 == 比较数组
相同维数切含有相同个数元素的数组才可以比较,每个元素都相同的才相等。
import "testing"// 相同维数切含有相同个数元素的数组才可以比较,每个元素都相同的才相等。
func TestCompareArray(t *testing.T) {a := [...]int{1, 2, 3, 4}b := [...]int{1, 3, 4, 5}//c := [...]int{1, 2, 3, 4, 5}d := [...]int{1, 2, 3, 4}e := [...]int{1, 4, 3, 5}// 输出 falset.Log(a == b)// 长度不同的两个数组,比较会得到一个编译错误//t.Log(a == c)// 输出 truet.Log(a == d)// 输出 falset.Log(b == e)
}
逻辑运算符
位运算符
按位清零运算符
import "testing"const (Readable = 1 << iotaWritableExecutable
)// 按位清零运算符
func TestBitClear(t *testing.T) {a := 7 // 0111// true true truet.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)a = a &^ Readable// 6t.Log(a &^ Readable)// false true truet.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
循环
Go 语言只支持循环关键字 for
While条件循环
无限循环
条件
if 条件
与其他语言差异:
- condition 表达式结果必须是布尔值
- 支持变量赋值
if var declaration; condition{
//do something
switch 条件
与其他语言差异:
- 条件表达式不限制为常量或者整数;
- 单个 case 中,可以出现多个结果选项,使用逗号分隔;
- 与C语言等规则相反,Go语言不需要用 break 来明确退出一个 case;
- 可以不设定 switch 之后的条件表达式,在此种情况下,整个 switch 结构与多个 if…else… 的逻辑作用等同
数组和切片
数组
声明
遍历
截取
切片
声明
内部结构
注意:切片不可以比较
Map
声明
访问的 key 不存在时,仍会返回 0 值,不能通过返回 nil 来判断元素是否存在(int 类型,默认是 0,如果换成 string 类型,默认就是空字符串了)
Map 与工厂
import ("testing"
)func TestMapWithFunValue(t *testing.T) {m := map[int]func(op int) int{}m[1] = func(op int) int { return op }m[2] = func(op int) int { return op * op }m[3] = func(op int) int { return op * op * op }// 输出 2 4 8t.Log(m[1](2), m[2](2), m[3](2))}
实现 set
字符串
与其他语言差别:
- string 是数据类型,不是引用或指针类型
- string 是只读的 byte slice,len() 函数可以它所包含的 byte 数
- string 的 byte 数组可以存放任何数据
函数
- 可以有多个返回值
- 所有参数都是值传递
- 函数可以作为变量的vi
- 函数可以作为参数和返回值
这个需要好好看看,好像还有闭包之类的知识
// 延时一秒后返回入参
func SlowFun(op int) int {time.Sleep(time.Second * 1)return op
}// 定义一个函数timeSpent帮助记录入参函数的执行耗时
//
// 入参是一个函数, 返回也是一个函数
func timeSpent(入参函数名 func(op int) int) func(op int) int {return func(返回函数的参数 int) int {start := time.Now()ret := 入参函数名(返回函数的参数) // 执行有入参的该函数fmt.Println("该入参函数执行结果:", ret)fmt.Println("时间损耗:", time.Since(start).Seconds())return ret}
}func TestFn1(t *testing.T) {// 给timeSpent入参一个函数, 我们返回一个函数, 这个函数执行这个入参函数并打印该的执行时间// tsSF 得到的就是返回的函数,我们给这个函数提供一个 「返回函数的参数」-> 10tsSF := timeSpent(SlowFun)// 打印执行t.Log(tsSF(10))
}
可变参数
defer 函数
延迟执行函数,可以当做 Java 中的 try…catch…finally 使用
面向对象
封装数据和行为
结构体定义
初始化
行为/方法定义
接口
定义
type Programmer interface {WriteHelloWorld() Code
}
实现
type GoProgrammer struct {
}
func (p *GoProgrammer) WriteHelloWorld() Code {return "fmt.Println(\"Hello World!\")"
}
接口变量
自定义类型
type IntConv func(op int) int
注意 func
复合
Go 不支持继承,但是可以通过复合的方式来复用
匿名类型嵌入
import ("fmt""testing"
)type Pet struct {
}func (p *Pet) Speak() {fmt.Print("...")
}func (p *Pet) SpeakTo(host string) {p.Speak()fmt.Println("\n", host)
}type Dog struct {//p *PetPet // 内嵌的结构类型没法当继承来使用
}
不是继承,如果把内部的 struct 看做父类,外部的 struct 看成子类,会发现以下问题:
- 不支持子类替换
- 子类并不是继承了父类的方法
- 父类的定义的方法无法访问子类的数据和方法
多态
代码:
import ("fmt""testing"
)type Code string
type Programmer interface {WriteHelloWorld() Code
}type GoProgrammer struct {
}func (p *GoProgrammer) WriteHelloWorld() Code {return "fmt.Println(\"Hello World!\")"
}type JavaProgrammer struct {
}func (p *JavaProgrammer) WriteHelloWorld() Code {return "System.out.Println(\"Hello World!\")"
}// interface 作为入参,必须是指针实例
func writeFirstProgram(p Programmer) {fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}func TestPolymorphism(t *testing.T) {goProg := &GoProgrammer{}// 注意 &,取地址或者理解为指针javaProg := new(JavaProgrammer)writeFirstProgram(goProg)writeFirstProgram(javaProg)
}
空接口
- 空接口可以表示任何类型(可以理解为 Java 中的 Object)
- 通过断言来将空接口转换为指定类型
v, ok := p.(int) // ok = true 时转换成功
import ("fmt""testing"
)// 空接口可以代表任何类型
func DoSomething(p interface{}) {// if 版本://if i, ok := p.(int); ok {// fmt.Println("Integer", i)// return//}//if i, ok := p.(string); ok {// fmt.Println("string", i)// return//}//fmt.Println("Unknown Type")// switch 版本:switch v := p.(type) {case int:fmt.Println("Integer", v)case string:fmt.Println("string", v)default:fmt.Println("Unknown Type")}
}func TestEmptyInterfaceAssertion(t *testing.T) {DoSomething(1)DoSomething("test")DoSomething(true)
}
接口使用原则
第一行可以理解为 单一职责,第三行可以理解为 接口隔离原则
错误机制
- 没有异常机制
- error 类型实现了 error 接口
- 可以通过 errors.New 来快速创建错误实例
errors.New("it's a arrer
实践
定义不同的错误变量,以便于判断错误类型
及早失败,避免嵌套
感觉类似于 卫语句
func GetFibonacci1(str string) {var (i interr errorlist []int)if i, err = strconv.Atoi(str); err == nil {if list, err = GetFibonacci(i); err == nil {fmt.Println(list)} else {fmt.Println("Error", err)}} else {fmt.Println("Error", err)}
}// GetFibonacci2 相对于 GetFibonacci1 来说,避免了错误嵌套,感觉类似 Java 中卫语句?
func GetFibonacci2(str string) {var (i interr errorlist []int)if i, err = strconv.Atoi(str); err != nil {fmt.Println("Error", err)return}if list, err = GetFibonacci(i); err != nil {fmt.Println("Error", err)return}fmt.Println(list)}
panic
- panic 用于不可以恢复的错误
- panic 退出前会执行 defer 指定的内容
panic vs os.Exit
后者不会调用 defer
执行的函数,后者退出时不输出当前调用栈信息
recover
错误恢复代码,类似于 Java 中的 try...catch...
中的 catch
import ("errors""fmt""testing"
)func TestPanicVxExit(t *testing.T) {defer func() {if err := recover(); err != nil {fmt.Println("recovered from ", err)}}()fmt.Println("Start")panic(errors.New("Something wrong!"))
}// 输出:
// Start
// recovered from Something wrong!
// --- PASS: TestPanicVxExit (0.00s)
使用的时候不能随便 recover,不要随便捕获异常,该抛错误的就抛错误,要不不知道什么错。
package
- 基本的复用模块单元(另外,首字母大写表明可被包外代码访问)
- 代码的 package 可以和所在目录不一致
- 同一目录里的 Go 代码的 package 要保持一致
init 方法
- 在 main 被执行前,所有依赖的 package 的 init 方法都会被执行
- 不同包的 init 函数按照包导入的依赖关系决定执行顺序
- 每个包可以有多个 init 函数
- 包的每个源文件也可以有多个 init 函数,这点比较特殊
测试
单元测试
内置单元测试框架
- Fail,Error:该测试失败,该测试继续,其他测试继续执行
- FailNow,Fatal:该测试失败,该测试中止,其他测试继续执行
- 代码覆盖率:go test-v-cover
- 断言:https://github.com/stretchr/testify
import ("fmt""github.com/stretchr/testify/assert""testing"
)func TestSquare(t *testing.T) {inputs := [...]int{1, 2, 3}expected := [...]int{1, 4, 9}for i := 0; i < len(inputs); i++ {ret := square(inputs[i])if ret != expected[i] {t.Errorf("input is %d, the expected is %d, the actual %d",inputs[i], expected[i], ret)}}
}func TestErrorInCode(t *testing.T) {fmt.Println("Start")t.Error("Error")fmt.Println("End")
}func TestFailInCode(t *testing.T) {fmt.Println("Start")t.Fatal("Error")fmt.Println("End")
}func TestSquareWithAssert(t *testing.T) {inputs := [...]int{1, 2, 3}expected := [...]int{1, 4, 9}for i := 0; i < len(inputs); i++ {ret := square(inputs[i])assert.Equal(t, expected[i], ret)}
}
Benchmark
相关命令(非 Windows 把双引号去掉):
- go test -bench=“.”
- go test -bench=“.” -benchmem
BDD
Behavior Driven Development,让业务领域的专家参与开发。
感觉理解起来就是写清需求……
BDD In Go
项目网站:https://github.com/smartystreets/goconvey
安装:go get -u github.com/smartystreets/goconvey/convey
启动 UI:$GOPATH/bin/goconvey
不安全编程
理解的 Go 的不安全编程是指用到了 unsafe 包中的指针:使用"不安全编程"帮你绕过 Go 语言中的类型检测系统
合理的类型转换以及原子操作不算不安全编程
import ("fmt""sync""sync/atomic""testing""time""unsafe"
)type Customer struct {Name stringAge int
}// 理解的 Go 的不安全编程是指用到了 unsafe 包中的内容:https://www.cnblogs.com/traditional/p/11890639.html
func TestUnsafe(t *testing.T) {i := 10f := *(*float64)(unsafe.Pointer(&i))t.Log(unsafe.Pointer(&i))t.Log(f)
}// The cases is suitable for unsafe
type MyInt int// 合理的类型转换
func TestConvert(t *testing.T) {a := []int{1, 2, 3, 4}b := *(*[]MyInt)(unsafe.Pointer(&a))t.Log(b)
}// 原子类型操作
func TestAtomic(t *testing.T) {var shareBufPtr unsafe.PointerwriteDataFn := func() {data := []int{}for i := 0; i < 100; i++ {data = append(data, i)}atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data))}readDataFn := func() {data := atomic.LoadPointer(&shareBufPtr)fmt.Println(data, *(*[]int)(data))}var wg sync.WaitGroupwriteDataFn()for i := 0; i < 10; i++ {wg.Add(1)go func() {for i := 0; i < 10; i++ {writeDataFn()time.Sleep(time.Microsecond * 100)}wg.Done()}()wg.Add(1)go func() {for i := 0; i < 10; i++ {readDataFn()time.Sleep(time.Microsecond * 100)}wg.Done()}()}wg.Wait()
}
常见任务
- 这些没太花重心学,后期直接看 web 相关框架
内置 JSON 解析
利用 反射 实现,通过 FeildTag 来标识对应的 json 值
var jsonStr = `{"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}
} `func TestEmbeddedJson(t *testing.T) {e := new(Employee)err := json.Unmarshal([]byte(jsonStr), e)if err != nil {t.Error(err)}fmt.Println(*e)if v, err := json.Marshal(e); err == nil {fmt.Println(string(v))} else {t.Error(err)}}
easyjson
是用代码生成的
相关代码:easyjson
HTTP 服务
import ("fmt""net/http""time"
)func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello World!")})http.HandleFunc("/time/", func(w http.ResponseWriter, r *http.Request) {t := time.Now()timeStr := fmt.Sprintf("{\"time\": \"%s\"}", t)w.Write([]byte(timeStr))})http.ListenAndServe(":8080", nil)
}
路由规则
- URL分为两种,末尾是 /:表示一个子树,后面可以跟其他子路径;末尾不是 /,表示一个叶子,固定的路径
- 以 / 结尾的URL可以匹配它的任何子路径,比如 /images会匹配 /images/cute-cat.jpg
- 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理
- 如果没有找到任何匹配项,会返回404错误
default router
更好的 router
httprouter: A high performance HTTP request router that scales well
import ("fmt""log""net/http""github.com/julienschmidt/httprouter"
)func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {fmt.Fprint(w, "Welcome!\n")
}func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}func main() {router := httprouter.New()router.GET("/", Index)router.GET("/hello/:name", Hello)log.Fatal(http.ListenAndServe(":8080", router))
}
构建 RESTful
这个看这里的代码吧:geektime/go_learning/code/ch45/roa/resource_oriented_arc.go
关联阅读
Go 反射
Go 并发
Go 语言从入门到实战相关推荐
- 《Go语言从入门到实战》学习笔记(1)——Go语言学习路线图、简介
非常有幸在<极客时间>上看到<Go语言从入门到实战>这门课程,本课程的作者给出了较为详细的学习路线图,具体如下: 学习路线图 学习目的 个人学习的目的主要是了解Go语言的基本 ...
- 《Go语言从入门到实战》学习笔记(2)——编写第一个Go语言程序
本节内容主要侧重于环境构建以及经典代码的编写. 一.开发环境构建 GOPATH在1.8版本之前设置这个环境变量 1.8版本后(含1.8)如果没有设置使用默认值,在Unix上默认为$HOME/go,在W ...
- C语言从入门到实战——函数
函数 1.函数分类 C 程序是由函数组成的,我们写的代码都是由主函数 main()开始执行的.函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元. 从函数定义的角度看,函数可分为系统函数和用 ...
- Go语言入门到实战——14.Go语言的协程机制以及并发机制
Go语言入门到实战--00主目录 在上一讲中我们学习了Go语言的包的知识已经依赖管理. 协程(groutine)是一种更加轻量级的线程(thread). 一.协程与线程得到比较 1.对于java而言, ...
- 《Android 开发入门与实战(第二版)》——6.6节配置改变
本节书摘来自异步社区<Android 开发入门与实战(第二版)>一书中的第6章,第6.6节配置改变,作者eoe移动开发者社区 组编 , 姚尚朗 , 靳岩,更多章节内容可以访问云栖社区&qu ...
- 【前端开发】HTML入门与实战
[什么是HTML]: HTML: 超文本标记语言,标准通用标记语言下的一个应用. "超文本"就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元素. HTML 是用来描述网页的 ...
- js模板字符串自定义类名_【Vue.js 入门到实战教程】07Vue 组件注册 | 基本使用和组件嵌套...
来源 | https://xueyuanjun.com/post/21929除了前面介绍的基本语法之外,Vue.js 还支持通过组件构建复杂的功能模块,组件可以称得上是 Vue.js 的灵魂,是 Vu ...
- 《Angular4从入门到实战》学习笔记
<Angular4从入门到实战>学习笔记 腾讯课堂:米斯特吴 视频讲座 二〇一九年二月十三日星期三14时14分 What Is Angular?(简介) 前端最流行的主流JavaScrip ...
- NGUI从入门到实战1.2NGUI的主要特性
NGUI从入门到实战1.2NGUI的主要特性 本节会依次简单介绍NGUI的主要特性.这是一节概述性的介绍,具体的效果实现与操作方法会在本书后面的章节中一一介绍.本文选自NGUI从入门到实战(大学霸) ...
最新文章
- 新手探索NLP(三)
- 从30岁到35岁:为你的生命多积累一些厚度(转)
- 科大星云诗社动态20210222
- 数字三角形:顺推法(二维数组)
- git提交项目到已存在的远程分支
- C++链表插入节点函数为什么要传递头节点的二维指针
- IDEA包的分层显示
- C语言初学者学习资料分享
- 转录组和蛋白质组结合分析-入门笔记
- IDEA/Android Studio中替换(replace)快捷键
- 实战一:给定一段音频,请提取12维MFCC特征,阅读代码预加重、分帧、加窗部分,完善作业代码中fbank和mfcc部分,并给出最终的Fbank和MFCC特征,用默认的配置参数,无需进行修改
- 简单使用PHP 的 Silm框架.
- S@Kura的PHP进阶之路(二)
- “开宝五子棋陪练”软件的隐私政策
- 做业务的程序猿如何提升技能?
- 【源码】王者装逼工具/提升几倍的等级战力
- JS的严格模式和标准模式
- kali Linux命令大全
- 程序员做什么副业最轻松最赚钱?
- 203. 电子编程入门到工程师--逆推原理图(电源部分)