第5篇:复合数据类型之数组

基础数据类型

  • 整型,用于表示整数
  • 浮点型,用于表示小数
  • 复数,用来表示复数
  • 布尔型,用于表示真/假
  • 字符串,用于表示文本信息

复合数据类型

  • 数组,用于表示多个数据(数据集合)
  • 结构体,用于自定义一些数据集合和方法

引用数据类型

  • 指针,用于表示内存地址的类型
  • 切片,用于表示多个数据(数据集合)
  • 字典,用于表示键值对集合
  • 函数,用于将一个语句序列打包为一个单元,然后可以从程序中其它地方多次调用
  • 通道,channel,是goroutine中的通信机制

接口数据类型

  • 接口,用于约束和泛指数据类型

上一节我们讨论了基本数据类型,它们可以用于构建程序中数据的结构,是Go语言世界的原子。在接下来的几节,我们主要讨论四种类型:数组、slice、map和结构体。同时,我们将演示如何使用结构体来解码和编码到对应JSON格式的数据,并且通过结合使用模板来生成HTML页面。

数组和结构体是复合(聚合)类型;它们的值由许多元素或成员字段的值组成。数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。

一、数组的定义

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice(切片),它是可以增长和收缩的动态序列,slice功能也更灵活,但是要理解slice工作原理的话需要先理解数组。

数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。

Golang Array的特点

  • 数组:是同一种数据类型的固定长度的序列。

  • 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。

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

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

    for i := 0; i < len(a); i++ {
    }
    for index, v := range a {
    }
  • 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic

  • 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。

  • 支持 “==”、”!=” 操作符,因为内存总是被初始化过的

  • 指针数组 [n]*T,数组指针 *[n]T。

二、数组的初始化

一维数组

// 方式一:先声明再赋值(声明时内存中已开辟空间,内存初始化的值是0)
var numbers [3]int
numbers[0] = 999
numbers[1] = 666
numbers[2] = 333

// 方式二:声明+赋值
var names = [2]string{"乔丹","科比"}

// 方式三:声明+赋值 + 指定位置
var ages = [3]int{0:87,1:73,2:99}

// 方式四:省略个数
var names = [...]string{"刘备","诸葛亮"}
var ages = [...]int{0:87, 2:99}

// 声明 指针类型的数组(指针类型),不会开辟内存初始化数组中的值,numbers = nil
var numbers *[3]int

// 声明数组并初始化,返回的是 指针类型的数组(指针类型)
numbers := new([3]int)

代码:

package main

import (
    "fmt"
)

var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}

func main() {
    a := [3]int{1, 2}           // 未初始化元素值为 0。
    b := [...]int{1, 2, 3, 4}   // 通过初始化值确定数组长度。
    c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
    d := [...]struct {
        name string
        age  uint8
    }{
        {"user1", 10}, // 可省略元素类型。
        {"user2", 20}, // 别忘了最后一行的逗号。
    }
    fmt.Println(arr0, arr1, arr2, str)
    fmt.Println(a, b, c, d)
}

输出结果:

[1 2 3 0 0] [1 2 3 4 5] [1 2 3 4 5 6] [   hello world tom]
[1 2 0] [1 2 3 4] [0 0 100 0 200] [{user1 10} {user2 20}]

多维数组

示例1

package main

import "fmt"

func main() {
    var nestData1 [3]int
    fmt.Println(nestData1)  // [0 0 0]

    var nestData2 [3][2][3]int  
    fmt.Println(nestData2)  //  [[[-1 0 0] [0 0 0]] [[0 0 0] [0 0 0]] [[0 0 0] [0 0 0]]]
    //[  [0,0,0],[0,0,0]  ]
    var nestData3 [2][3]int
    nestData3[0] = [3]int{11, 22, 33}
    nestData3[1][1] = 666
    fmt.Println(nestData3)  // [[11 22 33] [0 666 0]]

    nestData4 := [2][3]int{[3]int{11, 22, 33}, [3]int{44, 55, 66}}
    fmt.Println(nestData4)  // [[11 22 33] [44 55 66]]
}

