莱州市双语网站,物联网和互联网的区别,文化网站前置审批,旅游营销推广方案文章目录 数组和切片数组:one: 数组初始化:two: 数组的遍历:three: 多维数组:four: 将数组传递给函数 切片(Slice):one: 切片的初始化:star: new和make区别 :two: 切片的使用:three: 将切片传递给函数:four: 多维切片:four: Bytes包:four: 切片和垃圾回收 #x1f4c5; 2024年… 文章目录 数组和切片数组:one: 数组初始化:two: 数组的遍历:three: 多维数组:four: 将数组传递给函数 切片(Slice):one: 切片的初始化:star: new和make区别 :two: 切片的使用:three: 将切片传递给函数:four: 多维切片:four: Bytes包:four: 切片和垃圾回收 2024年4月28日 使用版本为1.21.5
数组和切片
⭐️ 在go语言中数组和切片看起来几乎一模一样区别在于数组是不可变扩容的切片是可变可伸缩(在Java中想数组和列表(ArrayList)) 数组
⭐️ 在go中数组只能是基本类型不能是引用类型(在Java中两者都可以)
⭐️ 由于数组是固定大小的如果你知道了你要存放数据的长度且以后不会有扩容了就可以考虑使用数组
⭐️ Go语言的数组是一种值类型不是指针类型
1️⃣ 数组初始化
⭐️ 由于数组在声明后大小就不能变声明大小时可以使用常量来替换
func main() {var a 1;const b 1;var aa [a]int; //报错a是变量var bb [b]int;
}⭐️ 当然和其他语言一样它还可以声明并赋值下标也是从0开始的可以通过下标来范围和其他语言类似
func main() {const b 3var bb [b]int{1,2,3}println(bb[0])
}
⭐️ 可以通过len来访问数组中元素的数量还可以通过cap来查看数组的容量
⭐️ 如果想要创建一个指针类型的数组就可以用到new
数值类型数组赋值改变另外一个数组不会影响赋值的数组
func main() {a : [5]int{1, 2, 3, 4, 5}b : ab[2] 100fmt.Println(a)fmt.Println(b)
}//输出
[1 2 3 4 5]
[1 2 100 4 5]
指针类型相反
func main() {a : new([5]int)b : ab[2] 100fmt.Println(a)fmt.Println(b)
}//输出
[0 0 100 0 0]
[0 0 100 0 0]⭐️ 还可以通过切割来访问数组的某一段数值和其他语言差不多
arr[1:3]2️⃣ 数组的遍历
⭐️ 可以使用for和for-range来遍历数组
func main() {a : []int{1, 2, 3, 4, 5}for i : 0; i len(a); i {fmt.Printf(%d , i)}println()for i, v : range a {fmt.Printf(%d %d\n, i, v)}
}3️⃣ 多维数组
⭐️ 想像成阵列就好了
func main() {var a [10][4]intfor i : 0; i len(a); i {for j : 0; j len(a[i]); j {a[i][j] i j}}
}⭐️ 还可以使用for-range
func main() {var a [10][4]intfor row : range a {for column : range a[0] {a[row][column] 1;}}
}4️⃣ 将数组传递给函数
第一个大数组传递给函数会消耗很多内存使用下面的方法可以避免:
可以像C语言那样使用数组的指也可以使用切片学到切片再说
package mainimport fmtfunc main() {var a [10][4]intfuncName(a)
}func funcName(a *[10][4]int) {for i : 0; i len(a); i {for j : 0; j len(a[i]); j {fmt.Printf(a[%d][%d] , i, j)}}
}
切片(Slice)
⭐️ 切片在Go中使用的更加广泛由于它可变成的特性可以使用它来频繁的插入和删除元素
⭐️ 一个切片是对底层数组的一个连续片段的引用因此它被视为引用类型。这种结构设计使得多个切片能够共享相同的底层数组内存即使这些切片表达的是数组的不同部分。当通过一个切片对底层数组进行修改时所有指向同一底层数组的其他切片都会看到这些更改。这是因为切片并没有复制底层数组的元素而只是提供了一种访问和操作这些元素的方式有点像java数组和ArrayList的关系
⭐️ 一个切片由三个部分组成指针、长度、容量指针指向第一个切片元素对于的底层数组元素地址但是并不一定就是数组的一个元素
⭐️ 优点 因为切片是引用所以它们不需要使用额外的内存并且比使用数组更有效率所以在 Go 代码中 切片 比数组更常用
1️⃣ 切片的初始化
⭐️ 切片的初始化和数组类似但是切片可以使用make关键字来创建也推荐使用make来创建切片make函数接收三个参数类型长度容量容量一定是会大于或者等于长度的就像一个桶子里面水的高度不会大于桶子的高度 ⭐️ 可以直接通过一个数组来创建切片Slice
arr1[0:5]表示此数组从0到5(不包含5)被切成了一个切片5就是此切片的len
如果是arr1[0:5:5] 前两个数和之前提到的一样但是最后这个5-0就表示cap
func main() {arr1 : [10]int{1,2,3,4,5,6,7,8,9,10}var slice1 []int arr1[0:5] //通过数组的切片创建fmt.Println(slice1)
}⭐️ 多个切片如果表示同一个数组的片段它们可以共享数据因此一个切片和相关数组的其他切片是共享存 储的相反不同的数组总是代表不同的存储。数组实际上是切片的构建块
func main() {arr1 : [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}var slice1 []int arr1[0:5] //通过数组的切片创建var slice2 []int arr1[0:5]slice2[0] 100 //修改切片2的0下标元素fmt.Println(slice1[0]) //发现切片1的也修改了
}//输出:
100此时如果你想要扩容就可以使用切片的切片操作来扩容 然后当执行 slice1 slice1[0:6] 时Go 语言会检查新切片所需的容量是否超过了现有切片的容量。在这个例子中新切片需要的容量是6而现有切片的容量是5。由于6大于5Go 语言会分配一个新的底层数组来容纳这6个元素并将新切片的指针指向这个新数组。然后它会复制原切片中的元素到新数组中并更新新切片的长度和容量。 因此尽管最初的切片 slice1 的容量只有5但是在执行 slice1 slice1[0:6] 之后slice1 被重新分配了一个更大的底层数组其容量至少为6以容纳新的切片范围。这就是为什么 slice1 可以扩展到 0:6 的原因 func main() {arr1 : [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}var slice1 []int arr1[0:5] //通过数组的切片创建fmt.Println(slice1) //输出[1 2 3 4 5]slice1 slice1[0:6] //切片的切片操作扩容fmt.Println(slice1) //输出[1 2 3 4 5 6]
}⭐️ 通过var nums []int这种方式声明的切片默认值为nil所以不会为其分配内存而在使用make进行初始化时建议预分配一个足够的容量可以有效减少后续扩容的内存消耗
//源代码// make内置函数分配并初始化一个类型的对象
// slice、map或chan(仅限)和new一样第一个参数是类型而不是
/ /值。与new不同make的返回值类型与its相同
//参数而不是指针。结果的具体情况取决于
//类型:
//
// slice: size指定了长度。切片的容量为
//等于它的长度可以向提供第二个整数参数
//指定不同的容量;它必须不小于
/ /长度。例如make([]int, 0, 10)会分配一个底层数组
//返回长度为0、容量为10的切片
//由底层数组支持。
// map:一个空的map被分配了足够的空间来容纳
//指定元素的数量在这种情况下大小可以省略
//分配一个较小的起始长度。
// channel:使用指定的参数初始化channel的缓冲区
//缓存容量。如果为零或大小省略则通道为
/ /无缓冲的。
func make(t Type, size ...IntegerType) Type⭐️ 你使用make函数创建一个切片时如果指定了长度第二个参数那么切片将会被初始化为该长度并且每个元素都会被初始化为该类型对应的零值。对于整数类型如int零值就是0。
func main() {var a make([]int, 1, 10)println(a[0]) //输出0
}⭐️ 切片的长度代表着切片中元素的个数切片的容量代表着切片总共能装多少个元素切片与数组最大的区别在于切片的容量会自动扩张而数组不会
⭐️ 和数组一样也可以使用new来创建数组
func main() {var a make([]int, 10)var b new([10]int)[0:10]fmt.Printf(%T, b)fmt.Printf(%T, a)
}⭐️ new和make区别 new(T) 为每个新的类型T分配一片内存初始化为 0 并且返回类型为*T的内存地址这种方法 返回一个指向类型为 T值为 0 的地址的指针它适用于值类型如数组和结构体它相当于 T{} 。 make(T) 返回一个类型为 T 的初始值它只适用于3种内建的引用类型切片、map 和 channel
2️⃣ 切片的使用
⭐️ 切片的基本使用与数组完全一致区别只是切片可以动态变化长度
⭐️ 通过append来对切片实行操作
在尾部添加元素
func main() {var a make([]int, 0, 0) //创建一个空切片a append(a, 1, 2, 3, 4, 5, 6, 7) //添加元素fmt.Println(len(a), cap(a)) //输出内部元素个数和切片的容量
}//输出78新 slice 预留的 buffer容量 大小是有一定规律的。 在golang1.18版本更新之前网上大多数的文章都是这样描述slice的扩容策略的 当原 slice 容量小于 1024 的时候新 slice 容量变成原来的 2 倍原 slice 容量超过 1024新 slice 容量变成原来的1.25倍。 在1.18版本更新之后slice的扩容策略变为了 当原slice容量(oldcap)小于256的时候新slice(newcap)容量为原来的2倍原slice容量超过256新slice容量newcap oldcap(oldcap3*256)/4 在头部插入元素
⭐️ ...操作符被称为扩展操作符spread operator用于将切片或数组展开为多个单独的参数。
func main() {var a make([]int, 0, 0) //创建一个空切片a append(a, 1, 2, 3, 4, 5, 6, 7) //添加元素a append([]int{-1, a}, a...) //这个a会转换成unicode代码中的值97fmt.Println(a) //输出数组 [-1 97 1 2 3 4 5 6 7]fmt.Println(len(a), cap(a)) //输出内部元素个数和切片的容量
}从中间插入
1.a[:i1]: 这部分表示从切片a的开始到索引i1处的元素即保留原始切片的前i1个元素。
2.[]int{999,888}: 这是一个包含两个整数999和888的切片这是要插入的元素。
3.a[i1:]...: 这部分表示从切片a的索引i1处开始到末尾的所有元素采用...的方式表示这些元素将被展开并追加到前面的切片组合之后。
4.append(...)将前面提到的三个切片部分组合起来并执行append操作将所有元素添加到一起最终生成一个新的切片。
5.a ...: 将新生成的切片赋值回原始切片a完成插入操作并修改原始切片。
通过这种方式切片a在索引i处被扩展其中插入了两个新的整数值999和888。func main() {i : 3var a make([]int, 0, 0) //创建一个空切片a append(a, 1, 2, 3, 4, 5, 6, 7) //添加元素a append(a[:i1], append([]int{999, 888}, a[i1:]...)...) //使用i当索引在索引中间开辟两个空间来插入来插入元素fmt.Println(a) //输出数组fmt.Println(len(a), cap(a)) //输出内部元素个数和切片的容量
}删除元素
nums : []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
//从头部删除n个元素
nums nums[n:]
fmt.Println(nums) //n3 [4 5 6 7 8 9 10]//从尾部删除n给元素
nums nums[:len(nums)-n]
fmt.Println(nums) //n3 [1 2 3 4 5 6 7]//从中间指定下标删除
nums append(nums[:i], nums[in:]...)
fmt.Println(nums)// i2n3[1 2 6 7 8 9 10] 将切片 b 的元素追加到切片 a 之后 a append(a, b…)复制切片 a 的元素到新的切片 b 上 b make([]T, len(a)) copy(b, a)删除位于索引 i 的元素 a append(a[:i], a[i1:]…)切除切片 a 中从索引 i 至 j 位置的元素 a append(a[:i], a[j:]…)为切片 a 扩展 j 个元素长度 a append(a, make([]T, j)…)在索引 i 的位置插入元素 x a append(a[:i], append([]T{x}, a[i:]…)…)在索引 i 的位置插入长度为 j 的新切片 a append(a[:i], append(make([]T, j), a[i:]…)…)在索引 i 的位置插入切片 b 的所有元素 a append(a[:i], append(b, a[i:]…)…)取出位于切片 a 最末尾的元素 x x, a a[len(a)-1], a[:len(a)-1]将元素 x 追加到切片 a a append(a, x) 3️⃣ 将切片传递给函数
⭐️ 前面说过传递数组你可以使用切片来提高程序运行效率
⭐️ 如果你有一个函数需要对数组做操作你可能总是需要把参数声明为切片。当你调用该函数时把数组分 片创建为一个切片引用并传递给该函数 var slice1 []type arr1[:] 那么 slice1 就等于完整的 arr1 数组所以这种表示方式是 arr1[0:len(arr1)] 的一种缩写。另外一种表述方式是 slice1 arr1 。 arr1[2:] 和 arr1[2:len(arr1)] 相同都包含了数组从第二个到最后的所有元素。 arr1[:3] 和 arr1[0:3] 相同包含了从第一个到第三个元素不包括第三个 func main() {var arr [6]int{1, 2, 3, 4, 5, 6}sum : sum(arr[:]) //数组的切片fmt.Println(sum)
}func sum(a []int) int {sum : 0for _, i : range a {sum i}return sum
}4️⃣ 多维切片
⭐️和数组一样切片通常也是一维的但是也可以由一维组合成高维。通过分片的分片或者切片的数 组长度可以任意动态变化所以 Go 语言的多维切片可以任意切分。而且内层的切片必须单独分配 通过 make 函数)
func main() {slice : [][]int{{1, 2, 3}, {4, 5, 6}}// slice2 : make([][]int, 2,10) 通过makefmt.Println(slice)
}4️⃣ Bytes包
⭐️ []bytes这种切片类型很常见包括使用的string类型它底层就是使用的它
⭐️ 他提供一个非常有用的Buffer它可以实现类似Java的Stringbuilder的功能
//源代码
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {buf []byte // contents are the bytes buf[off : len(buf)]off int // read at buf[off], write at buf[len(buf)]lastRead readOp // last read operation, so that Unread* can work correctly.
}⭐️ 使用感觉它没有Stringbuilder好用它不能使用那种流方法只能另起一行或者是用循环输入
func main() {var buffer bytes.Bufferbuffer.WriteString(hello) //追加Stringbuffer.WriteString(world) //追加Stringstr : buffer.String() //获取Stringfmt.Println(str) //helloworld}func main() {var buffer bytes.Bufferfor {var str stringfmt.Scanln(str)buffer.WriteString(str) //追加Stringif str q {break}}str : buffer.String() //获取Stringfmt.Println(str) //helloworld}4️⃣ 切片和垃圾回收
⭐️ 切片的底层指向一个数组该数组的实际体积可能要大于切片所定义的体积。只有在没有任何切片指向的 时候底层的数组内层才会被释放这种特性有时会导致程序占用多余的内存