数组相关

   在Go语言中,数组是一种容器相关的数据类型,用于存放多种相同类型的数据。

数组定义

   在定义数组时,必须定义数组的类型以及长度,数组一经定义不可进行改变。

   同时,数组的长度是按照元素个数进行统计的,并且数组长度是数组的一部分。

package main

import (
"fmt"
) func main() {
var arr [10]string // 定义一个长度为10byte的数组
fmt.Println(len(arr)) // 10 数组长度
fmt.Println(cap(arr)) // 10 数组容量
}

定义赋值

   数组可以在一经定义的时候就进行赋值,赋值的长度不可大于数组的长度。

   数组也可以不定义长度,而是使用...语法来动态计算填充的元素长度。

   数组也可以在定义的时候通过索引对其进行赋值。

   以下是定长的数组定义赋值

package main

import (
"fmt"
) func main() {
var arr = [10]string{"①", "②", "③", "④","⑤","⑥","⑦","⑧","⑨","⑩"}
fmt.Printf("数组长度:%d\n", len(arr)) // 数组长度:10
fmt.Printf("数组容量:%d\n", cap(arr)) // 数组容量:10
fmt.Printf("第一个元素:%v\n", arr[0]) // 第一个元素:①
}

   以下是不定长的数组定义赋值

package main

import (
"fmt"
) func main() {
var arr = [...]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩", "拾壹"}
fmt.Printf("数组长度:%d\n", len(arr)) // 数组长度:11
fmt.Printf("数组容量:%d\n", cap(arr)) // 数组容量:11
fmt.Printf("最后一个元素:%v\n", arr[len(arr)-1]) // 最后一个元素:拾壹
}

   以下是定长的数组索引赋值:

package main

import (
"fmt"
) func main() {
var arr = [10]string{1: "②", 8: "⑨"}
fmt.Printf("数组长度:%d\n", len(arr)) // 数组长度:11
fmt.Printf("数组容量:%d\n", cap(arr)) // 数组容量:11
fmt.Printf("数组元素:%v\n", arr) // 数组元素:[ ② ⑨ ]
}

默认填充

   当定义了一个数组并未填充数据时,会进行默认的数据填充:

   布尔值是false

   字符串是""

   整形和浮点型都是0

多维数组

   多维数组只有第一层可以使用...来让编译器推导数组长度,其他层都需要手动指定长度。

   如下定义了一个二维数组:

package main

import (
"fmt"
) func main() {
// 一维数组:不定长 二维数组:定长,2个 在一维数组中创建了3个二维数组
var arr = [...][2]string{
{"1-1", "1-2"},
{"2-1", "2-2"},
{"3-1", "3-2"},
}
fmt.Println(arr) // [[1-1 1-2] [2-1 2-2] [3-1 3-2]]
}

值类型

   数组本身是值类型的,所以当重新复制一份数组时,不会受到前数组的影响。

   这相当于深拷贝。

   如下示例,单一的数组中存入值类型,互不影响。

package main

import (
"fmt"
) func main() {
var arr = [...]int{1, 2, 3}
// 数组是值类型
newArr := arr
arr[0] = 10
fmt.Println(arr) // [10 2 3]
fmt.Println(newArr) // [1 2 3]
}

  

   示例二,二维数组的改变,也不会引发另一个数组的改变,故此说是深拷贝。

package main

import (
"fmt"
) func main() {
var arr = [...][1]int{
{10},
}
// 数组是值类型
newArr := arr
arr[0][0] = 100
fmt.Println(arr) // [[100]]
fmt.Println(newArr) // [[10]]
}

循环遍历

   可以使用for的索引循环,也可以使用range来进行数组遍历。

package main

import (
"fmt"
) func main() {
var arr = [10]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"}
// 索引循环
for index := 0; index < len(arr); index++ {
fmt.Println(index)
}
// range循环
for index,ele := range arr{
fmt.Println(index)
fmt.Println(ele)
}
}

切片相关

   切片是基于数组的一层封装,非常灵活且支持扩容。

   你可以将它当作一个更加高级的数组。

   注意:切片是引用类型,不能直接做比较,只能和nil做比较。

   nil则相当于null

切片声明

   声明切片类型的基本语法如下:

var 变量名 []切片类型

   以下是一些示例,对于单纯的创建切片来说,它没有像数组一样的容量限制,但是具有类型限制:

package main

import (
"fmt"
) func main() {
var a []string
var b = []int{}
var c = []bool{true,false}
fmt.Println(a) // []
fmt.Println(b) // []
fmt.Println(c) // [true false]
fmt.Println(a == nil) // true
}

切片引用

   如果对一个数组进行切片取值,那么切片的元素引用于原数组中的元素。

package main

import (
"fmt"
) func main() {
var arr = [...]string{"①", "②", "③", "④", "⑤"}
var arrScile = arr[1:3] // 引用数组的②③
arr[1] = "二"
arr[2] = "三"
fmt.Println(arrScile) // [二 三] 跟随发生改变
}

   这里是常见的一些切片引用的操作,需要注意的是切片引用顾头不顾尾。

