本文最后更新于 654 天前,其中的信息可能已经有所发展或是发生改变。
承接上文 Go 基础入门笔记 – Part 1 ,本文将介绍 Go 语言基础知识的后半部分:
- 数组与切片
- Map 及其进阶使用
- 字符串与 Byte 类型
- 函数
数组与切片
数组
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
func TestArrayInit(t *testing.T) {
var arr [3]int //Length set but not initialized.
arr1 := [4]int{1, 2, 3, 4} //Length set and initialized.
arr3 := [...]int{1, 3, 4, 5} //Length not defined by user, set to 4 for defined integers.
arr1[1] = 5 //set value for variable in array.
t.Log(arr[1], arr[2])
t.Log(arr1, arr3)
}
在上面的例子中,可以看到声明数组的多个方式。
- 在声明数组及其长度时,可以同时对其数值进行初始化。
- 如果数组长度不确定,可以使用
[...]
代替数组的长度。
func TestArrayTraverse(t *testing.T) { //Shown in recent codes.
arr := [...]int{1, 1, 4, 5, 1, 4}
for i, e := range arr {
t.Log(i, e)
}
}
在上一篇文章提到,我们可以通过 for…range 语句方便的对数组进行遍历。
func TestArrayIntercept(t *testing.T) { //Array interception, usage: [<front>:<rear(not included)>]
arr := [...]int{1, 1, 4, 5, 1, 4}
arrSec := arr[0:]
arrSec1 := arr[1:5]
t.Log(arrSec)
t.Log(arrSec1)
}
如上面的例子所示,我们可以截取数组中连续的一部分,用于给其他变量赋值,传递返回值等。
切片
Go 中提供了一种灵活,功能强悍的内置类型切片。
与数组相比,切片的长度是不固定的,可以追加元素。
func TestSliceInit(t *testing.T) {
var s0 []int //Declare new slice.
t.Log(len(s0), cap(s0)) //Show length and capacity.
s0 = append(s0, 1) //Insert new data from tail.
t.Log(len(s0), cap(s0)) //Show length and capacity.
s1 := []int{1, 2, 3, 4} //Declare new slice and initialize.
t.Log(len(s1), cap(s1)) //Show length and capacity.
s2 := make([]int, 3, 5) //Another way to make new slice.
t.Log(len(s2), cap(s2)) //Show length and capacity.
//t.Log(s2[0], s2[1], s2[2], s2[3], s2[4])
t.Log(s2) //Showing all data.
s2 = append(s2, 1)
s2 = append(s2, 4)
t.Log(s2) //Showing all data after append.
t.Log(len(s2), cap(s2)) //Show length and capacity.
}
在上面的例子中,我们能看到声明切片的多种方式,并可以直观的返回其对应长度与容量。
func TestSliceGrowing(t *testing.T) { //Slice capacity doubles when there's not enough space.
var growth []int
for i := 1; i <= 16; i++ {
growth = append(growth, i)
t.Log(len(growth), cap(growth), growth)
}
}
通过执行上述代码,我们可以发现:
- 当切片的元素个数即将超过其容量时,其容量会扩增到原来的两倍,进行内存的分配。
func TestSliceMemoryShare(t *testing.T) { //Slice interceptions share their memory and data.
var months = []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
var Q2 = months[3:6]
t.Log(Q2, len(Q2), cap(Q2))
Q2[2] = "July"
t.Log(months)
}
在上述代码,我们可以通过设置下限及上限来设置截取切片。
但截取切片后赋予新变量,其内存空间与原切片的内存共享,即会相互影响。
Map 及其进阶使用
Map 是一种无序的键值对的集合。
func TestMapInit(t *testing.T) { //Different ways to declare maps.
m1 := map[int]int{1: 1, 2: 4, 3: 9}
t.Log(m1[2], len(m1))
m2 := map[int]int{}
m2[4] = 16
t.Log(m2, len(m2))
m3 := make(map[int]int, 10)
t.Log(len(m3))
}
在上面的例子中,我们能看到声明集合的多种方式。
func TestAccessKey(t *testing.T) { //Check if a key is valid.
m1 := map[int]int{}
t.Log(m1[1])
m1[2] = 0
t.Log(m1[2])
if v, ok := m1[3]; ok {
t.Log(v)
} else {
t.Log("Nil")
}
}
在对map里面的元素进行调用时,若不存在相应的键值,仍会返回初始值(如 int 类型的 0)。
此时,通过对 key 的检验,可以排除该键值是否存在的问题。
func TestMapWithFunction(t *testing.T) { //Map implements factory mode.
m := map[int]func(op int) int{}
m[1] = func(op int) int { return op }
m[2] = func(op int) int { return op * op }
m[3] = func(op int) int { return op * op * op }
t.Log(m[1](2), m[2](2), m[3](2))
}
在上述代码中,我们可以看到,可以通过使用函数值,实现map的工厂模式。
func TestMapToSet(t *testing.T) { //Map implements Set collection.
m := map[int]bool{}
m[1] = true
t.Log(m[1], m[3], len(m))
delete(m, 1)
m[3], m[5] = true, true
t.Log(m[1], m[3], len(m))
}
Go的内置集合中没有Set的实现,但是我们可以使用 map[type]bool 来去实现一个Set。
字符串与 Byte 类型
Go 当中的字符串本质是只读的字符型数组。
func TestString(t *testing.T) {
str := "Hello world"
t.Log(str, len(str))
//str[6] = '_' //compile error, can't assign
str = "逸一时,误一世"
t.Log("Sentence length:", len(str)) //length is 21, 3 bytes for a Chinese character
str = "\xE4\xB8\x96" //UTF-8 code
t.Log("UTF8 word and length:", str, len(str))
}
这段代码可以这样理解:
- 字符串本质是只读的字符型数组,无法通过类似数组中元素修改那样修改字符串。
- 字符串可存储 Unicode 字符,文中的中文字符为 UTF-8 编码,每个字占用 3 字节。
func TestStringFunctions(t *testing.T) {
str := "逸一时,误一世,逸久逸久罢已龄"
strParts := strings.Split(str, ",")
for _, e := range strParts {
t.Log(e)
}
t.Log(strings.Join(strParts, "_"))
str1 := strconv.Itoa(10) //Convert integer to string
t.Log(str1)
ret, err := strconv.Atoi(str1) //Convert string to integer, return integer and error code (if exist)
if err != nil {
t.Log("Convert failed")
} else {
t.Log(ret - 10)
}
}
上述代码举例了一些常用的相关函数:
strings.Split
函数,用于将字符串通过特定关键字拆分为字符串“数组”。strings.Join
函数,用于将字符串“数组”通过特定关键字重新组合为单个字符串。strconv
函数,用于对相应的变量类型进行字符转换。
函数
函数是基本的代码块,用于执行一个任务。
这里直接放出样例:
func MultiValue() (int, int) {
rand.Seed(time.Now().Unix())
return rand.Intn(10), rand.Intn(100)
}
func VerySlowFunction(op int) int {
i := 0
rand.Seed(time.Now().Unix())
for rand.Intn(100) != op {
time.Sleep(1 * time.Millisecond)
i++
}
return i
}
func spentTime(inner func(op int) int) func(testInt int) {
return func(n int) {
start := time.Now() //Record start time.
inner(n) //Execute inner function.
fmt.Println("Time spent:", time.Since(start).Milliseconds()) //Calculate spent time.
}
}
func IntSum(nums ...int) int { //multi parameters
ans := 0
for _, num := range nums {
ans += num
}
return ans
}
func CleanOutput() {
fmt.Println("Cleaning resources... Please wait")
}
func TestDefer(t *testing.T) {
defer CleanOutput() //defer is always reachable after function execution.
fmt.Println("Function start.")
panic("Function execution failed.")
fmt.Println("Unreachable printing.") //Println after panic, unreachable.
}
func TestFunctions(t *testing.T) {
t.Log(MultiValue()) //Return multiple value
spentTime(VerySlowFunction)(67) //Test function execution time.
t.Log(IntSum(1, 2, 3, 4)) //Test summary function.
}
通过上述代码,可以知道:
- MultiValue 函数中,Go 函数可以返回多个值。
- spentTime 函数中,可以获知 Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。
- IntSum 函数中,可知函数可以接受可变数量的数值。
- TestDefer 函数中,可知 defer 始终会在函数执行结束后执行,即使函数中途报错退出。