文章目录
- 这是我的Golang学习笔记
- 1. 运行 go 程序:go run xxx.go
- 2. 标识符的私有化
- 3. 函数
- 4. 不能将源文件命名为 xxx_test.go
- 5. Windows不支持`go run *.go`的写法
- 6. go 不支持任何函数之外存在执行语句
- 7. 包别名语法
- 8. init函数
- 9. 关于包的导入
- 10. 常量
- 11. 每个包都要独占一个目录
- 12. 数据类型
- 13. 格式化输出控制符
- 14. strings的基本使用
- 14.1 strings.HasPrefix(s string, prefix string) bool
- 14.2 strings.HasSuffix(s string, suffix string) bool
- 14.3 strings.Index(s string, str string) int
- 14.4 strings.LastIndex(s string, str string) int
- 14.5 strings.Replace(str string, old string, new string, n int) string
- 14.6 strings.Count(str string, substr string) int
- 14.7 strings.Repeat(str string, count int) string
- 14.8 strings.ToLower(str string) string
- 14.9 strings.ToUpper(str string) string
- 14.10 ...
- 14.11 Go遍历含中文的字符串并输出
- 15. strconv 包的基本使用
- 16. 时间和日期类型
- 17. 流程控制
- 18. 数组和切片
- 19. 错误
- 20. 使用 goroute 实现并发
- 21. 闭包
- 22. 关于排序
这是我的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 funcName(FormalParameterList) ReturnValueList {
FuncBody
}
func add(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 | 主要用来关闭channel |
len | 用来求长度,比如string、arrav、slice、map、channel |
new | 用来分配内存,主要用来分配值类型,比如int、struct |
make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice |
append | 用来追加元素到数组、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
*/
9.4 包的名称与其所在目录不一致
此时由于import后面跟的是目录名而不是包名,而使用资源时,限定符必须是包名,因此当包的名称与其所在目录不一致
时,我们需要给导入的包取别名。
import (
test "project_name/test_dir"
)
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. 格式化输出控制符
控制符 | 含义 |
---|---|
%v | the value in a default format when printing structs, the plus flag (%+v) adds field names |
%#v | a Go-syntax representation of the value |
%T | a 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) string
字符串替换,返回新串,源串不变
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 < 13")
default:
fmt.Println("This is default")
}
}
/*
output:
API server listening at: 127.0.0.1:7550
a is: 12 , a > 0 && a < 13
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
*/