返回顶部

05 | 数组和切片

内置函数

1. close:主要用来关闭channel

2. len:用来求长度,比如string、array、slice、map、channel

3. new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针

4. make:用来分配内存,主要用来分配引用类型,比如chan、map、slice

5. append:用来追加元素到数组、slice中

6. panic和recover:用来做错误处理

new和make的区别

  new 返回的是一个地址,使用前还需要我们进行初始化  

  make 返回的是类型

package main

import (
	"fmt"
)

func main() {
	// new 返回的是一个地址,使用前还需要我们进行初始化
	s1 :=new ([]int)
	fmt.Println(s1)
	*s1 = make([]int,10)
	(*s1)[0] = 100
	fmt.Println(*s1)

	// make 返回的是类型

	s2 := make([]int,10)
	fmt.Println(s2)
	s2[0]=1
	fmt.Println(s2)

}

输出结果如下

apend 简单使用

package main

import "fmt"

func main() {
	var a []int
	a = append(a, 10, 20, 383,44)
	a = append(a, a...)
	fmt.Println(a)
}

输出结果如下

panic和recover:用来做错误处理

recover 捕获异常

panic 错误堆栈信息

package main

import (
	"fmt"
	"time"
)
import "errors"

func initConfig() (err error) {
	return errors.New("init config failed")
}

func test() {

	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()

	err := initConfig()
	if err != nil {
		panic(err)
	}
	return
}

func main() {
	for {
		test()
		time.Sleep(time.Second)
	}



} 

输出结果如下

递归函数

递归函数的设计原则

  1  一 个大的问题能够分解成相似的小问题

  2  定义好出口条件

package main

import (
	"fmt"
)

func recusive(n int) {
	if n > 1 {
		return
	}
	fmt.Println("hello", n)
	recusive(n + 1)
}

func factor(n int) int {
	if n == 1 {
		return 1
	}
	return factor(n-1) * n
}

func fab(n int) int {
	if n <= 1 {
		return 1
	}

	return fab(n-1) + fab(n-2)
}

func main() {
	// 递归实现阶乘
	fmt.Println(factor(5))

	// 递归调用 2 次
	recusive(0)

	// 递归实现斐波那契
	for i := 0; i < 4; i++ {
		fmt.Println(fab(i))
	}
}

输出结果如下

闭包

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

 例子1 不带参数的闭包

package main

import "fmt"

func Adder() func(int) int {
    var x int
    f := func(d int) int {
        x += d
        return x
    }
    return f
}


func main() {
    f := Adder()
    fmt.Println(f(1))
    fmt.Println(f(100))
    fmt.Println(f(1000))

}

返回结果如下

  例子2 带参数的闭包

package main

import (
    "fmt"
    "strings"
)


func makeSuffix(suffix string) func(string) string {
    f := func(name string) string {

        if strings.HasSuffix(name, suffix) == false {
            return name + suffix
        }
        return name
    }

    return f
}

func main() {

    f1 := makeSuffix(".bmp")
    fmt.Println(f1("test"))
    fmt.Println(f1("pic"))

    f2 := makeSuffix(".jpg")
    fmt.Println(f2("test"))
    fmt.Println(f2("pic"))
}

 

输出结果如下

数组

数组的定义:

数组是具有固定长度并拥有零个或者多个相同数据类型元素的序列

定义一个数组的方法:
var 变量名[len] type

长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型

数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1

例子:
var a[5] int       //3个整数的数组

var a[5]string    //3个字符串的数组

像上面这种定义方法,我们是指定了数组的长度,但是还有如下定义方法:

var a=[...]int{1,2,3}

如果把数组的长度替换为...,那么数组的长度由初始化数组的元素个数决定

数组中的每个元素是通过索引来访问,索引是从0开始

例如 数组var a[5]int 获取第一个元素就是a[0],

获取数组的长度是通过len(a)

这里需要知道:数组的长度也是数组类型的一部分,所以要知道[3]int和[4]int是不同的数组类型

默认情况下一个新数组中的元素初始值为元素类型的零值

如一个证书类型的数组,默认值就是0

初始化数组:

有一下几种方法:
var a = [5] int{1,2,3,4,5}
var a = [5] int{1,2,3}
var a = [...]int{1,2,3,4}
var a = [5]string{1:"go",3:"python"}

