从0开始学Go(一)

    技术2025-07-15  10

    文章目录

    这是我的Golang学习笔记1. 运行 go 程序:go run xxx.go2. 标识符的私有化3. 函数3.1 自定义函数类型3. 2 go支持多返回值函数3.2.1 下面是基础版本示例3.2.2 进阶版3.2.3 返回值命名 3.3 可变参数3.4 defer语句3.5 匿名函数3.6 内置函数3.7 递归函数 4. 不能将源文件命名为 xxx_test.go5. Windows不支持`go run *.go`的写法6. go 不支持任何函数之外存在执行语句7. 包别名语法8. init函数9. 关于包的导入9.1 普通的导入包的语句:9.2 只想使用某个包中的初始化动作9.3 直接使用某个包中的变量/函数 10. 常量11. 每个包都要独占一个目录12. 数据类型13. 格式化输出控制符14. strings的基本使用14.1 strings.HasPrefix(s string, prefix string) bool14.2 strings.HasSuffix(s string, suffix string) bool14.3 strings.Index(s string, str string) int14.4 strings.LastIndex(s string, str string) int14.5 strings.Replace(str string, old string, new string, n int)14.6 strings.Count(str string, substr string) int14.7 strings.Repeat(str string, count int) string14.8 strings.ToLower(str string) string14.9 strings.ToUpper(str string) string14.10 ...14.11 Go遍历含中文的字符串并输出 15. strconv 包的基本使用16. 时间和日期类型16.1 func (t Time) Format(layout string) string 17. 流程控制17.1 if/else17.2 普通switch case17.3 条件switch case17.4 for17.5 for range17.6 goto/continue/break 和 label 18. 数组和切片18.1 数组18.2 编译器自动确定数组大小18.3 将数组指定位置的元素初始化成指定的值(其余位置初始化为0)18.4 数组的复制和传递18.5 可以通过指针直接访问数组元素18.6 切片18.7 切片的追加18.8 通过数组切片创建切片18.9 使用make创建切片18.10 用代码证明,切片在参数传递过程中是一种浅拷贝的值传递18.11 数组和切片的展开(解包) 19. 错误19.1 errors包的基本使用 20. 使用 goroute 实现并发21. 闭包22. 关于排序22.1 sort包对切片进行排序22.2 sort包对数组进行排序

    这是我的Golang学习笔记

    所有项目均为Windows + VSCode + go环境下创建

    这并不是一篇从完全意义上的小白开始学习的博文,看这篇博文之前还是需要一丁点的go的知识点的,比如第一个Golang程序“HelloGo”怎么写等等,甚至包括环境搭建啥的,这种我就不写了,毕竟随便一搜索就是大把的资料。


    1. 运行 go 程序:go run xxx.go

    // Hello Go package main import ( "fmt" ) func main() { fmt.Printf("Hello Go") }

    2. 标识符的私有化

    如果想要在外部调用某个包内部的函数/变量,需要命名时首字母大写

    3. 函数

    func (FormalParameterList) ReturnValueList { FuncBody } func (a int, b int) int { return a + b }

    3.1 自定义函数类型

    type typeName func(FormalParameterList) ReturnValueList

    package main import "fmt" //type opFunc func(int, int) int func add(a, b int) int { return a + b } //func operator(op opFunc, a, b int) int { func operator(op func(int, int) int, a, b int) int { return op(a, b) } func main() { c := add sum := operator(c, 100, 200) fmt.Println(sum) } /* output: API server listening at: 127.0.0.1:14310 300 Process exiting with code: 0 */

    3. 2 go支持多返回值函数

    返回值列表必须使用“()”括起来

    3.2.1 下面是基础版本示例

    // base example: // calc returns the sum and average of two numbers func calc(a int, b int)(int, int) { sum := a + b avg := (a + b) / 2 return sum, avg } // usage: sum, avg = calc(10, 20) // 如果有部分返回值不使用,可以使用“_”占位 _, avg = calc(10, 20)

    3.2.2 进阶版

    在定义函数时,直接命名返回值,这样可以在返回时,直接只写一个return

    package main import ( "fmt" ) // getPerimeterArea returns the circumference and area of a rectangle func getPerimeterArea(width int, height int) (perimeter int, area int) { perimeter = width*2 + height*2 area = width * height return } func main() { //var perimeter, area = getPerimeterArea(100, 50) perimeter, area := getPerimeterArea(100, 50) fmt.Println("The rectangle's perimeter is: ", perimeter, ", area is: ", area) } /* output: API server listening at: 127.0.0.1:8161 The rectangle's perimeter is: 300 , area is: 5000 Process exiting with code: 0 */

    3.2.3 返回值命名

    单返回值参数也可以命名,一旦命名,不论是单返回值还是多返回值,都必须使用“()”括起来

    3.3 可变参数

    func FuncName1(arg...int) int { // 0个或多个参数 } func FuncName2(a int, arg...int) int { // 1个或多个参数 } func FuncName3(a int, b int, arg...int) int { // 2个或多个参数 } package main import ( "fmt" ) func add(a int, arg ...int) int { var sum int = a for i := 0; i < len(arg); i++ { sum += arg[i] } return sum } func addString(a string, arg ...string) (result string) { result = a for i := 0; i < len(arg); i++ { result += arg[i] } return } func main() { sum := add(10) fmt.Println(sum) result := addString("Hello", " ", "Go", "!") fmt.Println(result) } /* output: API server listening at: 127.0.0.1:42029 10 Hello Go! Process exiting with code: 0 */

    3.4 defer语句

    当函数返回时,自动执行defer语句,因此可以用来清理资源多个defer语句,按先进后出的方式执行defer中的语句,在defer声明时就已经决定了 package main import ( "fmt" ) func main() { var i int = 0 defer fmt.Println("i = ", i) i++ for j := 0; j < 3; j++ { defer fmt.Println("j = ", j) } } /* output: API server listening at: 127.0.0.1:2584 j = 2 j = 1 j = 0 i = 0 Process exiting with code: 0 */

    3.5 匿名函数

    package main import ( "fmt" ) func test(a, b int) int { result := func(a1, b1 int) int { return a1 + b1 }(a, b) //此处使用小括号说明在定义这个匿名函数的同时调用了它 return result } func main() { fmt.Println(test(100, 300)) } /* output: API server listening at: 127.0.0.1:32747 400 Process exiting with code: 0 */

    3.6 内置函数

    不需要导入任何包也不需要定义就可以直接使用的函数

    函数名功能close主要用来关闭channellen用来求长度,比如string、arrav、slice、map、channelnew用来分配内存,主要用来分配值类型,比如int、structmake用来分配内存,主要用来分配引用类型,比如chan、map、sliceappend用来追加元素到数组、slice中copy拷贝panic和recover用来做错误处理 //new package main import ( "fmt" ) func main() { a := new(int) *a = 100 fmt.Println(a) fmt.Println(*a) } /* output: API server listening at: 127.0.0.1:47147 0xc0000120b8 100 Process exiting with code: 0 */ //make package main import ( "fmt" ) func main() { pipe := make(chan int, 3) fmt.Println(len(pipe)) pipe <- 1 pipe <- 2 pipe <- 3 fmt.Println(len(pipe)) close(pipe) } /* output: API server listening at: 127.0.0.1:8259 0 3 Process exiting with code: 0 */

    new 和 make 的区别:new返回一个指针,而make返回的是一个类型变量,没有指针,并且在使用make时必须指明长度。

    package main import ( "fmt" ) func main() { s1 := new([]int) fmt.Println(s1) s2 := make([]int, 10) fmt.Println(s2) (*s1)[0] = 100 s2[0] = 100 } /* output: PS E:\Code\GoCode\TestProject> go build .\main.go PS E:\Code\GoCode\TestProject> .\main.exe &[] [0 0 0 0 0 0 0 0 0 0] panic: runtime error: index out of range [0] with length 0 goroutine 1 [running]: main.main() E:/Code/GoCode/TestProject/main.go:12 +0x168 */ //append package main import ( "fmt" ) func main() { var a []int a = append(a, 10, 20, 30) a = append(a, a...) fmt.Println(a) } /* output: API server listening at: 127.0.0.1:20616 [10 20 30 10 20 30] Process exiting with code: 0 */

    copy的使用规则: copy(dest, src) dest 和 src 必须同类型 如果 len(dest) > len(src),不足的部分不会改变,如果 len(dest) < len(src),dest 不会扩容,只会将src前面对应的部分拷贝到dest中。

    //copy package main import ( "fmt" ) func main() { var arr = []int{0, 1, 2, 3, 4} slice := make([]int, 10) copy(slice, arr) fmt.Println(arr) fmt.Println(slice) } /* output: API server listening at: 127.0.0.1:10400 [0 1 2 3 4] [0 1 2 3 4 0 0 0 0 0] Process exiting with code: 0 */ //panic package main import "errors" func initConfig() (err error) { return errors.New("init config failed") } func test() { err := initConfig() if err != nil { panic(err) } } func main() { test() } /* output: PS E:\Code\GoCode\TestProject> go build .\main.go PS E:\Code\GoCode\TestProject> .\main.exe panic: init config failed goroutine 1 [running]: main.test(...) E:/Code/GoCode/TestProject/main.go:12 main.main() E:/Code/GoCode/TestProject/main.go:17 +0x62 */ //panic //panic异常可以被捕获并且处理,从而避免程序终结 package main import ( "fmt" "time" ) func test() { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() b := 0 a := 100 / b fmt.Println(a) } func main() { for { time.Sleep(time.Second) test() } } /* output: PS E:\Code\GoCode\TestProject> go build .\main.go PS E:\Code\GoCode\TestProject> .\main.exe runtime error: integer divide by zero runtime error: integer divide by zero runtime error: integer divide by zero runtime error: integer divide by zero PS E:\Code\GoCode\TestProject> */

    3.7 递归函数

    这个跟其它语言一致,此处不赘述。

    4. 不能将源文件命名为 xxx_test.go

    xxx_test.go 是测试文件,启动指令为 go test xxx_test.go,使用 go run/build 指令时,xxx_test.go文件的名称是非法的。

    5. Windows不支持go run *.go的写法

    网上教的go语言多文件的main package的运行方法:cd packageDir && go run *.go 或者直接 go run xxx/*.go 的写法,经过实测,此写法在Windows上不支持,会报错:

    GetFileAttributesEx *.go: The filename, directory name, or volume label syntax is incorrect.

    6. go 不支持任何函数之外存在执行语句

    “:=” 这种写法相当于先定义(未初始化),然后再赋值,因此也是不允许的。

    package main import ( "fmt" ) var num0 int = 0 num1 := 1 func main() { fmt.Println(num0) fmt.Println(num1) } /* 直接报错 # TestProject .\main.go:8:1: syntax error: non-declaration statement outside function body exit status 2 Process exiting with code: 1 */

    7. 包别名语法

    /* 语法为: import( alaisName "packageName" ) */ //Example: package main import ( format "fmt" ) func main() { format.Println("This is a test for taking an alias for a package when importing it") } /* output: API server listening at: 127.0.0.1:19950 This is a test for taking an alias for a package when importing it Process exiting with code: 0 */

    8. init函数

    每个源文件都可以有一个init函数,它会被go的运行框架自动调用(在main函数之前调用)。

    package main import ( "fmt" ) func init() { fmt.Println("This is init") } func main() {main fmt.Println("This is main") } /* output: API server listening at: 127.0.0.1:14768 This is init This is main Process exiting with code: 0 */

    9. 关于包的导入

    9.1 普通的导入包的语句:

    import( "package1" "package2" "..." )

    9.2 只想使用某个包中的初始化动作

    如果只想使用某个包中的初始化动作(init 函数),而不使用其它任何变量和函数,可以使用给包取别名的语法,用“_”关键字作为其别名。

    /* import( _ "packageName" ) */ 下面是一个示例,目录结构为: TestProject ├──go.mod ├──another_pkg | └──another_pkg.go └──main └──main.go

    main.go:

    package main import ( _ "TestProject/another_pkg" "fmt" ) func main() { fmt.Println("This is main") }

    another_pkg.go:

    package another_pkg import ( "fmt" ) func init() { fmt.Println("This is init of another_pkg") }

    go.mod:

    module TestProject go 1.14

    9.3 直接使用某个包中的变量/函数

    使用下列语句导入某个包时,使用该包中的变量或者函数时,可以不带包名,直接使用。

    import( . "packageName" )

    Example:

    package main import ( . "fmt" ) func main() { Println("This is package name import test...") } /* output: API server listening at: 127.0.0.1:7663 This is package name import test... Process exiting with code: 0 */

    10. 常量

    常量使用const修饰,只能修饰boolean,number(int相关类型,浮点类型,complex)和string 语法:const identifier [type] =value,其中type可以省略

    const str string = "Hello Go" const str = "Hello Go" const pi = 3.141592654 const a = 9/3 const c = getValue() //非法的 //比较优雅的写法: const( a = 0 b = 1 c = 2 ) //更加专业的写法 //这种写法会自动将a初始化成0,后边的依次初始化为1/2 const( a = iota b // 1 c // 2 )

    11. 每个包都要独占一个目录

    不允许一个目录下存在多个包的go源码文件。

    12. 数据类型

    数字类型:int、int8、int16、int32、int64、uint8、uint16、uint32、uint64、float32、float64 类型转换:type(variable),example:var a int = 8; var b int32 = int32(a) 字符类型:var a byte, example:var b byte = ‘C’字符串类型:var str string, example:var str = “Hello World” 注:使用反单引号创建的字符串是原字符串,不需要转义字符,甚至还支持换行。

    13. 格式化输出控制符

    控制符含义%vthe value in a default formatwhen printing structs, the plus flag (%+v) adds field names%#va Go-syntax representation of the value%Ta Go-syntax representation of the type of the value%%a literal percent sign; consumes no value

    For more information, please refer to: go文档fmt包信息

    14. strings的基本使用

    14.1 strings.HasPrefix(s string, prefix string) bool

    判断s是否以prefix开头

    //Example: Determine whether a url starts with "http://", if not, add it. package main import ( "fmt" "strings" ) func main() { var url = "www.baidu.com" if !strings.HasPrefix(url, "https://") { url = "https://" + url } fmt.Println(url) } /* output: PS E:\Code\StudyCode\GoCode\src\TestProject> go build main/main.go PS E:\Code\StudyCode\GoCode\src\TestProject> .\main.exe https://www.baidu.com */

    14.2 strings.HasSuffix(s string, suffix string) bool

    判断字符串s是否以suffix结尾

    //Example: Determine whether a path ends with "/", if not, add it package main import ( "fmt" "strings" ) func main() { var path = "/usr/bin" if !strings.HasSuffix(path, "/") { path = path + "/" } fmt.Println(path) } /* output: PS E:\Code\StudyCode\GoCode\src\TestProject> go build main/main.go PS E:\Code\StudyCode\GoCode\src\TestProject> .\main.exe /usr/bin/ */

    14.3 strings.Index(s string, str string) int

    判断str在s中首次出现的位置,如果没有,则返回-1

    14.4 strings.LastIndex(s string, str string) int

    判断str在s中最后出现的位置,如果没有,则返回-1

    14.5 strings.Replace(str string, old string, new string, n int)

    字符串替换

    14.6 strings.Count(str string, substr string) int

    字符串基数

    14.7 strings.Repeat(str string, count int) string

    重复count次str

    14.8 strings.ToLower(str string) string

    转为小写

    14.9 strings.ToUpper(str string) string

    转为大写

    14.10 …

    For more information, please access to the official website: go strings package

    14.11 Go遍历含中文的字符串并输出

    package main import ( "fmt" ) func sample(_str string) { fmt.Printf("string print:\n") for _, j := range _str { fmt.Printf("%c", j) } fmt.Println() fmt.Printf("rune print:\n") str := []rune(_str) for _, j := range str { fmt.Printf("%c", j) } //compare Chinese character //str := []rune(_str) //str[1] == str[2] //also } func main() { sample("test string:看我神威,无坚不摧") } /* output: API server listening at: 127.0.0.1:4136 string print: test string:看我神威,无坚不摧 rune print: test string:看我神威,无坚不摧 Process exiting with code: 0 */

    15. strconv 包的基本使用

    关于字符串转换的包,详情请查询官方文档

    16. 时间和日期类型

    time 包timeTime 类型,用来表示时间获取当前时间 now := time.Now()time.Now().Day()、time.Now().Minute()、time.Now().Month()、time.Now().Year()格式化,fmt.Printf("%02d%02d%02d %02d:%02d:%02d\n", now.Year()…)time.Duration 用来表示纳秒一些常量:const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute )

    16.1 func (t Time) Format(layout string) string

    layout必须使用go诞生的时间的字符串,否则输出的字符串不符合预期。

    package main import ( "fmt" "time" ) func main() { now := time.Now() fmt.Println(now.Format("02/01/2006 15:04")) fmt.Println(now.Format("2006/1/02 15:04")) fmt.Println(now.Format("2006/1/02")) fmt.Println(now.Format("05/07/2020 16:22")) } /* output: API server listening at: 127.0.0.1:38257 05/07/2020 16:23 2020/7/05 16:23 2020/7/05 14/07/5050 76:55 Process exiting with code: 0 */

    17. 流程控制

    17.1 if/else

    package main import ( "fmt" ) func main() { if 0 > 1 { fmt.Println("0 > 1? fatal error...") } else if 0 < 1 { fmt.Println("yes, '0 < 1', that's right") } } /* output: API server listening at: 127.0.0.1:21498 yes, '0 < 1', that's right Process exiting with code: 0 */

    17.2 普通switch case

    和其它语言中不同,go中的switch不需要break也不会往下走,如果想要继续往下走,需要使用关键字 fallthrough,同时匹配多个结果可以使用“,”隔开。

    package main import ( "fmt" ) func main() { var a int = 10 switch a { case 0: fmt.Println("a is equal 0") case 10, 20: fmt.Println("a is equal 10 or 20") fallthrough default: fmt.Println("a is equal default") } } /* output: API server listening at: 127.0.0.1:41983 a is equal 10 or 20 a is equal default Process exiting with code: 0 */

    17.3 条件switch case

    go中还支持直接使用条件来进行case,另外switch后还可以跟语句,但是语句必须以“;”结尾

    package main import ( "fmt" ) func main() { var a int = 10 switch a = 12; { case a < 0: fmt.Println("a < 0") case a > 0 && a < 13: fmt.Println("a is: ", a, ", a > 0 && a < 11") default: fmt.Println("This is default") } } /* output: API server listening at: 127.0.0.1:7550 a is: 12 , a > 0 && a < 11 Process exiting with code: 0 */

    17.4 for

    for 初始化语句; 条件判断; 变量修改 { content }

    17.5 for range

    这个操作用于遍历数组、slice、map、chan等,语法:

    for index, val := range variable { } package main import "fmt" func main() { str := "Hello, 中国" for index, val := range str { fmt.Printf("index[%d] val[%c] len[%d]\n", index, val, len([]byte(string(v)))) } } /* output: API server listening at: 127.0.0.1:8097 index[0] val[H] len[1] index[1] val[e] len[1] index[2] val[l] len[1] index[3] val[l] len[1] index[4] val[o] len[1] index[5] val[,] len[1] index[6] val[ ] len[1] index[7] val[中] len[3] index[10] val[国] len[3] Process exiting with code: 0 */

    17.6 goto/continue/break 和 label

    这个和C语言的一样,就不赘述了,不同的是go语言支持continue label和break label,这两个用法和C语言中的continue和break一样,甚至在go语言中加不加label没有任何区别,所以黑人问号?我是真的搞不懂为什么会有continue label和continue label的用法

    18. 数组和切片

    18.1 数组

    同一种数据类型的固定长度的序列,一旦定义,长度不可变定义语法:var a [len] int,例如:var a[5]int, 默认初始化成0定义的同时初始化:package main import ( "fmt" ) func main() { var a [5]int = [5]int{1} fmt.Println(a) } /* //output: API server listening at: 127.0.0.1:35339 [1 0 0 0 0] Process exiting with code: 0 解读: 使用花括号中的值从左到右依次进行初始化,不够的,初始化成0 如果花括号中的值数量比定义的数组容量大,则会报错,编译失败。 */ 长度是数组类型的一部分,因此 var a[5]int 和 var a[10]int 是不同的类型可以通过下标访问数组中的元素,如果下标在数组合法范围之外,则会触发panic两种遍历方法://Method1 for i := 0; i < len(arrayName); i++ { } //Method2 for index, value := range arrayName { }

    18.2 编译器自动确定数组大小

    var array = [...]int {1, 2, 3, 4, 5} // 这种方式定义的array的大小为5,由编译器根据花括号中的元素数量确定数组的大小

    18.3 将数组指定位置的元素初始化成指定的值(其余位置初始化为0)

    package main import ( "fmt" ) func main() { var nums = [5]int{3: 3, 4: 4} var strs = [5]string{2: "神", 3: "威"} var nums2 = [...]int{3: 3, 4: 4} //此时编译器根据指定的元素的最大编号确定数组的大小 fmt.Println(nums) fmt.Println(strs) fmt.Println(nums2) } /* output: API server listening at: 127.0.0.1:2730 [0 0 0 3 4] [ 神 威 ] [0 0 0 3 4] Process exiting with code: 0 */

    18.4 数组的复制和传递

    package main import ( "fmt" ) func test01() { var a [3]int a[0] = 100 fmt.Println(a) for i := 0; i < len(a); i++ { fmt.Printf("%d\t", a[i]) } fmt.Println() for index, value := range a { fmt.Printf("a[%d] = %d\n", index, value) } } func test03(arr [3]int) { arr[0] = 1000 } func test02() { var a [3]int b := a b[0] = 100 fmt.Println(a) } func main() { fmt.Println("-----test01-----") test01() fmt.Println("-----test02-----") test02() fmt.Println("-----test03-----") var a [3]int test03(a) fmt.Println(a) } /* output: API server listening at: 127.0.0.1:12158 -----test01----- [100 0 0] 100 0 0 a[0] = 100 a[1] = 0 a[2] = 0 -----test02----- [0 0 0] -----test03----- [0 0 0] Process exiting with code: 0 ******************解读****************** test01演示了如何访问、修改和遍历一个数组 test02说明了数组在go中属于值类型的变量,赋值操作相当于复制了一遍数组 注意:这点和C/C++中不一样,C/C++中,数组名在绝大多数时候都扮演着指针的角色 在Python中,没有数组,与之类似的是列表,对列表赋值只是得到一个引用,新的列表跟原列表是同一个。 test03说明了在参数传递过程中,列表是值传递(复制),而不是引用 */

    18.5 可以通过指针直接访问数组元素

    package main import ( "fmt" ) func main() { var ptr *[5]int = &([5]int{0, 1, 2, 3, 4}) var arr [5]int = [5]int{5, 6, 7, 8, 9} fmt.Println(ptr[1]) fmt.Printf("%p\n", ptr) fmt.Println((&arr)[2]) } /* output: API server listening at: 127.0.0.1:38237 1 0xc0000c8030 7 Process exiting with code: 0 */

    18.6 切片

    切片是一个数组的引用,因此切片是引用类型。切片的长度可变,因此,切片是一个可变的数组切片的遍历方式和数组一样,可以用len()求长度cap可以求出slice的最大容量, 0 <= len(slice) <= cap(slice),器中array是slice引用的数组切片的定义:var 变量名 []类型,比如 var str [] string、var arr [] int可以对数组进行切片操作,然后返回一个切片package main import ( "fmt" ) func main() { var slice []int var arr = [5]int{0, 1, 2, 3, 4} slice = arr[2:3] fmt.Println(slice) fmt.Println("len(slice) = ", len(slice)) fmt.Println("cap(slice) = ", cap(slice)) } /* output API server listening at: 127.0.0.1:15272 [2] len(slice) = 1 cap(slice) = 3 Process exiting with code: 0 */

    18.7 切片的追加

    数组一旦定义,长度不可变,但是切片可以使用相应函数改变长度,例如追加。

    package main import ( "fmt" ) func main() { var a []int a = append(a, 10, 20, 30) a = append(a, a...) // sliceName...的写法可以将切片展开 fmt.Println(a) } /* output: API server listening at: 127.0.0.1:20616 [10 20 30 10 20 30] Process exiting with code: 0 */

    18.8 通过数组切片创建切片

    package main import ( "fmt" ) func main() { var slice []int var arr = [...]int{0, 1, 2, 3, 4} slice = arr[:] fmt.Printf("&arr[0] = %p\n", &(arr[0])) fmt.Printf("&slice[0] = %p\n", &(slice[0])) slice[0] = 100 fmt.Println(arr) fmt.Println(slice) fmt.Println("---------------------------------") slice = append(slice, 100) fmt.Println(arr) fmt.Println(slice) fmt.Printf("&arr[0] = %p\n", &(arr[0])) fmt.Printf("&slice[0] = %p\n", &(slice[0])) } /* output: API server listening at: 127.0.0.1:23238 &arr[0] = 0xc00000a390 &slice[0] = 0xc00000a390 [100 1 2 3 4] [100 1 2 3 4] --------------------------------- [100 1 2 3 4] [100 1 2 3 4 100] &arr[0] = 0xc00000a390 &slice[0] = 0xc00000c1e0 Process exiting with code: 0 *******************解读******************* 试验结果证明,使用数组切片操作来创建切片时,生成的切片底层使用的数组就是创建切片的数组 除非新生成的切片底层的数组更换了,否则对新切片的一切操作都会反映到原数组上(例如修改元素的值) */

    18.9 使用make创建切片

    var slice []type = make([]typeName, length) slice := make([]typeName, length) slice := make([]typeName, length, maxSize)

    18.10 用代码证明,切片在参数传递过程中是一种浅拷贝的值传递

    下面的代码中:   两次打印slice的地址,得到的结果不一样,说明这是值传递而不是引用传递,因为如果是引用,则两次打印的地址应该相同才对。   两次打印slice[0]的地址,发现它们是相同的,因此可以确定,两个slice引用的是同一个数组。   因此我们可以确定:slice在参数传递的过程中,是值传递的形式,但是它在值传递的过程中,是浅拷贝的。

    package main import ( "fmt" ) func sliceTest(slice []int) { fmt.Printf("In func sliceTest(), slice's address is:%p\n", &slice) fmt.Printf("&slice[0] = %p\n", &(slice[0])) } func main() { var slice []int = []int{0, 1, 2, 3, 4} fmt.Printf("In func main(), slice's address is: %p\n", &slice) fmt.Printf("&slice[0] = %p\n", &(slice[0])) sliceTest(slice) } /* output: API server listening at: 127.0.0.1:49333 In func main(), slice's address is: 0xc0000044a0 &slice[0] = 0xc00000a390 In func sliceTest(), slice's address is:0xc0000044e0 &slice[0] = 0xc00000a390 Process exiting with code: 0 */

    下面的代码也能证明切片在参数传递过程中是值传递的。

    package main import ( "fmt" ) func testSlice(slice []int) { slice = append(slice, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) } func main() { slice := []int{} fmt.Println("Before test", slice) testSlice(slice) fmt.Println("After test", slice) } /* output: API server listening at: 127.0.0.1:26247 Before test [] After test [] Process exiting with code: 0 */

    18.11 数组和切片的展开(解包)

    下面的代码证明了,切片可以使用 slice... 展开,数组不行,但是数组可以通过切片操作返回一个相应的切片,然后使用该切片展开。 将下面代码解注释会导致编译失败。

    package main import ( "fmt" ) func main() { var arr [5]int = [5]int{1, 2, 3, 4, 5} slice := []int{1, 2, 3, 4, 5} //slice = append(slice, arr...) fmt.Println("Before append: ", slice) slice = append(slice, slice...) fmt.Println("After append:", slice) slice = append(slice, arr[:]...) fmt.Println("The second append:", slice) } /* output: API server listening at: 127.0.0.1:4986 Before append: [1 2 3 4 5] After append: [1 2 3 4 5 1 2 3 4 5] The second append: [1 2 3 4 5 1 2 3 4 5 1 2 3 4 5] Process exiting with code: 0 */

    19. 错误

    19.1 errors包的基本使用

    //panic package main import "errors" func initConfig() (err error) { return errors.New("init config failed") } func test() { err := initConfig() if err != nil { panic(err) } } func main() { test() } /* output: PS E:\Code\GoCode\TestProject> go build .\main.go PS E:\Code\GoCode\TestProject> .\main.exe panic: init config failed goroutine 1 [running]: main.test(...) E:/Code/GoCode/TestProject/main.go:12 main.main() E:/Code/GoCode/TestProject/main.go:17 +0x62 */

    20. 使用 goroute 实现并发

    package main import ( "fmt" "time" ) func test(str string) { for i := 0; i < 3; i++ { fmt.Println(str) time.Sleep(time.Second) } } func main() { go test("AAAAA") go test("BBBBB") time.Sleep(time.Second * 5) } /* output: API server listening at: 127.0.0.1:3800 BBBBB AAAAA AAAAA BBBBB BBBBB AAAAA Process exiting with code: 0 */

    21. 闭包

    一个函数和与其相关的引用环境组合而成的实体

    package main import ( "fmt" ) func add() func(int) int { var x int return func(d int) int { x += d return x } } func main() { f := add() fmt.Println(f) fmt.Println("f(1) = ", f(1)) fmt.Println("f(100) = ", f(100)) g := add() g(10) fmt.Println("f(1000) = ", f(1000)) h := f fmt.Println("h(1000) = ", h(1000)) f = add() fmt.Println("f(10000) = ", f(10000)) } /* output: API server listening at: 127.0.0.1:28789 0x4bd900 f(1) = 1 f(100) = 101 f(1000) = 1101 h(1000) = 2101 f(10000) = 10000 Process exiting with code: 0 *********************************************************************** 分析: f := add() 在此语句中,x 被定义并且初始化为0 然后f指向了一个函数(暂且称为A,A由add()返回,其地址被 f 接收),可以通过 f 调用函数A 在后面的语句执行过程中: x 由于作为一个外部变量被函数A所引用,因此被闭包保存 并且每次使用 f 执行函数A时,函数A对于 x 的修改都会保存在x中 一旦 f := add() 重新执行了,f 指向了一个新的函数(暂且称为B) 虽然 A 和 B 长得一模一样,但是确实是两个不同的函数 函数B的闭包环境中,x 被初始化为0 而 h := f,则使得 h 保存了函数A的地址,因此通过 h 使用函数A 时,x 继续累加 因此就可以看到本例中得到的结果。 */

    下面是闭包的一个应用举例

    package main import ( "fmt" "strings" ) func addSuffix(suffix string) func(string) string { return func(name string) string { if strings.HasSuffix(name, suffix) == false { return name + suffix } return name } } func main() { addJpg := addSuffix(".jpg") addPng := addSuffix(".png") fmt.Println(addJpg("BeautifulGirl")) fmt.Println(addJpg("BeautifulLady")) fmt.Println(addPng("GentleMan")) fmt.Println(addPng("HandsomeGuy")) } /* output: API server listening at: 127.0.0.1:9158 BeautifulGirl.jpg BeautifulLady.jpg GentleMan.png HandsomeGuy.png Process exiting with code: 0 */

    22. 关于排序

    直接使用sort包即可对slice进行排序,读者可以自行查阅相关资料,也可以查看我的另一篇文档[暂未完成,以后上传了会来这里挂上链接]

    22.1 sort包对切片进行排序

    代码如下:

    //Example1, sort slice package main import ( "fmt" "math/rand" "sort" "time" ) func createSliceInt(size int) (slice []int) { slice = make([]int, size) for i := 0; i < size; i++ { slice[i] = rand.Intn(100) } return } func main() { rand.Seed(time.Now().UnixNano() / 1000) slice := createSliceInt(10) sort.Ints(slice) fmt.Println(slice) } /* output: API server listening at: 127.0.0.1:19186 [8 10 26 31 36 58 77 88 91 91] Process exiting with code: 0 */

    22.2 sort包对数组进行排序

    不能直接对数组进行排序,因为被排序的对象需要作为参数传递进去,并且当排序完毕后,作为参数的排序对象已经排序完成(被改变了内部元素序列),数组作为参数时是整个数组进行值拷贝,在排序函数内部排序完毕并不能影响到原来的数组,因此不能对数组进行排序。可以对整个数组进行切片然后传递进去。

    //Example2: package main import ( "fmt" "sort" ) func main() { var arr [5]int = [5]int{5, 4, 3, 2, 1} sort.Ints(arr[:]) fmt.Println(arr) } /* output: API server listening at: 127.0.0.1:47606 [1 2 3 4 5] Process exiting with code: 0 */
    Processed: 0.015, SQL: 9