GO学习笔记 数组与切片

一.数组

1.数组的介绍

数组可以可以存放多个同一类型数据。数组也是一种数据类型,在GO中,数组是值类型。

举例:

package main

import "fmt"

func main(){
    // 使用数组
    // 定义一个数组
    var nums [6]float64;
    // 给数组的每一个元素赋值
    nums[0] = 3.0
    nums[1] = 5.0
    nums[2] = 1.0
    nums[3] = 3.4
    nums[4] = 2.0
    nums[5] = 50.0

    // 遍历数字求平均值
    total := 0.0
    for i := 0; i < len(nums); i++ {
        total += nums[i]
    }
    avg_num := fmt.Sprintf("%.2f",total / float64(len(nums)))
    fmt.Println(avg_num)
}

2.数组定义和内存布局

数组的定义

var 数组 [数组大小]数据类型

如:

var a [10]int;

赋值a[0],a[1]....a[9]

数组的地址可以通过数组名来获取 &arr

数组的首地址就是数组第一个值的地址的。 &arr[0]

数组第二个值的地址就是第一个地址加这个数组类型的字节数,这边如果是int就加8个字节。

package main

import "fmt"

func main() {
    var arr [4]int;
    fmt.Println("第一个地址:",&arr[0])  
    fmt.Println("第二个地址:",&arr[1])
    fmt.Println("第三个地址:",&arr[2])
    fmt.Println("第四个地址:",&arr[3])
    // 打印结果:
    // 第一个地址: 0xc000052120
    // 第二个地址: 0xc000052128
    // 第三个地址: 0xc000052130
    // 第四个地址: 0xc000052138
}

3.数组的使用

访问数组元素

数组名[下标] 比如:你要使用a数组的第三个元素 a[2]

package main

import "fmt"

func main() {
    var intArr [4]int
    intArr[0] = 10
    intArr[1] = 20
    intArr[2] = 30
    intArr[3] = 40

    fmt.Println("第一个地址:", &intArr[0])
    fmt.Println("第二个地址:", &intArr[1])
    fmt.Println("第三个地址:", &intArr[2])
    fmt.Println("第四个地址:", &intArr[3])
    // 打印结果:
    // 第一个地址: 0xc000052120
    // 第二个地址: 0xc000052128
    // 第三个地址: 0xc000052130
    // 第四个地址: 0xc000052138

    // 遍历数组打印
    for i := 0; i < len(intArr); i++ {
        fmt.Println(intArr[i])
    }
}

4.四种初始化数组的方式

package main

import "fmt"

func main() {
    // 四种数组的初始化
    // 法一
    var numArr01 [3]int = [3]int{1, 2, 3}
    fmt.Println("numArr01", numArr01)

    // 法二
    var numArr02 = [3]int{4, 5, 6}
    fmt.Println("numArr02", numArr02)

    // 法三
    var numArr03 = [...]int{7, 8, 9}
    fmt.Println("numArr03", numArr03)

    // 法四
    var numArr04 = [...]int{1:11, 0:10, 2:12}  // 按下表的位置顺序输出,有序
    fmt.Println("numArr04", numArr04)
}

5.数组的变量

方式1 :常规变量

package main

import "fmt"

func main() {
    // for 遍历数组
    var arrDemo = [...]int{1,2,3,4,5}
    for i := 0; i < len(arrDemo); i++ {
        fmt.Println(arrDemo[i])
    }
}

方式二:for-range结构遍历

GO 语言一种独有的结构,可以用来遍历访问数组的元素。

基本语法

for index,value := range arr {

....

}

说明:

(1)第一个返回值index是数组的下标

(2)第二个value是在该下标位置的值

(3)仅在for循环内部可见的局部变量

(4)遍历数组元素的时候,如果不想使用下标index,可以直接把下标index标为下划线_

(5)index和value 的名称不是固定的,可自定义

举例:

package main

import "fmt"

func main() {
    var arrDemo = [...]int{1,2,3,4,5}
    // for-range遍历方式
    for index, value := range arrDemo{
        fmt.Println(index,value)
    }
}

6.数组的使用注意事项和细节

(1)数组是多个相同类型数据的结合,一个数组一旦声明定义了,其长度是固定的,不能动态变化。

(2)var arr[]int 这时arr就是一个slice切片。

(3)数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用

(4)数组创建后,如果没有赋值,有默认值

