有任何问题,大家可以一起交流心得。


原教程地址:微软Go入门。对于想了解Go的同学,很推荐一看。

安装

此处不赘述,官网走起(可能需要kx上网)。


语法:

变量

声明变量:

var(变量名 类型 [= 数值]) []为可选项
var 变量名 类型
var 变量名 = xxxx
变量名 := xxx

`注意,每个变量声明之后,必须有地方使用到,不然就无法run。


Go 是一种强类型语言。 这意味着你声明的每个变量都绑定到特定的数据类型,并且只接受与此类型匹配的值。(VsCode搭配官方的工具,每次ctrl+s之后,代码都会被格式化。同时,可以看到提示的各种语法问题,编译问题等等。)

Go 有四类数据类型:
基本类型:数字(int32 int64 int…)、字符串(string)和布尔值(bool)
聚合类型:数组和结构(struct)
引用类型:指针(& *)、切片(slice)、映射、函数(func)和通道(channel)
接口类型:接口(interface)

rune只是为了区分int和char,源码如下:

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

有时,你需要对字符进行转义。 为此,在 Go 中,请在字符之前使用反斜杠 ()。 例如,下面是使用转义字符的最常见示例:
\n:新行
\r:回车符
\t:选项卡
':单引号
":双引号
\:反斜杠

iota用于自增的常量使用,效果更佳,示例如下:

type ByteSize float64const (_           = iota // ignore first value by assigning to blank identifierKB ByteSize = 1 << (10 * iota)MBGBTBPBEBZBYB
)

挑战:可以尝试用iota赋值一周的七天。


函数(func):
语法:

func name(parameters) (results) {body-content
}
  • os.Args 变量包含传递给程序的每个命令行参数。
  • Go 是“按值传递”编程语言。 这意味着每次向函数传递值时,Go 都会使用该值并创建本地副本(内存中的新变量)。

在 Go 中,有两个运算符可用于处理指针:

  • & 运算符使用其后对象的地址。
    • 运算符取消引用指针。 也就是说,你可以前往指针中包含的地址访问其中的对象。

模块 & 库…

语法:

  • 初始化模块: go mod init 模块名 ;
  • 升级模块: go get 外部模块名@版本号(其中,“@版本号”可不填,默认为@latest,即为最新版本) ;
  • 列出当前模块的所有依赖: go list -m all
  • 列出模块可用版本: go list -m -versions 外部模块名
  • 多个版本存在,或者有的库已经用不上了: go mod tidy 可以去除相应的require行

举个栗子:
go mod init example.com/hello, 会获得一个go.mod文件:

module example.com/hellogo 1.15

当import 库之后,go.mod会增加require选项,比如:

import ("rsc.io/quote"quoteV1 "rsc.io/quote/v3"
)

查看go.mod:

module example.com/hellogo 1.15require (golang.org/x/text v0.3.5 // indirectrsc.io/quote v1.5.2rsc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1 // indirect
)

同时,会出现go.sum,这部分是为了确保项目所依赖的模块不会由于恶意,意外或其他原因而意外被更改。(个人理解为对文件做哈希值校验。)

关于modules的使用,参考官方博客,如何控制依赖之类的。
关于语义导入版本控制(Semantic Import Versioning),参考此处,可以了解到模块vx.x.x的历史等等。


defer/panic/recover

defer(可以推迟函数运行,类似于堆栈的后进先出。通常情况下,当你想要避免忘记任务(例如关闭文件或运行清理进程)时,可以推迟某个函数的运行。)

package mainimport ("io""os"
)func main() {f, err := os.Create("notes.txt")if err != nil {return}defer f.Close()if _, err = io.WriteString(f, "Learning Go!"); err != nil {return}f.Sync()
}

panic(内置 panic() 函数会停止正常的控制流。 所有推迟的函数调用都会正常运行。 进程会在堆栈中继续,直到所有函数都返回。 然后,程序会崩溃并记录日志消息。 此消息包含错误和堆栈跟踪,有助于诊断问题的根本原因。)

package mainimport "fmt"func main() {g(0)fmt.Println("Program finished successfully!")
}func g(i int) {if i > 3 {fmt.Println("Panicking!")panic("Panic in g() (major)")}defer fmt.Println("Defer in g()", i)fmt.Println("Printing in g()", i)g(i + 1)
}

recover(Go 提供内置函数 recover(),允许你在出现紧急状况之后重新获得控制权。 只能在已推迟的函数中使用此函数。 如果调用 recover() 函数,则在正常运行的情况下,它会返回 nil,没有任何其他作用。)

package mainimport "fmt"func main() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered in main", r)}}()g(0)fmt.Println("Program finished successfully!")
}func g(i int) {if i > 3 {fmt.Println("Panicking!")panic("Panic in g() (major)")}defer fmt.Println("Defer in g()", i)fmt.Println("Printing in g()", i)g(i + 1)
}

数组、切片、映射、结构

数组(定长)的例子:

q := [...]int{1, 2, 3} // 一维省略号可以帮助计算出数组的长度。
var twoD [3][5]int // 二维数组
var threeD [3][5][2]int // 二维数组
for i := 0; i < 3; i++ {for j := 0; j < 5; j++ {for k := 0; k < 2; k++ {threeD[i][j][k] = (i + 1) * (j + 1) * (k + 1)}}
}
fmt.Println("\nAll at once:", threeD)

切片只是名为基础数组的数组之上的一种数据结构。(数据底层还是一个数组。)通过切片,可访问整个基础数组,也可仅访问部分元素。切片只有 3 个组件:

  • 指针,指向基础数组可访问的第一个元素(并非一定是数组的第一个元素)。
  • 长度(len),指示切片中的元素数目。(实际可见的长度)
  • 容量(cap),显示切片开头与基础数组结束之间的元素数目。(实际可用长度)

例子:

package mainimport "fmt"func main() {months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}quarter1 := months[0:3]quarter2 := months[3:6]quarter3 := months[6:9]quarter4 := months[9:12]fmt.Println(quarter1, len(quarter1), cap(quarter1))fmt.Println(quarter2, len(quarter2), cap(quarter2))fmt.Println(quarter3, len(quarter3), cap(quarter3))fmt.Println(quarter4, len(quarter4), cap(quarter4))
}

Go 具有内置函数copy(dst, src []Type) 用于创建切片的副本。make([]Type, 3)用来创建切片。(个人理解是:用这种方式来填补深拷贝和浅拷贝的差异。)

package mainimport "fmt"func main() {letters := []string{"A", "B", "C", "D", "E"}fmt.Println("Before", letters)slice1 := letters[0:2]slice2 := letters[1:4]slice3 := make([]string, 3)copy(slice3, letters[1:4])slice1[1] = "Z"fmt.Println("After", letters)fmt.Println("Slice2", slice2)fmt.Println("Slice3", slice3)
}

映射(map):映射是动态的。 创建项后,可添加、访问或删除这些项。(无他,就是键值对。)

// 方式1(直接给数值)
studentsAge := map[string]int{"john": 32,"bob":  31,
}
// 方式2(make初始化)
studentsAge := make(map[string]int)
studentsAge["john"] = 32
studentsAge["bob"] = 31// 访问映射中没有的项时 Go 不会返回错误,这是正常的。 但有时需要知道某个项是否存在。
// 在 Go 中,映射的下标表示法可生成两个值。 第一个是项的值。 第二个是指示键是否存在的布尔型标志。
val, exist := studentsAge["christy"]
fmt.Println("Christy's age is", studentsAge["christy"])// 若要从映射中删除项,请使用内置函数 delete()。
delete(studentsAge, "john")// 可使用基于范围的循环
for name, age := range studentsAge {fmt.Printf("%s\t%d\n", name, age)
}// 如果对string进行循环时,每个都是rune类型,但是如果取下标的话,则是byte

结构,语法及例子如下:

type 结构名 struct {字段 类型 字段 类型
}
// (go是传值的语言,要修改还是得用指针类型。)
package mainimport "fmt"type Employee struct {ID        intFirstName stringLastName  stringAddress   string
}func main() {employee := Employee{LastName: "Doe", FirstName: "John"}fmt.Println(employee)employeeCopy := &employee // 不用指针改不了值employeeCopy.FirstName = "David"fmt.Println(employee)
}
// 结构嵌套
package mainimport "fmt"type Person struct {ID        intFirstName stringLastName  stringAddress   string
}type Employee struct {PersonManagerID int
}type Contractor struct {Person // 可以不给字段名,但是初始化的时候要给清楚该字段的值CompanyID int
}func main() {employee := Employee{ // 给清楚Person类型对应的属性Person: Person{FirstName: "John",},}employee.LastName = "Doe"fmt.Println(employee.FirstName)
}

处理Json:json.Marshal以及json.Unmarshal

package mainimport ("encoding/json""fmt"
)type Person struct {ID        intFirstName string `json:"name"`LastName  stringAddress   string `json:"address,omitempty"` // omitempty忽略空值,减少传输开销。
}type Employee struct {PersonManagerID int
}type Contractor struct {PersonCompanyID int
}func main() {employees := []Employee{Employee{Person: Person{LastName: "Doe", FirstName: "John",},},Employee{Person: Person{LastName: "Campbell", FirstName: "David",},},}data, _ := json.Marshal(employees) // 由于存在omitempty选项,json里没有address的内容,减少传输开销。fmt.Printf("%s\n", data)var decoded []Employeejson.Unmarshal(data, &decoded)fmt.Printf("%v", decoded)
}

错误&&日志

通用做法如下,把可能的错误返回出来:

employee, err := getInformation(1000)
if err != nil {// Something is wrong. Do something.
}
  • 始终检查是否存在错误,即使预期不存在。 然后正确处理它们,以免向最终用户公开不必要的信息。(比如以前写后台的时候,数据库错误之类的,在测试的时候,被打印出来,影响到用户体验。)
  • 在错误消息中包含一个前缀,以便了解错误的来源。 例如,可以包含包和函数的名称。在记录错误时记录尽可能多的详细信息,并打印出最终用户能够理解的错误。(提供更多debug的(给人看的)信息。)
  • 创建尽可能多的可重用错误变量。(重用更多的错误类型,比如404之类的错误。)
  • 了解使用返回错误和 panic 之间的差异。 不能执行其他操作时再使用 panic。 例如,如果某个依赖项未准备就绪,则程序运行无意义(除非你想要运行默认行为)。(尽量程序处理好各种错误,不要panic再来recover。)

记录日志(log库)、存入文件(os库):

package mainimport ("log""os"
)func main() {file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)if err != nil {log.Fatal(err)}defer file.Close()log.SetOutput(file)log.Print("Hey, I'm a log!")
}

Go 的几个记录框架有 Logrus、zerolog、zap 和 Apex。

以zerolog为例,安装:
go get -u github.com/rs/zerolog/log

package mainimport ("github.com/rs/zerolog""github.com/rs/zerolog/log"
)func main() {zerolog.TimeFieldFormat = zerolog.TimeFormatUnixlog.Print("Hey! I'm a log message!")
}

主要是为了管理日志级别比较方便,打印如下:{"level":"debug","time":1609855453,"message":"Hey! I'm a log message!"}


接口

以下创建图形接口,然后实现正方形的结构体,实现接口中的方法,就是等于实现了接口。那么,可以用图形结构体初始化一个正方形(因为已经实现了该接口,调用的是被实现的方法。)

type Shape interface {Perimeter() float64Area() float64
}type Square struct {size float64
}func (s Square) Area() float64 {return s.size * s.size
}func (s Square) Perimeter() float64 {return s.size * 4
}func main() {var s Shape = Square{3}fmt.Printf("%T\n", s)fmt.Println("Area: ", s.Area())fmt.Println("Perimeter:", s.Perimeter())
}

实现net/http 程序包中的 http.Handler 接口:

package httptype Handler interface {ServeHTTP(w ResponseWriter, r *Request)
}func ListenAndServe(address string, h Handler) error// 就可以把实现的具体结构作为参数传递给ListenAndServe()的第二个参数了。
type database map[string]dollarsfunc (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {for item, price := range db {fmt.Fprintf(w, "%s: %s\n", item, price)}
}func main() {db := database{"Go T-Shirt": 25, "Go Jacket": 55}log.Fatal(http.ListenAndServe("localhost:8000", db))
}

并发

需要注意:个人理解,主程序为主进程,如果在子线程执行函数的过程中,主进程的程序已经运行完了。此时,程序会直接退出,而不会等待子线程完成指令。

func main(){login()go func() {launch()}()
}

关于Channel,官方博客值得一看:Share Memory By Communicating。意思是通过通信来共享内存。
无缓冲channel(个人理解为同步的意思,有线程放进变量,必得有线程取出变量,不然就会出现Dead Lock的报错提醒。)

package mainimport ("fmt""net/http""time"
)func main() {start := time.Now()apis := []string{"https://management.azure.com","https://dev.azure.com","https://api.github.com","https://outlook.office.com/","https://api.somewhereintheinternet.com/","https://graph.microsoft.com",}ch := make(chan string)for _, api := range apis {go checkAPI(api, ch)}for i := 0; i < len(apis); i++ {fmt.Print(<-ch)}elapsed := time.Since(start)fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}func checkAPI(api string, ch chan string) {_, err := http.Get(api)if err != nil {ch <- fmt.Sprintf("ERROR: %s is down!\n", api)return}ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}

有缓冲channel

func send(ch chan string, message string) {ch <- message
}func main() {size := 2ch := make(chan string, size)send(ch, "one")send(ch, "two")go send(ch, "three")go send(ch, "four")fmt.Println("All data sent to the channel ...")for i := 0; i < 4; i++ {fmt.Println(<-ch)}fmt.Println("Done!")
}

select关键字是switch的channel版本。
如何在使用 select 关键字的同时与多个 channel 交互的简短主题。 有时,在使用多个 channel 时,需要等待事件发生。

package mainimport ("fmt""time"
)func process(ch chan string) {time.Sleep(3 * time.Second)ch <- "Done processing!"
}func replicate(ch chan string) {time.Sleep(1 * time.Second)ch <- "Done replicating!"
}func main() {ch1 := make(chan string)ch2 := make(chan string)go process(ch1)go replicate(ch2)for i := 0; i < 2; i++ {select {case process := <-ch1:fmt.Println(process)case replicate := <-ch2:fmt.Println(replicate)}}
}

测试

  • 如何在 Go 中进行测试。(通过使用测试驱动开发)。

编写代码时要遵循的一个良好做法是使用测试驱动开发 (TDD) 方法。 使用此方法时,我们将首先编写测试。 我们可以肯定那些测试会失败,因为它们测试的代码还不存在。 然后,我们将编写满足测试条件的代码。

创建测试文件时,该文件的名称必须以 _test.go 结尾。 要编写的每个测试都必须是以 Test 开头的函数。 然后,你通常为你编写的测试编写一个描述性名称,例如 TestDeposit。

举例:

package bankimport "testing"func TestAccount(t *testing.T) {}

输出如下:

=== RUN   TestAccount
--- PASS: TestAccount (0.00s)
PASS
ok      github.com/msft/bank    0.391s

测试命令:
go test -v


(To be continue) 后续熟练Go,上手CRUD,尝试做个Web项目。
推荐:
effective go:如何在Go领域下,写出自己的高效。
go web wiki:写基础Web应用。

Go入门(微软教程笔记)相关推荐

  1. MybatisMybatisPlusSpringboot之最全入门学习教程笔记

    目录 1 Mybatis概述 1.1 Mybatis概念 1.1.1 JDBC 缺点 1.1.2 Mybatis优化 1.2 快速入门 1.2.1 创建数据库 1.2.2 IDEA2021创建项目 1 ...

  2. python 入门学习教程笔记-- BMR 计算器

    本讲内容涉及到的知识点有: 1.数值类型: 2.字符串分割,字符串格式化输出,使用{}占位 https://docs.python.org/3/library/stdtypes.html#str.sp ...

  3. SimFAS(深克斯)中控编程入门培训教程笔记

    近日学习了 SimFAS中控系统的编程培训,总体来说很简单,现在把PPT简单记录一下: 总体概况:完成整个完完成整个项目实施, 简单:涉及到编程的地方,都是 输入中文关键字,然后出来参数让您填,代码就 ...

  4. Scons入门指南 使用教程 笔记

    Scons入门指南 使用教程 笔记 说明:本文为作者阅读scons官方手册之后编写的笔记,更详细内容可以自行查看官方文档 1. 介绍 Scons是一个开放源码.以Python语言编码的自动化构建工具, ...

  5. 尚硅谷大数据技术Zookeeper教程-笔记01【Zookeeper(入门、本地安装、集群操作)】

    视频地址:[尚硅谷]大数据技术之Zookeeper 3.5.7版本教程_哔哩哔哩_bilibili 尚硅谷大数据技术Zookeeper教程-笔记01[Zookeeper(入门.本地安装.集群操作)] ...

  6. Java入门教程笔记(一)

    Java入门教程笔记(一) Java入门教程笔记系列仅适用于有过一定编程基础的人学习java时进行参考和借鉴 不适用于作为入门级教程 以下凡"小黑窗"都是表示"DOS命令 ...

  7. 尚硅谷大数据技术Spark教程-笔记09【SparkStreaming(概念、入门、DStream入门、案例实操、总结)】

    尚硅谷大数据技术-教程-学习路线-笔记汇总表[课程资料下载] 视频地址:尚硅谷大数据Spark教程从入门到精通_哔哩哔哩_bilibili 尚硅谷大数据技术Spark教程-笔记01[SparkCore ...

  8. Java入门教程笔记(三)

    Java入门教程笔记(一) Java入门教程笔记(二) 对前两份笔记的补充: static关键词: 被static关键词修饰的变量或者方法可以不依赖于某个特定的对象存在 被static关键词修饰的方法 ...

  9. Vue 新手学习笔记:vue-element-admin 之入门开发教程(v4.0.0 之前)

    说实话都是逼出来的,对于前端没干过ES6都不会的人,vue视频也就看了基础的一些 但没办法,接下来做微服务架构,前端就用 vue,这块你负责....说多了都是泪,脚手架框架布了都没看过 干就完事,不过 ...

最新文章

  1. expressjs路由匹配规则
  2. 基于Pytorch再次解析使用块的现代卷积神经网络(VGG)
  3. 【Visual Studio 2019】创建 导入 CMake 项目
  4. Java 技术篇-mac操作系统JRE、JDK环境的配置演示
  5. 安装mysql.dox_linux虚拟机上装mysql数据库-Go语言中文社区
  6. 【Linux】一步一步学Linux——telinit命令(144)
  7. 《大数据》2015年第3期“研究”——社交网络影响力传播研究(下)
  8. rp2836 网卡以及串口与接插件位置关系
  9. vue-router传参的坑(query和params)
  10. 循环神经网络 递归神经网络_递归神经网络-第5部分
  11. 地震数据.dat文件转.sgy文件
  12. ppt将画好的箭头改为直线
  13. MediaWiki安装插件Semantic MediaWIKI + PageForms
  14. 计算机二级WPS Office考试大纲2021年
  15. [codevs1746][NOI2002]贪吃的九头龙
  16. .net ImageProcessor组件转换图片格式
  17. sas 统计学 anova
  18. [1-6] 把时间当做朋友(李笑来)Chapter 6 【更多思考】 摘录
  19. PHP针对数字的加密解密类,可直接使用
  20. 三生诀游戏服务器维护,轮回三生诀变态-轮回三生诀游戏满V变态版 v1.0预约_手机乐园...

热门文章

  1. 西门子PLC动态加密催款程序
  2. Android开发之使用URL访问网络资源
  3. python index out of bounds_错误:索引2超出大小为1的轴0的界限
  4. 代编股票选股公式、代编公式选股、代编期货量化交易公式、代编公式选股
  5. Spring - RabbitMQ循环依赖问题解决
  6. 书法 | 从零学硬笔,我的三天成长路 2
  7. 东京大学计算机专业好吗,给想报东大计算机专业的同学的几点建议
  8. 北理在线作业答案c语言,18秋北理工《计算机组成原理》在线作业答案
  9. html怎么把字做成动画效果,如何使用HTML5 css3实现粒子效果文字动画特效(附完整代码)...
  10. 目标检测YOLO实战应用案例100讲-基于深度学习的红外目标检测研究与应用