所有项目均为Windows + VSCode + go环境下创建
这并不是一篇从完全意义上的小白开始学习的博文,看这篇博文之前还是需要一丁点的go的知识点的,比如第一个Golang程序“HelloGo”怎么写等等,甚至包括环境搭建啥的,这种我就不写了,毕竟随便一搜索就是大把的资料。
如果想要在外部调用某个包内部的函数/变量,需要命名时首字母大写
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 */返回值列表必须使用“()”括起来
在定义函数时,直接命名返回值,这样可以在返回时,直接只写一个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 */单返回值参数也可以命名,一旦命名,不论是单返回值还是多返回值,都必须使用“()”括起来
不需要导入任何包也不需要定义就可以直接使用的函数
函数名功能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> */这个跟其它语言一致,此处不赘述。
xxx_test.go 是测试文件,启动指令为 go test xxx_test.go,使用 go run/build 指令时,xxx_test.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.“:=” 这种写法相当于先定义(未初始化),然后再赋值,因此也是不允许的。
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 */每个源文件都可以有一个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 */如果只想使用某个包中的初始化动作(init 函数),而不使用其它任何变量和函数,可以使用给包取别名的语法,用“_”关键字作为其别名。
/* import( _ "packageName" ) */ 下面是一个示例,目录结构为: TestProject ├──go.mod ├──another_pkg | └──another_pkg.go └──main └──main.gomain.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使用下列语句导入某个包时,使用该包中的变量或者函数时,可以不带包名,直接使用。
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 */常量使用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 )不允许一个目录下存在多个包的go源码文件。
For more information, please refer to: go文档fmt包信息
判断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 */判断字符串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/ */判断str在s中首次出现的位置,如果没有,则返回-1
判断str在s中最后出现的位置,如果没有,则返回-1
字符串替换
字符串基数
重复count次str
转为小写
转为大写
For more information, please access to the official website: go strings package
关于字符串转换的包,详情请查询官方文档
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 */和其它语言中不同,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 */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 */这个操作用于遍历数组、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 */这个和C语言的一样,就不赘述了,不同的是go语言支持continue label和break label,这两个用法和C语言中的continue和break一样,甚至在go语言中加不加label没有任何区别,所以黑人问号?我是真的搞不懂为什么会有continue label和continue label的用法
数组一旦定义,长度不可变,但是切片可以使用相应函数改变长度,例如追加。
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 */下面的代码中: 两次打印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 */下面的代码证明了,切片可以使用 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 */一个函数和与其相关的引用环境组合而成的实体
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 */直接使用sort包即可对slice进行排序,读者可以自行查阅相关资料,也可以查看我的另一篇文档[暂未完成,以后上传了会来这里挂上链接]。
代码如下:
//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 */不能直接对数组进行排序,因为被排序的对象需要作为参数传递进去,并且当排序完毕后,作为参数的排序对象已经排序完成(被改变了内部元素序列),数组作为参数时是整个数组进行值拷贝,在排序函数内部排序完毕并不能影响到原来的数组,因此不能对数组进行排序。可以对整个数组进行切片然后传递进去。
//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 */