a[2:]  // 等同于 a[2:len(a)]
a[:3] // 等同于 a[0:3]
a[:] // 等同于 a[0:len(a)]

长度容量

   引用切片的len()指的是切出来的元素数量。

   而cap()则是被切片的数组中,从第一个位置切的地方往后算还剩多少个元素。

   长度就是当前切片或者数组中存在的元素个数

   容量就是最多可以容纳的元素个数

package main

import (
"fmt"
) func main() {
var arr = [...]string{"①", "②", "③", "④", "⑤"}
var arrSlice = arr[1:3]
fmt.Println(len(arrSlice)) // 2
fmt.Println(cap(arrSlice)) // 4
}

   如何理解?这里有一幅图,因为有指针的关系。所以才是引用:

  

make切片

   上面的切片都是经过数组创建出来的,切片的容量不能由我们自己进行控制。

   但是使用make()的话就可以动态的创建出一个切片。

make([]类型, size切片中元素数量, cap切片容量)

   如下示例:

package main

import (
"fmt"
) func main() {
// 创建一个string类型的切片 默认存放3个元素 最大可存放5个
var slice = make([]string,3,5)
fmt.Println(slice) // [ ]
}

append

   使用内置函数append(),可以为切片添加元素。

   可以添加一个元素,也可以添加多个元素,甚至可以利用解构语法进行切片合并。

   需要注意的是,当使用append()方法后,应该用一个变量来接收它。

   以下是添加多元素的示例

package main

import (
"fmt"
) func main() {
var slice []string
slice = append(slice, "一", "二")
fmt.Println(slice) // [一 二]
}

   以下是合并两个切片的示例

package main

import (
"fmt"
) func main() {
var (
slice1 []string
slice2 []string
)
slice1 = append(slice1, "一", "二","三")
slice2 = append(slice2, "四", "五")
slice1 = append(slice1,slice2...)
fmt.Println(slice1) // [一 二 三 四 五] }

copy

   切片是引用类型,如果想将其作为值类型的传递可以用copy

   它有两个参数,分别是destSlicesrcSlice

   destSlice:目标切片

   srcSlice:数据来源切片

   注意:目标切片必须是make创建的切片,否则将会失败

package main

import (
"fmt"
) func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 5, 5)
// 目标切片 数据来源切片
copy(slice2, slice1)
slice1[0] = 100
fmt.Println(slice1) // [100 2 3 4 5]
fmt.Println(slice2) // [1 2 3 4 5]
}

删除元素

   切片中没有提供任何删除元素的方法,但是可以利用append()返回新切片的特性,来达到删除元素的目的。

package main

import (
"fmt"
) func main() {
slice1 := []int{1, 2, 3, 4, 5}
// 删除3
slice1 = append(slice1[:2],slice1[3:]...)
fmt.Print(slice1) // [1 2 4 5]
}

扩容行为

   切片不需要指定容量,它会自动进行扩容。如下示例:

func main() {
var a []string fmt.Println(cap(a)) // 0 初始值
a = append(a,"1")
fmt.Println(cap(a)) // 1
a = append(a,"2")
fmt.Println(cap(a)) // 2
a = append(a,"3")
fmt.Println(cap(a)) // 4
a = append(a,"4")
fmt.Println(cap(a)) // 4
a = append(a,"5")
fmt.Println(cap(a)) // 8
a = append(a,"6")
fmt.Println(cap(a)) // 8
a = append(a,"7")
fmt.Println(cap(a)) // 8
a = append(a,"8")
fmt.Println(cap(a)) // 8
}

   可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}

   从上面的代码可以看出以下内容:

  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

   需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如intstring类型的处理方式就不一样。

   关于上面的示例,其实只看前两个判断就够了。因为旧的容量没有超过1024

循环遍历

   可以使用for的索引循环,也可以使用range来进行切片遍历。

package main

import (
"fmt"
) func main() {
var arr = []string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"}
// 索引循环
for index := 0; index < len(arr); index++ {
fmt.Println(index)
}
// range循环
for index,ele := range arr{
fmt.Println(index)
fmt.Println(ele)
}
}