关于数组的类型

值类型所以我们要在函数中修改数组中的值,需要通过指针

数组的遍历

方法 1

for i := 0; i < len(a); i++ {
}  

方法 2

第一个是下标,第二个是值

var a = [3]int{1, 2, 3}
for i, v := range a {

	fmt.Printf("%d %d\n", i, v)

}  

当然如果不需要索引也可以:

var a = [3]int{1, 2, 3}
for _, v := range a {

fmt.Printf("%d\n", v)

}

数组是值类型,因此改变副本的值,不会改变本身的值

package main

import "fmt"

func test3(arr [5]int) {
	arr[0] = 1000
	fmt.Println(arr)

}

func main() {
	var a [5]int
	test3(a)

	fmt.Println(a)
}  

输出结果如下

指针修改函数中数组的值

package main

import "fmt"


func test4(arr *[5] int){
	(*arr)[0] = 100
}


func main() {
	var a [5]int
	test4(&a)
	fmt.Println(a)
}  

输出结果如下

 

练习:使用非递归的方式实现斐波那契数列,打印前100个数。

package main

import "fmt"

func fab(n int) {
    var a []uint64
    a = make([]uint64, n)

    a[0] = 1
    a[1] = 1

    for i := 2; i < n; i++ {
        a[i] = a[i-1] + a[i-2]
    }

    for _, v := range a {
        fmt.Println(v)
    }
}

func main() {
    fab(5)


}

 

输出结果如下

 

二维数组

var a[3][2]

其实二维数组可以通过excel表格理解,就是几行几列的问题,像上面的这个例子就是一个3行2列的二维数组。

package main

import "fmt"

func main() {
	var age [5][3]int
	age[0][0]=1
	fmt.Println(age)
	var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
	fmt.Println(f)
}  

输出结果如下

关于二维数组的遍历,创建一个二维数组并循环赋值,然后循环打印内容

package main

import "fmt"

func testArray2() {
	var a [2][5]int = [...][5]int{{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}}

	for row, v := range a {
		for col, v1 := range v {
			fmt.Printf("(%d,%d)=%d ", row, col, v1)
		}
		fmt.Println()
	}
}

func main() {
	testArray2()
}  

输出结果如下

关于数组的比较

如果两个数组的元素类型相同是可以相互比较的,例如数组a:= [2]int{1,2}和数组b:=[2]int{3,4}

因为同样都是int类型,所以可以通过==来比较两个数组,看两边的元素是否完全相同,使用!= 比较看两边的元素是否不同

通过下面的例子演示更加清晰:

a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{3, 2}
d := [3]int{1, 2}
fmt.Println(a == b, a == c, b == c)
fmt.Println(a == d)

上面的例子中第一个打印的结果是true,false,false,而当添加第二个打印的时候,就无法编译过去,因为两者是不能比较的

切片slice

定义

slice 表示一个拥有相同类型元素的可变长的序列

定义一个slice其实和定义一个数组非常类似

var 变量名[]type
var b = []int

和数组对比slice似乎就是一个没有长度的数组

slice的初始化
var a[5] int //这是定义一个数组
var b[]int = a[0,2]
var b[]int = a[0:5]
var b[]int = a[:]
var b[]int = a[:3]

var b[] int = []int{1,2,3,4}

同样遍历切片和数组是一模一样的

通过把数组和slice对比我们其实可以发现,两者其实非常类似,当然两者也确实有着紧密的关系

slice的底层实现就是一个数组,通常我们会叫做slice的底层数组。

slice具有三个属性:指针,长度和容量,如下图

指针指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素

长度是指slice中元素的个数,不能超过slice的容量

容量的大小是从slice的起始元素到底层数组的最后一个元素的个数

slice是引用类型(是可变的这点和数组有一定的区别)

package main

import "fmt"

func test1(b[] int){
    b[0]=100
}

func main() {
    var a[5]int
    fmt.Println(a)
    //定义一个slice
    var b[]int
    b = a[1:]
    fmt.Println(b)
    //在一个函数中修改slice的值,原来的值会发生该表因为slice是引用类型
    test1(b)
    fmt.Println(b)
}

输出结果如下

 

写一个程序演示切片的内存布局

package main

import "fmt"

func test1(b[] int){
    b[0]=100
}