数值类型数组:默认值为0

字符串数组: 默认为””

bool数组: 默认值为false

(5)使用数组的步骤1.声明数组并开辟空间 2.给数组各个元素赋值 3. 使用数组

(6)数组的下标是从0开始的

(7)数组的下标必须在指定范围内使用,否则报panic:数组越界,比如:

var arr [5]int 则有效下标为0-4

(8)GO的数组属值类型,在默认情况下是值传递,因此会进行拷贝。数组间不会互相影响。

(9)如想在其中函数中,去修改原来的数组,可以使用引用传递(指针方式)。

(10)长度是数组的一部分,在传递函数参数时,需要考虑数组的长度。

举例:(8),(9)

package main

import "fmt"

func test(arr [3]int) {
    arr[0] = 88
}

func demo(arr *[3]int) {
    (*arr)[1] = 99 
}

func main() {
    arr := [3]int{11,22,33}
    fmt.Println(arr)
    test(arr)
    // 数组的值不会因为调用的函数改变而改变
    // 拷贝的思想
    fmt.Println(arr)
    // demo 修改指针的方法可以实现修改数组里面的值
    // 传地址
    demo(&arr)
    fmt.Println(arr)
}

7.数组的应用案例

1.创建一个byte类型的26个元素的数组,分别放置A-Z。使用for循环访问所有元素并打印

出来。提示:字符数据运算 A + 1 > B

package main

import "fmt"

func main(){

    // 创建一个byte类型的26个元素的数组,分别放置A-Z。使用for循环访问所有元素并打印
    // 出来。提示:字符数据运算 A + 1 > B

    // 思路:
    //1.声明一个数组 var myChars [26]bytes
    // 2.使用for循环,利用 字符可以进行运算的特点来赋值  'A' + 1 -> 'B'

    var myChars [26]byte
    for i := 0; i < 26; i++ {
        myChars[i] = 'A' + byte(i)   // 注意需要将 i = > byte
    }

    for i:=0; i < 26; i++ {
        fmt.Printf("%c ",myChars[i])
    }
}

2.请求出一个数组最大值,并得到对应的下标

package main

import "fmt"

func  main()  {
    // 请求出一个数组最大值,并得到对应的下标

    // 思路 假定第一个元素就是最大值,下标是0
    // 然后从第二个元素开始循环比较,如果发现有更大,则交换
    var intArr[5] int = [...]int {1,-1,-99,90,11}
    maxVal := intArr[0]
    maxValindex := 0

    for i:=1; i<len(intArr); i++ {
        if maxVal < intArr[i] {
            maxVal = intArr[i]
            maxValindex = i
        }
        
    }
    fmt.Printf("maxVal=%v maxValindex=%v",maxVal,maxValindex)
}

3.请求一个数组的和和平均值。 for range

package main

import "fmt"

func main(){
    // 请求一个数组的和和平均值。 for range
    var intArr[5] int = [...]int {1,-1,-5,90,11}
    sum := 0
    for _,val := range intArr{
        // 求和
        sum += val
    }
    fmt.Println(sum,float64(sum)/float64(len(intArr)))

}

4.一个数组的反转

package main

import "fmt"
import "math/rand"
import "time"

func main()  {
    // 一个数组的反转
    // 要求:随机生成5个数,并将其反转打印
    // rand.Intn()  生成(0,n)的随机数
    // 反转打印,交换的次数是 len / 2
    // 第一个与倒数第一  第二与倒数第二
    var intArr [5] int 
    // 为了每次生成的随机数不一样
    rand.Seed(time.Now().UnixNano())
    for i:=0; i<len(intArr); i++ {
        intArr[i] = rand.Intn(100)  // 
    }
    fmt.Println(intArr)
    temp := 0
    for i:=0; i < len(intArr) / 2; i ++{
        temp = intArr[len(intArr) - 1 - i]
        intArr[len(intArr) -1 - i] = intArr[i]
        intArr[i] = temp
    }
    fmt.Println(intArr)
}

二.切片 Slice

1.切片的基本介绍

(1)slice 就是切片的意思

(2)切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。

(3)切片的使用和数组类似,遍历切片,访问切片的元素和求切片长度len(slice) 都一样。

(4)切片的长度是可以变化的,因此切片是一个可以动态变化的数组

(5)切片定义的基本语法:(特别像python的列表)

