# go-demos **Repository Path**: youngniu/go-demos ## Basic Information - **Project Name**: go-demos - **Description**: 学习 Golang - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-10-22 - **Last Updated**: 2021-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: Go语言, demo ## README # GO 入门教程 [TOC] ## 资源 - 微软出品的Go教程 [开始使用 Go - Learn | Microsoft Docs](https://docs.microsoft.com/zh-cn/learn/paths/go-first-steps/) ## Go Proxy 设置代理以解决插件安装不上的问题,在终端中输入以下代码: ```bash go env -w GOPROXY=https://goproxy.cn,direct ``` ------ ## 01 Hello World * 第一行代码 *package main* 定义了包名。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。 * *import "fmt"* 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。 * *func main()* 是程序开始执行的函数。 * *fmt.Println(...)* 可以将字符串输出到控制台,并在最后自动增加换行字符 \n **执行 go 程序** 方式一 ```bash go run name.go ``` 方式二 ```bash go build name.go # 生成可执行文件 ``` ------ ## 02 基本语法 基本语法 * 单行注释: `//` * 多行注释:`/* 写注释 */` * 占位符:`%d` 数字,`%s` 字符串 * 一行默认为一条语句,如果需要将多行语句写在一行的话需要用 `;` 号分割 ------ ## 03 基本类型 标准变量初始化格式 ```go var name string = "codeniu" var gae int // 变量没有赋初始值时,会为其设置一个默认值,这个值叫做零值,int 类型的零值为 0 var isMarried bool // 零值为 false var damageRate float32 = 0.17 // float 类型 ``` 类型推导 ```go var hp = 1000 // hp 的类型被编译器推导为 int ``` 短变量声明并初始化 ```go hp := 1000 // 相当于 var hp int = 1000 ``` > Attention > > 短变量声明时,变量名必须是唯一的,但是在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,看下面例子 ```go var hp int = 1000 hp:=1001 // 报错 no new variables on left side of := ``` net.Dial 提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(err) ```go conn, err := net.Dial("tcp","127.0.0.1:8080") // 标准格式 var conn net.Conn var err error conn, err = net.Dial("tcp", "127.0.0.1:8080") ``` 在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,这样是不会报错的 ```go conn, err := net.Dial("tcp", "127.0.0.1:8080") conn2, err := net.Dial("tcp", "127.0.0.1:8080") ``` ------ ## 04 函数的参数传递 - 值传递 - 指针传递 ## 05 模块的使用 - 创建模块 - 引用模块 ## 06 控制流程 - if-else 条件判断的使用 - for 循环的使用 - switch 的使用 - fallthrough 关键字 - defer 延迟函数运行 - panic() 使程序中断 ## 07 特殊的数据结构 ### 1、数组 [练习 - 使用数组 - Learn | Microsoft Docs](https://docs.microsoft.com/zh-cn/learn/modules/go-data-types/1-arrays) - 初始化时需定义长度和类型,不可改变长度 - 会默认分配零值,不同类型的零值参考基本类型 定义数组: 1. 基本 ```go var a [3]int a[1] = 1 a[2] = 2 fmt.Println(a[0], a[1], a[len(a)-1]) // 0 1 2 ``` 2. 赋初始值 数组长度为3,但这里只给了两个初始值所以打印出的数据后面有个空格 ```go name := [3]string{"zhangsan", "lisi"} fmt.Println(name) //[zhangsan lisi ] ``` 3. 不指定长度,根据数据多少来确定数组长度 可以看到这里的打印的结果中没有空格了,数组的长度是2 ```go name2 := [...]string{"zhangsan", "lisi"} fmt.Println(name2, len(name2)) //[zhangsan lisi] 2 ``` 4. 指定位置赋值 定义一个没有指定长度的 string 类型的数组 name3,并在数组下标为 3 的地方赋值 codeniu,程序推断出数组长度为 4 ```go name3 := [...]string{3: "codeniu"} fmt.Println("first index:", name3[0]) fmt.Println("last index:", name3[len(name3)-1]) fmt.Println(name3, len(name3)) //first index: //last index: codeniu //[ codeniu] 4 ``` ### 2、切片 [练习 - 了解切片 - Learn | Microsoft Docs](https://docs.microsoft.com/zh-cn/learn/modules/go-data-types/2-slices) - 切片跟数组相同,但是与数组更重要的区别是切片的大小是*动态*的 - 可以使用相同的方式去声明切片和数组 切片的三要素 1. 基础数组 array 2. 切片的长度 len(s):切片中元素的个数 3. 切片的容量 cap(s):切片开头与基础数组结束之间的元素数目 切片的语法:s[i:p] 1. s 数组 2. i 起始指针 3. p 结束指针 切片可以理解为从一个数组上切下来一部分,数组 array := [12]int{1,2...12}, 切片 quarter1 := array[0:3], 从0 开始切直到下标为3时停止,quarter1 的元素为[1,2,3], 长度 len(quarter1) 是 3,cap(quarter1) 是 12。 容量为什么是 12 呢?原因是基础数组有更多元素或位置可供使用,但对切片而言不可见。可以这么理解,容量就是数组长度 - 切片的起始指针。 例如:quarter2 := array[3:6],长度为 3,容量为 9( len(array) - i ). 拓展切片: ```go package main import "fmt" func main() { months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} quarter2 := months[3:6] quarter2Extended := quarter2[:4] fmt.Println(quarter2, len(quarter2), cap(quarter2)) fmt.Println(quarter2Extended, len(quarter2Extended), cap(quarter2Extended)) } // 这个操作将切片 quarter2 的长度拓展为 4 ``` 删除项: Go 没有内置函数用于从切片中删除元素。设计一个函数用于删除切片中指定下标的元素。内置函数 `append(slice, element)`便于你向切片添加元素。 ```go package main import "fmt" func main() [] { letters := [...]int{1,2,3,4} remove = 2 if remove < len(letters) { fmt.Println("Before", letters, "Remove ", letters[remove]) letters = append(letters[:remove], letters[remove+1:]...) fmt.Println("After", letters) } } ``` 备份: 在切片的中更改元素会影响到原始数组 ```go package main import "fmt" func main() { a := [...]int{1, 2, 3, 4} var slice1 = a[0:2] var slice2 = a[1:4] slice1[1] = 100 fmt.Println(a) fmt.Println(slice1) fmt.Println(slice2) } ``` 输出: [1 100 3 4] [1 100] [100 3 4] 创建副本后再更改可以消除影响 make() 内置函数 `make([]type,length)` 用于生成一个空切片 copy() 内置函数 `copy(dst, src []Type)` 用于创建切片的副本,参数分别为目标切片和原切片 ```go package main import "fmt" func main() { a := [...]int{1, 2, 3, 4} var slice1 = a[0:2] slice2 := make([]int, 3) copy(slice2, a[1:4]) slice1[1] = 100 fmt.Println(a) fmt.Println(slice1) fmt.Println(slice2) } ``` 输出: [1 100 3 4] [1 100] [2 3 4] ### 3、映射 [练习 - 使用映射 - Learn | Microsoft Docs](https://docs.microsoft.com/zh-cn/learn/modules/go-data-types/3-maps) **如何理解 Go 中的映射?** 是键值对的集合,映射中所有的键的类型必须相同,他们值得类型也必须相同,但是键与值可以是不同得类型。 **声明和初始化:** `map` 关键字,`map[T]T` 第一个 `T` 表示键得类型,第二个 `T` 表示值得类型。 ```go package main import "fmt" func main() { studentsAge := map[string]int{ "john": 32, "bob": 31, } fmt.Println(studentsAge) } ``` **只声明不初始化:** ```go var studentsAge map[string]int // or studentsAge := make(map[string]int) ``` 需要注意得是,通过 (`var studentsAge map[string]int`) 这种方式创建得映射叫做 `nil` 映射,映射不能进行添加操作,否则会报错。 ```go package main import "fmt" func main() { var studentsAge map[string]int studentsAge["john"] = 32 studentsAge["bob"] = 31 fmt.Println(studentsAge) } ``` 输出: ``` panic: assignment to entry in nil map goroutine 1 [running]: main.main() /Users/johndoe/go/src/helloworld/main.go:7 +0x4f exit status 2 ``` 此规则仅适用于添加项的情况。 如果在 `nil` 映射中运行查找、删除或循环操作,Go 不会执行 panic。 **访问项:** Go 中映射得访问与 JS 中类似,访问映射中没有的项时 Go 不会返回错误。但有时需要知道某个项是否存在。 在 Go 中,***映射的下标表示法可生成两个值。 第一个是项的值。 第二个是指示键是否存在的布尔型标志。*** ```go package main import "fmt" func main() { studentsAge := make(map[string]int) studentsAge["john"] = 32 studentsAge["bob"] = 31 age, exist := studentsAge["christy"] if exist { fmt.Println("Christy's age is", age) } else { fmt.Println("Christy's age couldn't be found") } } ``` **删除项:** 使用内置函数 `delete()` ,如果你尝试删除不存在的项,Go 不会执行 panic。 ``` delete(studentsAge, "john") ``` **映射中的循环**: 使用rang 关键字,`range` 会首先生成项的键,然后再生成该项的值。 可使用 `_` 变量忽略其中任何一个 ```go package main import ( "fmt" ) func main() { studentsAge := make(map[string]int) studentsAge["john"] = 32 studentsAge["bob"] = 31 for name, age := range studentsAge { fmt.Printf("%s\t%d\n", name, age) } } ``` ### 4、结构 ### 5、挑战 1. 斐波纳契数列是一个数字列表,其中每个数字是前几个斐波那契数字之和。 例如,数字 `6` 的序列是 `1,1,2,3,5,8`,数字 `7` 的序列是 `1,1,2,3,5,8,13`,数字 `8` 的序列是 `1,1,2,3,5,8,13,21`,以此类推。 ## 08 日志记录 ## 09 方法和接口 方法是一种特殊的函数,与普通函数存在的区别是:必须再方法名称前加上一个额外的参数。此附加参数成为接收方。 ### 1、声明方法 声明方法的语法如下:首先你需要一个结构,这个结构必须存在方法的参数中。 ```go type typeName struct { // struct body } func (variable typeName) MethodName(parameters ...) { // method functionality } ``` 可以按如下方式调用,如果尝试按平常的方式调用 MethodName 函数,则此函数将无法正常工作,因为此函数的签名表明它需要接收方。 ``` func main() { t := typeName{ 1 } fmt.Println("Perimeter:", t.MethodName()) } ``` ### 2、声明其他类型的方法 不能通过属于其他包的类型来定义结构。 因此,不能在基本类型(如 `string`)上创建方法。 ```go package main import ( "fmt" "strings" ) func (s string) Upper() string { return strings.ToUpper(string(s)) } func main() { s := string("Learning Go!") fmt.Println(s) fmt.Println(s.Upper()) } ``` 程序报错: ``` .\main.go:9:6: cannot define new methods on non-local type string .\main.go:16:15: s.Upper undefined (type string has no field or method Upper) ``` 尽管如此,你仍然可以利用一点技巧,基于基本类型创建自定义类型,然后将其用作基本类型。例如,假设你要创建一个方法,以将字符串从小写字母转换为大写字母。 你可以按如下所示写入方法: ```go package main import ( "fmt" "strings" ) type upperstring string func (s upperstring) Upper() string { return strings.ToUpper(string(s)) } func main() { s := upperstring("Learning Go!") fmt.Println(s) fmt.Println(s.Upper()) } ``` ### 3、嵌入 ### 4、重载 ### 5、方法中的封装 在 Go 中,只需使用大写标识符,即可公开方法,使用非大写的标识符将方法设为私有方法。 ### 6、接口 ## 10 并发 [简介 - Learn | Microsoft Docs](https://docs.microsoft.com/zh-cn/learn/modules/go-concurrency/0-introduction)