Go 数组&切片的更多相关文章

  1. 窥探Swift之数组安全索引与数组切片

    今天是元宵节,祝大家元宵节快乐!在Swift中的数组和字典中下标是非常常见的,数组可以通过索引下标进行元素的查询,字典可以通过键下标来获取相应的值.在使用数组时,一个常见的致命错误就是数组越界.如果在 ...

  2. golang中不定参数与数组切片的区别

    package main import "fmt" func main() { myfunc1(, , , ) //传递不定数量的参数 myfunc2([], , , }) //传 ...

  3. go语言 类型:数组切片

    初看起来,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针.数组切片的数据结构可以抽象为以下3个变量: 1.一个指向原生数组的指针: 2.数组切片中的元素个数: 3.数组切 ...

  4. go语言中的数组切片:特立独行的可变数组

    go语言中的数组切片:特立独行的可变数组 初看go语言中的slice,觉得是可变数组的一种很不错的实现,直接在语言语法的层面支持,操作方面比起java中的ArrayList方便了许多.但是在使用了一段 ...

  5. GO中的数组切片

    GO中的数组切片可以看做是功能更强大的数组,在append数据时,可以自动调整内存大小以适应数据实际大小,有些类似于C#中的List<T>. GO 中数组切片的“容量”与实际储存的大小可以 ...

  6. Go语言学习之4 递归&闭包&数组切片&map&锁

    主要内容: 1. 内置函数.递归函数.闭包2. 数组与切片3. map数据结构4. package介绍 5. 排序相关 1. 内置函数.递归函数.闭包 1)内置函数 (1). close:主要用来关闭 ...

  7. go递归函数如何传递数组切片slice

    数组切片slice这个东西看起来很美好,真正用起来会发现有诸多的不爽. 第一,数组.数组切片混淆不清,使用方式完全一样,有时候一些特性又完全不一样,搞不清原理很容易误使用. 第二,数组切片的appen ...

  8. [golang note] 数组切片

    数组 √ golang数组包含的每个数据称为数组元素(element),数组包含的元素个数被称为数组长度(length). √ golang数组的长度在定义后不可更改,并且在声明时可以是一个常量或常量 ...

  9. [翻译] NumSharp的数组切片功能 [:]

    原文地址:https://medium.com/scisharp/slicing-in-numsharp-e56c46826630 翻译初稿(英文水平有限,请多包涵): 由于Numsharp新推出了数 ...

  10. GO 语言学习笔记--数组切片篇

    1.对于make 数组切片,长度和容量需要理解清楚: 容量表示底层数组的大小,长度是你可以使用的大小: 容量的用处在哪?在与当你用 appen d扩展长度时,如果新的长度小于容量,不会更换底层数组,否 ...

随机推荐

  1. 数字电路基础(二)TTL与非门输入端悬空和接大电阻的问题

    引言 我们在做那些判断与非门输入输出的时候,常常把输入端悬空和接大电阻作为高电平输入处理,比如下边这一例题: 很显然,我们无法直接从与非门逻辑图中看出其内部工作原理,那我们该如何分析呢?那肯定是去分析 ...

  2. python小白入门基础(一:注释)

    # 注释:就是对代码的解释,方便大家阅读代码.注释后的代码程序不会执行.# 注释的分类:单行注释和多行注释# (1)单行注释# 在代码前面加个#字符print("hello world&qu ...

  3. Java垃圾回收略略观

    本文主要介绍Java垃圾回收(Garbage Collection),90%干货,文字颇多,需要耐心一点看. [对象判断状态算法] ------引用计数法 在创建对象时,为对象创建一个伴生的引用计数器 ...

  4. ctf古典密码从0到

    本文首发于“合天智汇”公众号 作者:淡灬看夏丶恋雨 古典密码和现代密码的区别: 代换密码 单表代换密码 字符或数学型 凯撒密码 仿射密码 四方密码 培根密码 图表 标准银河字母 圣堂武士密码 猪圈密码 ...

  5. 你不得不知的Java基础知识

    本篇博客主要记录Java中面向对象的概念和Java语法的基础知识. 面向对象 什么是面向对象 面向对象是一种优秀的软件设计思想,是相对于面向过程.面向切面等设计思想的一种软件设计理念.它的核心思想是运 ...

  6. Centos7安装后进不去,死活就要填licence,该怎么办?

    遇到这个问题不要麻爪,跟着我做: 1 回车 2 回车 c 回车 c 回车 然后就进入系统了. 要使它联网,点右上角的开关按钮,将PCI Ethernet选择为connect状态. 我的centos7是 ...

  7. roarctf_2019_easy_pwn

    这篇博客主要记录当直接在malloc_hook中直接写入one_gadget不起作用时的一些处理方法.题目附件:https://buuoj.cn/challenges#roarctf_2019_eas ...

  8. if __name__ == ‘__main__‘

    if __name__ == '__main__': def_test() 作为程序的入口,当函数被调用时会从此处开始运行 如被导入的模块内没写 if __name__ == '__main__',则 ...

  9. Readme for Software engineering

    作业任务: 软件工程 软件工程 作业要求 作业要求 作业目标 博客园.github注册 自我介绍 软工5问 自我介绍: 广东工业大学计算机学院18级信息安全二班 广东工业大学AD攻防工作室成员& ...

  10. GaussDB(DWS)应用实战:对被视图引用的表进行DDL操作

    摘要:GaussDB(DWS)是从Postgres演进过来的,像Postgres一样,如果表被视图引用的话,特定场景下,部分DDL操作是不能直接执行的. 背景说明 GaussDB(DWS)是从Post ...