func main() {
    var a[5]int
    fmt.Println(a)
    //定义一个slice
    var b[]int
    b = a[1:]
    fmt.Println(b)
    //在一个函数中修改slice的值,原来的值会发生该表因为slice是引用类型
    test1(b)
    fmt.Println(b)
}
View Code

输出结果如下

 

通过len和cap可以获取slice的长度和容量

通过下面例子理解:

package main

import "fmt"


func testSlice() {
    var slice []int
    var arr [5]int = [...]int{1, 2, 3, 4, 5}

    slice = arr[1:]
    fmt.Println(slice)
    fmt.Println(len(slice))
    fmt.Println(cap(slice))

    slice = slice[2:]
    fmt.Println(slice)
    fmt.Println(cap(slice))
    fmt.Println(len(slice))
}

func testSlice4() {
    var a = [10]int{1, 2, 3, 4}

    b := a[1:5]
    fmt.Printf("%p\n", b)
    fmt.Printf("%p\n", &a[1])
}

func main() {
    testSlice()
    //testSlice4()
}

输出结果如下

 

slice的地址指向的是数组中开始截取的第一个元素的地址

package main

import "fmt"


func testSlice4() {
	var a = [10]int{1, 2, 3, 4}

	b := a[2:5]
	fmt.Printf("%p\n", b)
	fmt.Printf("%p\n", &a[2])
}

func main() {
	testSlice4()
} 

输出结果如下

slice创建

内置函数make可以创建一个具有指定元素类型、长度和容量的slice,其中容量参数可以省略,这样默认slice的长度和容量就相等了

make([]type,len,cap)
make([]type,len)

现在说说关于:
make([]type,len)
make([]type,len,cap)

其实make创建了一个无名数组并返回了它的一个slice;这个数组仅可以通过slice来访问。

第一个:make([]type,len)返回的slice引用了整个数组。

第二个:make([]type,len,cap)slice只引用了数组的前len个元素,但是它的容量是数组的长度

通过下图理解切片的创建过程:

关于copy

该函数主要是切片(slice)的拷贝,不支持数组

将第二个slice里的元素拷贝到第一个slice里。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制。

通过下面例子便于理解: 

package main

import (
    "fmt"
)

func main() {
    var a []int = []int{1, 2, 3, 4, 5, 6}
    b := make([]int,2)
    copy(b, a)
    fmt.Println(b)
    
}

输出结果如下

 

package main

import (
	"fmt"
)

func main() {

	s1 := []int{1, 2, 3, 7, 8}
	s2 := []int{4, 5, 6}
	copy(s2, s1)
	fmt.Print(s2)
}

输出结果如下

这次拷贝就是把s2中的前三个元素拷贝到s1中的前三个,把s1中的前三个进行了覆盖

用内置append函数操作切片

遍历slice rune类型一个字符就是一个长度

package main

import "fmt"

func main() {
	var runnes []rune
	for _, v := range "hello go" {
		runnes = append(runnes, v)
	}
	fmt.Printf("%q\n", runnes)
}

输出结果如下

当使用append添加元素超出切片的容量的时候,它会重新开辟一块内存空间,把原数组内容copy过来进行后,在从后面追加

 

例子2直接在一个已经有元素的slice追加  

s1 := []int{1, 2, 3}
s1 = append(s1, 4, 5)
fmt.Printf("%#v\n", s1)

如果想要把另外一个slice也直接append到现在的slice中:  

s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2...)
fmt.Printf("%#v\n", s1)

这里在s2后面通过...其实就是把s2中的元素给展开然后在append进s1中

其实append函数对于理解slice的工作原理是非常重要的,下面是一个为[]int数组slice定义的一个方法:

package main

import "fmt"

func testSlice() {
    var a [5]int = [...]int{1, 2, 3, 4, 5}
    s := a[1:]
    fmt.Printf("before len[%d] cap[%d]\n", len(s), cap(s))
    s[1] = 100
    fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
    fmt.Println("before a:", a)

    s = append(s, 10)
    s = append(s, 10)
    fmt.Printf("after len[%d] cap[%d]\n", len(s), cap(s))
    s = append(s, 10)
    s = append(s, 10)
    s = append(s, 10)

    s[1] = 1000
    fmt.Println("after a:", a)
    fmt.Println(s)
    fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
}

func main() {
    testSlice()
}

输出结果如下

