AI智能
改变未来

从0开始学Go(一)

文章目录

  • 这是我的Golang学习笔记
  • 1. 运行 go 程序:go run xxx.go
  • 2. 标识符的私有化
  • 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.go
  • 5. 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) 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)
    • 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. 时间和日期类型
    • 16.1 func (t Time) Format(layout string) string
  • 17. 流程控制
    • 17.1 if/else
    • 17.2 普通switch case
    • 17.3 条件switch case
    • 17.4 for
    • 17.5 for range
    • 17.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 Gopackage mainimport (\"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 mainimport \"fmt\"//type opFunc func(int, int) intfunc 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 := addsum := operator(c, 100, 200)fmt.Println(sum)}/*output:API server listening at: 127.0.0.1:14310300Process exiting with code: 0*/

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

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

    3.2.1 下面是基础版本示例

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

    3.2.2 进阶版

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

    package mainimport (\"fmt\")// getPerimeterArea returns the circumference and area of a rectanglefunc getPerimeterArea(width int, height int) (perimeter int, area int) {perimeter = width*2 + height*2area = width * heightreturn}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:8161The rectangle\'s perimeter is:  300 , area is:  5000Process 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 mainimport (\"fmt\")func add(a int, arg ...int) int {var sum int = afor i := 0; i < len(arg); i++ {sum += arg[i]}return sum}func addString(a string, arg ...string) (result string) {result = afor 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:4202910Hello Go!Process exiting with code: 0*/

    3.4 defer语句

    • 当函数返回时,自动执行defer语句,因此可以用来清理资源
    • 多个defer语句,按先进后出的方式执行
    • defer中的语句,在defer声明时就已经决定了
    package mainimport (\"fmt\")func main() {var i int = 0defer 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:2584j =  2j =  1j =  0i =  0Process exiting with code: 0*/

    3.5 匿名函数

    package mainimport (\"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:32747400Process 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 用来做错误处理
    //newpackage mainimport (\"fmt\")func main() {a := new(int)*a = 100fmt.Println(a)fmt.Println(*a)}/*output:API server listening at: 127.0.0.1:471470xc0000120b8100Process exiting with code: 0*/
    //makepackage mainimport (\"fmt\")func main() {pipe := make(chan int, 3)fmt.Println(len(pipe))pipe <- 1pipe <- 2pipe <- 3fmt.Println(len(pipe))close(pipe)}/*output:API server listening at: 127.0.0.1:825903Process exiting with code: 0*/

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

    package mainimport (\"fmt\")func main() {s1 := new([]int)fmt.Println(s1)s2 := make([]int, 10)fmt.Println(s2)(*s1)[0] = 100s2[0] = 100}/*output:PS E:\\Code\\GoCode\\TestProject> go build .\\main.goPS 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 0goroutine 1 [running]:main.main()E:/Code/GoCode/TestProject/main.go:12 +0x168*/
    //appendpackage mainimport (\"fmt\")func main() {var a []inta = 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中。

    //copypackage mainimport (\"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*/
    //panicpackage mainimport \"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.goPS E:\\Code\\GoCode\\TestProject> .\\main.exepanic: init config failedgoroutine 1 [running]:main.test(...)E:/Code/GoCode/TestProject/main.go:12main.main()E:/Code/GoCode/TestProject/main.go:17 +0x62*/
    //panic//panic异常可以被捕获并且处理,从而避免程序终结package mainimport (\"fmt\"\"time\")func test() {defer func() {if err := recover(); err != nil {fmt.Println(err)}}()b := 0a := 100 / bfmt.Println(a)}func main() {for {time.Sleep(time.Second)test()}}/*output:PS E:\\Code\\GoCode\\TestProject> go build .\\main.goPS E:\\Code\\GoCode\\TestProject> .\\main.exeruntime error: integer divide by zeroruntime error: integer divide by zeroruntime error: integer divide by zeroruntime error: integer divide by zeroPS 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 mainimport (\"fmt\")var num0 int = 0num1 := 1func main() {fmt.Println(num0)fmt.Println(num1)}/*直接报错# TestProject.\\main.go:8:1: syntax error: non-declaration statement outside function bodyexit status 2Process exiting with code: 1*/

    7. 包别名语法

    /*语法为:import(alaisName \"packageName\")*///Example:package mainimport (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:19950This is a test for taking an alias for a package when importing itProcess exiting with code: 0*/

    8. init函数

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

    package mainimport (\"fmt\")func init() {fmt.Println(\"This is init\")}func main() {mainfmt.Println(\"This is main\")}/*output:API server listening at: 127.0.0.1:14768This is initThis is mainProcess 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 mainimport (_ \"TestProject/another_pkg\"\"fmt\")func main() {fmt.Println(\"This is main\")}

    another_pkg.go:

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

    go.mod:

    module TestProjectgo 1.14

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

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

    import(. \"packageName\")

    Example:

    package mainimport (. \"fmt\")func main() {Println(\"This is package name import test...\")}/*output:API server listening at: 127.0.0.1:7663This 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.141592654const a = 9/3const c = getValue() //非法的//比较优雅的写法:const(a = 0b = 1c = 2)//更加专业的写法//这种写法会自动将a初始化成0,后边的依次初始化为1/2const(a = iotab // 1c // 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 mainimport (\"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.goPS E:\\Code\\StudyCode\\GoCode\\src\\TestProject> .\\main.exehttps://www.baidu.com*/

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

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

    //Example: Determine whether a path ends with \"/\", if not, add itpackage mainimport (\"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.goPS 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 mainimport (\"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:4136string 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 = 1Microsecond          = 1000 * NanosecondMillisecond          = 1000 * MicrosecondSecond               = 1000 * MillisecondMinute               = 60 * SecondHour                 = 60 * Minute)

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

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

    package mainimport (\"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:3825705/07/2020 16:232020/7/05 16:232020/7/0514/07/5050 76:55Process exiting with code: 0*/

    17. 流程控制

    17.1 if/else

    package mainimport (\"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:21498yes, \'0 < 1\', that\'s rightProcess exiting with code: 0*/

    17.2 普通switch case

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

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

    17.3 条件switch case

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

    package mainimport (\"fmt\")func main() {var a int = 10switch 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:7550a is:  12 , a > 0 && a < 11Process exiting with code: 0*/

    17.4 for

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

    17.5 for range

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

    for index, val := range variable {}
    package mainimport \"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:8097index[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 mainimport (\"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
    • 两种遍历方法:
      //Method1for i := 0; i < len(arrayName); i++ {}//Method2for index, value := range arrayName {}

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

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

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

    package mainimport (\"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 mainimport (\"fmt\")func test01() {var a [3]inta[0] = 100fmt.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]intb := ab[0] = 100fmt.Println(a)}func main() {fmt.Println(\"-----test01-----\")test01()fmt.Println(\"-----test02-----\")test02()fmt.Println(\"-----test03-----\")var a [3]inttest03(a)fmt.Println(a)}/*output:API server listening at: 127.0.0.1:12158-----test01-----[100 0 0]100	0	0a[0] = 100a[1] = 0a[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 mainimport (\"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:3823710xc0000c80307Process exiting with code: 0*/

    18.6 切片

    • 切片是一个数组的引用,因此切片是引用类型。
    • 切片的长度可变,因此,切片是一个可变的数组
    • 切片的遍历方式和数组一样,可以用len()求长度
    • cap可以求出slice的最大容量, 0 <= len(slice) <= cap(slice),器中array是slice引用的数组
    • 切片的定义:
      var 变量名 []类型

      ,比如

      var str [] string

      var arr [] int
    • 可以对数组进行切片操作,然后返回一个切片
      package mainimport (\"fmt\")func main() {var slice []intvar 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))}/*outputAPI server listening at: 127.0.0.1:15272[2]len(slice) =  1cap(slice) =  3Process exiting with code: 0*/

    18.7 切片的追加

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

    package mainimport (\"fmt\")func main() {var a []inta = 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 mainimport (\"fmt\")func main() {var slice []intvar 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] = 100fmt.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] = 0xc00000c1e0Process 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 mainimport (\"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:49333In func main(), slice\'s address is: 0xc0000044a0&slice[0] = 0xc00000a390In func sliceTest(), slice\'s address is:0xc0000044e0&slice[0] = 0xc00000a390Process exiting with code: 0*/

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

    package mainimport (\"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:26247Before test []After test []Process exiting with code: 0*/

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

    下面的代码证明了,切片可以使用

    slice...

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

    package mainimport (\"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:4986Before 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包的基本使用

    //panicpackage mainimport \"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.goPS E:\\Code\\GoCode\\TestProject> .\\main.exepanic: init config failedgoroutine 1 [running]:main.test(...)E:/Code/GoCode/TestProject/main.go:12main.main()E:/Code/GoCode/TestProject/main.go:17 +0x62*/

    20. 使用 goroute 实现并发

    package mainimport (\"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:3800BBBBBAAAAAAAAAABBBBBBBBBBAAAAAProcess exiting with code: 0*/

    21. 闭包

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

    package mainimport (\"fmt\")func add() func(int) int {var x intreturn func(d int) int {x += dreturn 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 := ffmt.Println(\"h(1000) = \", h(1000))f = add()fmt.Println(\"f(10000) = \", f(10000))}/*output:API server listening at: 127.0.0.1:287890x4bd900f(1) =  1f(100) =  101f(1000) =  1101h(1000) =  2101f(10000) =  10000Process 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 mainimport (\"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:9158BeautifulGirl.jpgBeautifulLady.jpgGentleMan.pngHandsomeGuy.pngProcess exiting with code: 0*/

    22. 关于排序

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

    22.1 sort包对切片进行排序

    代码如下:

    //Example1, sort slicepackage mainimport (\"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 mainimport (\"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*/
  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » 从0开始学Go(一)