var 切片名 [] 类型

举例:

package main

import "fmt"

func main() {
    // 先定义一个数组
    var intArr [5]int = [...]int{1, 22, 33, 66, 99}

    // 定义一个切片
    slice := intArr[1:3]
    // 表示引用 intArr数组的下标为1的元素 到 下标为3 的元素,结束下标的元素不算在内
    fmt.Println(slice)       // [22 33]
    fmt.Println(len(slice))  // 2
    fmt.Println(cap(slice)) // 切片的容量是可以动态变化的,一般是slice的两倍 

}

2.切片在内存中形式

(1)slice的确是一个引用类型

(2)slice从底层来说,其实就是一个数据结构struct结构体

(3)slice 里面有三个部分,第一部分是引用数据开始的指针地址,第二部分是存放数据大小,第三部分是存放cap容量。

 slice[1] = 44
    // 改变切片的值,会使得切片引用的数组相应的数据也改变
    fmt.Println(intArr[2]) /

3.切片的使用

(1)第一种方式

定义一个切片,然后让切片去引用一个已经创建好的数组。

如下:

package main

import "fmt"

func main() {
    // 先定义一个数组
    var intArr [5]int = [...]int{1, 22, 33, 66, 99}

    // 定义一个切片
    slice := intArr[1:3]
    // 表示引用 intArr数组的下标为1的元素 到 下标为3 的元素,结束下标的元素不算在内
    fmt.Println(slice)       // [22 33]
}

(2)第二种方式

通过make来创建切片

基本语法:

var 切片名 [] type = make([],len,[cap])

type:就是数据类型

len:大小

cap:指定切片容量,可选

package main

import "fmt"

func main() {
    var slice []int = make([]int, 4, 10)
    fmt.Println(slice)
    fmt.Println("slice len=",len(slice),"slice cap=",cap(slice))
    slice[0] = 100
    slice[2] = 200
    fmt.Println(slice)

}

总结:

—1—通过make方式创建切片可以指定切片的大小和容量

—2—如果没有给切片的各个元素赋值,那么就会使用默认值(int,float=>0 string=>””,bool=>false)

—3—通过make方式创建的切片对应的数组是由make底层维护,对外不可见

(3)第三种方式

定义一个切片,直接就指定具体数组,使用原理类似make的方式。

package main

import "fmt"

func main() {
    // 第三种方式
    var strSlice []string = []string {"tom","jack","mary"}

    fmt.Println(strSlice)
    
}

这种方法没有指定cap的大小,所有与len的大小相同。

4.切片的遍历

切片的遍历和数组一样,也有两种方式。

(1)for 循环常规方式遍历

(2)for-range结构遍历切片

为上面两种方式举例:

package main

import "fmt"

func main() {
    // 使用常规的for循环遍历切片
    var arr [5]int = [...]int{10, 20, 30, 40, 50}
    slice := arr[1:4] // 第二个元素到第4个元素
    for i := 0; i < len(slice); i++ {
        fmt.Printf("i=%v v=%v\n",i,slice[i])
    }

    // 使用for--range方式遍历切片
    for i, v := range slice {
        fmt.Printf("i=%v v=%v\n", i, v)
    }
}

5.切片注意事项和细节说明

(1)切片初始化时 var slice = arr[startindex:endindex]

说明:从arr数组下标为startindex开始,取到下标为endindex的元素(不含最后那一个元素)。

(2)切片初始化时,仍然不能越界。范围在[0-len(arr)]之间,但是可以动态增长。

1)var slice = arr[0:end] 可以简写var slice = arr[:end]

2)var slice = arr[strat:len(arr)] 可以简写:var slice = arr[start:]

3) var slice = arr[0:len(arr)] 可以简写:var slice = arr[:]

(3)cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。

(4)切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make一个空间供切片来使用。

(5)切片还可以切片。

(6)切片是引用类型,所以在传递时,遵守引用传递机制。要改元素会一起变化。

举例:这个是一个原切片数据和修改后的数据都变化

package main

import "fmt"