示例2

package main

import (
    "fmt"
)

var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

func main() {
    a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
    fmt.Println(arr0, arr1)
    fmt.Println(a, b)
}

输出结果:

[[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] [[1 2 3] [7 8 9]]
[[1 2 3] [4 5 6]] [[1 1] [2 2] [3 3]]

值拷贝行为会造成性能问题,通常会建议使用 slice,或数组指针。

package main

import (
    "fmt"
)

func test(x [2]int) {
    fmt.Printf("x: %p\n", &x)
    x[1] = 1000
}

func main() {
    a := [2]int{}
    fmt.Printf("a: %p\n", &a)

    test(a)
    fmt.Println(a)
}

输出结果:

a: 0xc42007c010
x: 0xc42007c030
[0 0]

内置函数 lencap 都返回数组长度 (元素数量)。

package main

func main() {
    a := [2]int{}
    println(len(a), cap(a)) 
}

输出结果:

2 2

多维数组遍历

package main

import (
    "fmt"
)

func main() {

    var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

    for k1, v1 := range f {
        for k2, v2 := range v1 {
            fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
        }
        fmt.Println()
    }
}

输出结果:

(0,0)=1 (0,1)=2 (0,2)=3 
(1,0)=7 (1,1)=8 (1,2)=9

三、数组内存管理

数组内存管理

数组,定长且元素类型一致的数据集合。

必备知识点:

  • 数组的内存是连续的。

  • 数组的内存地址实际上就是数组第一个元素的内存地址。

  • 每个字符串的内部存储:len + str

    type stringStruct struct {
        str unsafe.Pointer
        len int
    }

示例1:

package main

import "fmt"

func main() {
    nums := [3]int8{11, 22, 33}

    fmt.Printf("数组的内存地址:%p \n", &nums)
    fmt.Printf("数组第1个元素的内存地址:%p \n", &nums[0])
    fmt.Printf("数组第2个元素的内存地址:%p \n", &nums[1])
    fmt.Printf("数组第3个元素的内存地址:%p \n", &nums[2])
}

>>> 输出
数组的内存地址:0xc00001604a 
数组第1个元素的内存地址:0xc00001604a 
数组第2个元素的内存地址:0xc00001604b 
数组第3个元素的内存地址:0xc00001604c 

示例2:

package main

import "fmt"

func main() {
    nums := [3]int32{11, 22, 33}

    fmt.Printf("数组的内存地址:%p \n", &nums)
    fmt.Printf("数组第1个元素的内存地址:%p \n", &nums[0])
    fmt.Printf("数组第2个元素的内存地址:%p \n", &nums[1])
    fmt.Printf("数组第3个元素的内存地址:%p \n", &nums[2])
}

>>> 输出
数组的内存地址:0xc0000b4004 
数组第1个元素的内存地址:0xc0000b4004 
数组第2个元素的内存地址:0xc0000b4008 
数组第3个元素的内存地址:0xc0000b400c 

示例3:

package main

import "fmt"

func main() {
    names := [2]string{"乔丹", "科比"}
    fmt.Printf("数组的内存地址:%p \n", &names)
    fmt.Printf("数组第1个元素的内存地址:%p \n", &names[0])
    fmt.Printf("数组第2个元素的内存地址:%p \n", &names[1])

}

>>> 输出:
数组的内存地址:0xc000128020 
数组第1个元素的内存地址:0xc000128020 
数组第2个元素的内存地址:0xc000128030

四、可变和拷贝

可变,数组的元素可以被更改(长度和类型都不可以修改)。

names := [2]string{"乔丹", "科比"}
names[1] = "詹姆斯"

注意:字符串不可以被修改。

拷贝,变量赋值时重新拷贝一份。

name1 := [2]string{"乔丹", "科比"}
name2 := name1

name1[1] = "詹姆斯"

fmt.Println(name1,name2)   // [乔丹 科比]   [詹姆斯 科比]

拷贝示例

package main

import "fmt"

func printArr(arr [5]int) {
    arr[0] = 10
    for i, v := range arr {
        fmt.Println(i, v)
    }
}

func printArr2(arr *[5]int) {
    arr[0] = 10
    for i, v := range arr {
        fmt.Println(i, v)
    }
}

func main() {
    var arr1 [5]int
    printArr(arr1)
    fmt.Println(arr1) // 值拷贝
    arr2 := [...]int{2, 4, 6, 8, 10}
    printArr2(&arr2)
    fmt.Println(arr2)  // 引用拷贝
}

输出

0 10
1 0
2 0
3 0
4 0
[0 0 0 0 0]
0 10
1 4
2 6
3 8
4 10
[10 4 6 8 10]

五、长度索引切片和循环

package main

import "fmt"

func main() {
    // 1. 长度
    //name := [2]string{"乔丹", "科比"}
    //fmt.Println(len(name))

    // 2. 索引
    //name := [2]string{"乔丹", "科比"}
    //data := name[0]
    //fmt.Println(data)
    //name[0] = "eric"
    //fmt.Println(name)

    // 3. 切片
    //nums := [3]int32{11, 22, 33}
    //data := nums[0:2] // 获取   0 <= 下标 < 2
    //fmt.Println(data)

    // 4. 循环
    //nums := [3]int32{11, 22, 33}
    //for i:=0;i<len(nums);i++{
    //    fmt.Println(i, nums[i] )
    //}

    // 5.for range 循环
    nums := [3]int32{11, 22, 33}
    for key, item := range nums {
        fmt.Println(key, item)
    }

    for key := range nums {
        fmt.Println(key)
    }

    for _,item := range nums {
        fmt.Println(item)
    }

}

六、数组练习

练习1

求数组所有元素之和

参考代码

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// 求元素和
func sumArr(a [10]int) int {
    var sum int = 0
    for i := 0; i < len(a); i++ {
        sum += a[i]
    }
    return sum
}

func main() {
    // 若想做一个真正的随机数,要种子
    // seed()种子默认是1
    //rand.Seed(1)
    rand.Seed(time.Now().Unix())

    var b [10]int
    for i := 0; i < len(b); i++ {
        // 产生一个0到1000随机数
        b[i] = rand.Intn(1000)
    }
    sum := sumArr(b)
    fmt.Printf("sum=%d\n", sum)
}

练习2

找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],找出两个元素之和等于8的下标分别是(0,4)和(1,2)

参考代码

package main

import "fmt"

//    找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],
// 找出两个元素之和等于8的下标分别是(0,4)和(1,2)

// 求元素和,是给定的值
func myTest(a [5]int, target int) {
    // 遍历数组
    for i := 0; i < len(a); i++ {
        other := target - a[i]
        // 继续遍历
        for j := i + 1; j < len(a); j++ {
            if a[j] == other {
                fmt.Printf("(%d,%d)\n", i, j)
            }
        }
    }
}

func main() {
    b := [5]int{1, 3, 5, 8, 7}
    myTest(b, 8)
}

文章作者: 张亚飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张亚飞 !
评论
 上一篇
第6篇:引用数据类型之切片 第6篇:引用数据类型之切片
Slice(切片)代表变长的序列,可以理解为动态数组,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
2020-12-27
下一篇 
第4篇:基础数据类型 第4篇:基础数据类型
基础数据类型 整型,用于表示整数。 浮点型,用于表示小数。 复数,用来表示复数。 布尔型,用于表示真/假。 字符串,用于表示文本信息。 复合数据类型 数组,用于表示多个数据(数据集合) 结构体,用于自定义一些数据集合和方法。 引用数
2020-12-26
  目录