💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## 内部实现 数组是 **元素类型相同** 且**长度固定** 的存储在内存中 数据类型。长度固定,是数组和切片最明显的区别。 数组存储的类型可以是内置类型,比如整型或者字符串,也可以是自定义的数据结构。 这里的索引,一直都是0,1,2,3这样的,因为其元素类型相同,我们也可以使用反射,获取类型占用大小,进行移位,获取相应的元素。 ## 概念 * 数组长度是数组类型的一部分 * 数组在go中为值类型 * 数组的指针和指针数组 * 指针数组指的是数组的元素是指针 * 数组的指针 * new方法创建指向数组的指针 * 占用连续的内存,索引访问的效率非常高 ## 声明和初始化 ### 声明原则 1. 指明存储的 *元素的数量* 2. 指明存储的 *数据的类型* ~~~ var <varname> [n]<type>, n>0 ~~~ >[info] 以上我们声明了一个数组array,但是还没有对他进行初始化,这时候数组array里面的值,是对应元素类型的零值,也就是说,现在这个数组是5个0 >数组一旦声明后,其元素类型和大小都不能变了,如果还需要存储更多的元素只能通过创建一个新的数组,然后把原来数组的数据复制过去 ### 初始化 刚刚声明的数组已经被默认的元素类型零值初始化了,可以采用如下办法再次进行初始化: ~~~ var array [5]int array = [5]int{1,2,3,4,5} 或者 var array [5]int = [5]int{1,2,3,4,5} ~~~ 这两步比较繁琐,Go为我们提供了`:=`操作符,可以让我们在创建数组的时候直接初始化。 ~~~ array := [5]int{1,2,3,4,5} ~~~ >[info]这种简短变量声明的方式不仅适用于数组,还适用于任何数据类型,这也是Go语言中常用的方式。 ### 自动计算数组的长度 有时候我们更懒,连数组的长度都不想指定,不过没有关系,使用...代替就好了,Go会自动推导出数组的长度。 ~~~ array := [...]int{1,2,3,4,5} ~~~ 假如我们只想给索引为1和3的数组初始化相应的值,其他都为0怎么做呢,直接的办法有: ~~~ array := [5]int{0,1,0,4,0} ~~~ 还有一种更好的办法,上面讲默认初始化为零值,那么我们就可以利用这个特性,只初始化索引1和3的值: ~~~ array := [5]int{1:1,3:4} ~~~ ## 使用数组 ### 访问元素 数组的访问非常简单,通过索引即可,操作符为`[]`。 ~~~ array := [5]int{1:1,3:4} fmt.Printf("%d",array[1]) ~~~ ### 修改元素 ~~~ array:=[5]int{1:1,3:4} fmt.Printf("%d\n",array[1]) array[1] = 3 fmt.Printf("%d\n",array[1]) ~~~ ### 遍历数组 传统方法 ~~~ func main() { array := [5]int{1: 1, 3: 4} for i := 0; i < 5; i++ { fmt.Printf("索引:%d,值:%d\n", i, array[i]) } } ~~~ ### 遍历数组 go风格方法 使用for range 循环 ~~~ func main() { array := [5]int{1: 1, 3: 4} for i, v := range array { fmt.Printf("索引:%d,值:%d\n", i, v) } } ~~~ ## 二维数组 ### 声明 ``` var <varname> [m][n]<type>, n>0 ``` ### 声明并定义 ``` var <varname> [m][n]<type> = {{x,x,x,x,x},{x,x,x,x,x}} ``` > len(array) 行数 > len(array[0]) 列数 ### 同样类型的数组 同样类型的数组是可以相互赋值的,不同类型的不行,会编译错误。那么什么是呢?Go语言规定,必须是**长度一样**,并且每个**元素的类型一样**的数组,才是同样类型的数组。 ~~~ array := [5]int{1: 1, 3: 4} var array1 [5]int = array //success var array2 [4]int = array1 //error ~~~ ## 指针数组 和数组本身差不多,只不过元素类型是指针。 ~~~ array := [5]*int{1: new(int), 3:new(int)} ~~~ 这样就创建了一个指针数组,并且为索引1和3都`创建了内存空间`,其他索引是指针的零值nil,这时候我们要修改指针变量的值也很简单,如下即可: ~~~ array := [5]*int{1: new(int), 3:new(int)} *array[1] = 1 ~~~ >[danger]以上需要注意的是,只可以给索引1和3赋值,因为只有它们分配了内存,才可以赋值,如果我们给索引0赋值,运行的时候,会提示无效内存或者是一个nil指针引用。 ~~~ panic: runtime error: invalid memory address or nil pointer dereference ~~~ 要解决这个问题,我们要通过`array[0] =new(int)`给索引0分配内存 ,然后再进行赋值修改。 ~~~ array := [5]*int{1: new(int), 3:new(int)} array[0] =new(int) *array[0] = 2 fmt.Println(*array[0]) ~~~ ## 函数间传递数组 数组是值传递。 ~~~ func main() { array := [5]int{1: 2, 3:4} modify(array) fmt.Println(array) # 值不变 } func modify(a [5]int){ a[1] = 3 fmt.Println(a) } ~~~ ### 数组的指针传递 ~~~ func modify(a *[5]int){ (*a)[1] = 3 arr[3] = 4 //两种格式 } func main() { array := [5]int{1: 2, 3:4} modify(&array) fmt.Println(array) } ~~~ 这是传递数组的指针的例子,会发现数组被修改了。所以这种情况虽然节省了复制的内存,但是要谨慎使用,因为一不小心,就会修改原数组,导致不必要的问题。 >[info]也可以通过函数的返回值实现数组的修改 >[danger]这里注意,数组的指针和指针数组是两个概念,数组的指针是`*[5]int`,指针数组是`[5]*int`,注意*的位置。 经常会见到: `p , *p , &p` 三个符号 ~~~ p是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数。 *p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。 &是取地址运算符,&p就是取指针p的地址。 等会,怎么又来了个地址,它到底和p有什么区别?区别在于,指针p同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,就像程序中定义了一个int型的变量i,编译器要为其分配一块内存空间一样。而&p就表示编译器为变量p分配的内存地址,而因为p是一个指针变量,这种特殊的身份注定了它要指向另外一个内存地址,程序员按照程序的需要让它指向一个内存地址,这个它指向的内存地址就用p表示。而且,p指向的地址中的内容就用*p表示。 ~~~ ## 练习 用切片实现斐波那契数列 ### 冒泡排序 ~~~ func Bubble() { var array [7]int = [7]int{7, 5, 1, 9, 0, 2, 8} for i := 0; i <len(array); i++ { for j := 0; j <len(array) - i - 1; j++ { if array[j] > array[j+1] { array[j], array[j+1] = array[j+1], array[j] } } } fmt.Println(array) } ~~~ 数组的比较 长度相同可以比较