func main() {
    var slice1 []int 
    var arr [5]int = [...]int {1,2,3,4,5} // 数组
    slice1 = arr[:]   // slice1 属于arr数组的一部分,slice2的指针和slice1存的数据一样
    var slice2 = slice1
    slice2[0] = 10
    fmt.Println(slice1)

    fmt.Println(slice2)

    fmt.Println(arr)
    // [10 2 3 4 5]
    // [10 2 3 4 5]
    // [10 2 3 4 5]

举例2:这个是一个原切片数据不变,修改后的数据变化

package main

import "fmt"

func test(slice []int) {
    slice[0] = 100 // 这里修改slice[0]
}

func main(){
    //
    var slice = []int {1,2,3,4}
    fmt.Println("slice=",slice)  // [1,2,3,4]
    test(slice)
    fmt.Println("slice=",slice)
}

6.切片中的使用方法append

用append内置函数,可以对切片进行动态追加。

切片append操作的底层原来分析:

(1)切片append操作的本质就是数组扩容

(2)go底层会创建一个新的数组newArr(安装扩容大小)

(3)将slice原来包含的元素拷贝到新的数组newArr

(4)slice重新引用到newArr(就是整个数组的指针地址变化)

(5)注意newArr是底层来维护的

举例:

package main

import "fmt"

func main(){
    // 用append 内置函数,可以对切片进行动态追加
    var slice []int = []int {100,200,300}
    fmt.Printf("slice=%v\n",slice)
    slice = append(slice,400)
    fmt.Println("new_slice=",slice)

    var demo_slice []int  = []int{1,2,3}
    // 切片追加切片
    slice = append(slice,demo_slice...)  // ... 不能忘
    fmt.Println("new_new_slice=",slice)
    
}

7.切片的拷贝操作

切片使用copy内置函数完成拷贝

copy拷贝的数据,数据空间是独立的,之间相互不影响。

如果要拷贝的切片元素个数大于新的切片的元素个数,那么只能够拷贝到新的切片的最大个数的数据到新的切片中。

举例:

package main

import "fmt"

func main(){
    var a []int = []int {1,2,3,4,5}
    var slice = make([]int, 10)  // 10个0的一个切片
    fmt.Println(slice)

    copy(slice,a)
    fmt.Println(slice)  // 将a上的元素复制到全为0的切片中
    // 切片才能进行拷贝操作
    // [0 0 0 0 0 0 0 0 0 0]
    // [1 2 3 4 5 0 0 0 0 0]

}

8.string 与slice

(1)string底层是一个byte数组,因此string也可以进行切片处理

(2)string是不可变的,也就是说不能通过str[0] = ‘z’ 方式来修改字符串的内容

(3)如果需要修改字符串,可以先将string-->[]byte再重新转回来 或者 []rune -->修改-->重写转成string (其实就相当于形成一个新的字符串)

举例:修改字符串举例

package main

import "fmt"

func main() {
    //string底层是一个byte数组 ,因此string也可以进行切片
    str := "hello_hello_hsz"
    // 使用切片获取hsz
    slice := str[12:]
    fmt.Println("slice=", slice)

    // string --> byte  转成byte数组
    arr := []byte(str)
    arr[0] = 'z'
    str = string(arr)
    fmt.Println("str=", str)

    // 可以处理英文和数字,但是不能处理中文
    // []byte 字节来处理,而一个汉字,是3个字节,因此就会出现乱码
    // 解决方法: string 转成 []rune 因此rune是按字符来计算处理的,兼容汉字
    arr1 := []rune(str)
    arr1[0] = '好'
    str = string(arr1)
    fmt.Println("str=", str)
}

9.斐波那契数列放到切片中

package main

import "fmt"

func fbn(n int) []uint64 {
    // 声明一个切片,切片大小n
    fbnSlice := make([]uint64, n)
    // 第一个数和第二个数的斐波那契为1
    if n == 1 {
        fbnSlice[0] = 1
    } else if n == 2 {
        fbnSlice[0] = 1
        fbnSlice[1] = 1
    } else {
        fbnSlice[0] = 1
        fbnSlice[1] = 1
        // 进行for循环来存放斐波那契的数列
        for i := 2; i < n; i++ {
            fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2]
        }
    }
    return fbnSlice
}

func main() {
    // 编写一个函数 fbn(n int )
    // 1. 可以接收一个 n int
    // 2.能够将斐波那契的数列放到切片中
    // 提示: slice[0] = 1  slice[1] = 1  slice[2] = 2   slice[3] = 3  slice[4] = 5
    fnbSlice := fbn(4)
    fmt.Println("fbnSlice=", fnbSlice)
}