从0开始学Go(一)

文章目录

这是我的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
*/

newmake 的区别: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. 格式化输出控制符

控制符含义
%vthe value in a default format
when 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) 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]intvar 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 [] stringvar 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
*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 下载安装Go语言的开发环境 Go语言的官方网站是golang.org,你可以在这里下载安装Go语言的开发环境。安装完毕后,可以在终端或命令行中输入go version来确认安装是否成功。 2. 习Go语言的基础语法 习一门编程语言,首先要掌握的是它的基础语法。可以通过阅读官方文档、教程、书籍等方式来习Go语言的基础语法。建议先习以下内容: - 变量和常量 - 数据类型 - 函数 - 控制结构(if/else、for、switch) - 数组和切片 - 结构体和指针 3. 编写第一个Go程序 在习了Go语言的基础语法后,可以试着编写第一个Go程序。可以使用任何文本编辑器(如Notepad、Sublime Text、Visual Studio Code等)来编写代码。以下是一个简单的“Hello World”程序: ``` package main import "fmt" func main() { fmt.Println("Hello, world!") } ``` 将代码保存为hello.go,然后在终端或命令行中输入以下命令来运行程序: ``` go run hello.go ``` 如果一切顺利,终端或命令行中将会输出“Hello, world!”。 4. 继续深入习Go语言 在习了Go语言的基础语法和编写第一个程序后,可以继续深入习Go语言的高级特性,比如: - 并发编程 - 接口和多态 - 包和模块化编程 - 错误处理 - JSON编码和解码 - 网络编程 可以阅读官方文档、书籍、博客等资源来习这些高级特性。同时,建议多实践,写一些小项目或练习题来加深对Go语言的理解和掌握。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值