从上面的这个方法可以看出:

每次appendInt的时候都会检查slice是否有足够的容量来存储数组中的新元素,如果slice容量足够,那么他会定义一个新的slice,注意这里仍然引用原始的底层数组,然后将新元素y复制到新的位置,并返回新的slice,这

样我们传入的参数切片x和函数返回值切片z其实用的是相同的底层数组。

如果slice的容量不够容纳增长的元素,appendInt函数必须创建一个拥有足够容量的新的底层数组来存储新的元素,然后将元素从切片x复制到这个数组,再将新元素y追加到数组后面。这样返回的切片z将和传入的参数

切片z引用不同的底层数组。

关于切片的比较

和数组不同的是,切片是无法比较的,因此不能通过==来比较两个切片是否拥有相同的元素

slice唯一允许的比较操作是和nill比较,切片的零值是nill

这里需要注意的是:值为nill的slice的长度和容量都是零,但是这不是决定的,因为存在非nill的slice的长度和容量是零所以想要检查一个slice是否为还是要使用len(s) == 0 而不是s == nill

下面是整理的练习切片使用的例子

如何修改一个字符串?这里是把开头的大写的h换成了小写

package main

import (
    "fmt"
)

func changeString(str1 string) {
    var runnes = []rune(str1)
    runnes[0] = 'h'
    res := string(runnes)
    fmt.Println(res)
}

func main() {
    changeString("Hello,Go")
}

输出结果如下

再看一个例子:
实现字符串的反转

package main

import (
	"fmt"
)


func reverseStr(str1 string) {
	var runes = []rune(str1)
	var res string
	for i := len(runes) - 1; i >= 0; i-- {
		res += string(runes[i])
	}
	fmt.Println(res)
}

func main() {
	reverseStr("hell0")
}

输出结果如下

 

上面这个方法就可以实现对字符串的反转,当然方法不止一种,下面也是一种方法  

func reverseStr2(str1 string) {
    var runes = []rune(str1)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    res := string(runes)
    fmt.Println(res)
}

上面的方法中我一直在用到rune,这个东西是什么东西呢?接着看

GO当中的:string rune,byte

在Go当中的字符换string 底层是用byte数组存的,并且是不可改变的

当我们通过for key, value := range str这种方式循环一个字符串的时候,其实返回的每个value类型就是rune

而我们知道在go中双引号引起来的是字符串string,在go中表示字符串有两种方式:

一种是byte,代表utf-8字符串的单个字节的值;另外一个是rune,代表单个unicode字符串

关于rune官网中一段解释:

rune is an alias for int32 and is equivalent to int32 in all ways. It is
used, by convention, to distinguish character values from integer values.

我们通过下面的代码例子来理解一下:

var a = "我爱你go"
fmt.Println(len(a))

上面已经说了,字符串的底层是byte字节数组,所以我们通过len来计算长度的时候,其实就是获取的该数组的长度,而一个中文字符是占3个字节,所以上面的结果是11

可能很多人第一眼看的时候,尤其初学者可能会觉得长度应该是5,其实,如果想要转换成4只需要通过虾米那方式就可以:

var a = "我爱你go"
fmt.Println(len([]rune(a)))

时间和日期类型

当前时间:now:= time.Now()

time.Now().Day()

time.Now().Minute()

time.Now().Month()

time.Now().Year()

time.Duration用来表示纳秒

一些常用的时间常量

const (
Nanosecond Duration = 1
Microsecond =1000 * Nanosecond
Millisecond =1000 * Microsecond
Second =1000 * Millisecond
Minute =60 * Second
Hour =60 * Minute
)

注意:如果想要格式化时间的时候,要特别特别注意,只能通过如下方式格式化:

fmt.Println(time.Now().Format("2006-01-02 15:04:05"))

Format里面的时间是固定的,因为是go第一个程序的诞生时间,也不知道go的开发者怎么想的,估计是想让所有学习go的人记住这个伟大的时刻吧

切片处理补充

关于切片删除

代码例子:

package main

import "fmt"

func main() {
    index := 2
    var s = []int{10,15,8,20}
    s = append(s[:index],s[index+1:]...)
    fmt.Println(s)
}

  

  

  

 

  

  

posted @ 2019-01-06 23:11  Crazymagic  阅读(130)  评论(0编辑  收藏  举报