数组的声明
Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针)
所以可以通过 new() 来创建: var arr1 = new([5]int)。
正常的创建方法是这样的 var arr2 [5]int
那么这两种方法的区别是什么呢?arr1 的类型是 *[5]int,而 arr2 的类型是 [5]int。
练习7.1:array_value.go:
证明当数组赋值时,发生了数组内存拷贝。
package main
import "fmt"
func chang(ss [5]int) {
ss[0] = 89
fmt.Print(ss, "\n")
}
func chang1(ss *[5]int) {
ss[0] = 89
fmt.Print(*ss, "\n")
}
func main() {
a := [5]int{1, 2, 3, 4, 5}
fmt.Print(a, "\n")
chang(a)
fmt.Print(a, "\n")
//指针形式
chang1(&a)
fmt.Print(a, "\n")
}
结果:
练习7.2:for_array.go:
写一个循环并用下标给数组赋值(从 0 到 15)并且将数组打印在屏幕上。
package main
import "fmt"
func main() {
var a [15]int
fmt.Print(a)
for i, _ := range a {
a[i] = i
}
fmt.Print(a)
}
结果:
练习7.3:fibonacci_array.go:
在第 6.6 节 我们看到了一个递归计算 Fibonacci 数值的方法。但是通过数组我们可以更快的计算出 Fibonacci 数。完成该方法并打印出前 50 个 Fibonacci 数字。
package main
import (
"fmt"
"time"
)
var fb [50]int
func fibonacci2(vua_1, idx_1, vua_2, idx_2 int) (vua_r, idx_r int) {
if idx_2 == 2 {
idx_r = idx_2 + 1
vua_r = vua_2 + vua_1
fmt.Print(idx_1, ":", vua_1, "\n")
fmt.Print(idx_2, ":", vua_2, "\n")
fibonacci2(vua_2, idx_2, vua_r, idx_r)
return
} else if idx_2 <= 50 {
idx_r = idx_2 + 1
vua_r = vua_2 + vua_1
fmt.Print(idx_2, ":", vua_2, "\n")
fibonacci2(vua_2, idx_2, vua_r, idx_r)
return
} else {
return
}
}
func main() {
t1 := time.Now()
fmt.Print(fb, "\n")
for i, _ := range fb {
if i == 0 || i == 1 {
fb[i] = 1
} else {
fb[i] = fb[i-1] + fb[i-2]
}
}
fmt.Print(fb, "\n")
t2 := time.Now()
delta := t2.Sub(t1)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
t3 := time.Now()
fibonacci2(1, 1, 1, 2)
t4 := time.Now()
delta1 := t4.Sub(t3)
fmt.Printf("longCalculation took this amount of time: %s\n", delta1)
}
结果:(数组比递归快)
数组初始化方法
第一种变化:
var arrAge = [5]int{18, 20, 15, 22, 16}
var arrAge =[10]int{18, 20, 15, 22, 16}//初始化前五个数
第二种变化:
var arrLazy = [...]int{5, 6, 7, 8, 22}
… 同样可以忽略,从技术上说它们其实变成了切片
第三种变化:key: value 语法
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
只有索引 3 和 4 被赋予实际的值,其他元素都被设置为空的字符串
切片
和数组不同的是,切片的长度可以在运行时修改,最小为 0, 最大为相关数组的长度:切片是一个 长度可变的数组。
切片提供了计算容量的函数 cap() 可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度。
多个切片如果表示同一个数组的片段,它们可以共享数据;因此一个切片和相关数组的其他切片是共享存储的,相反,不同的数组总是代表不同的存储。数组实际上是切片的构建块。
切片的声明和初始化方法
声明切片的格式是:
var identifier []type//(不需要说明长度)。
一个切片在未初始化之前默认为 nil,长度为 0。
初始化方式是:
var slice1 []type = arr1[start:end]。
一个由数字 1、2、3 组成的切片可以这么生成:
s := [3]int{1,2,3}[:]
(注:应先用 s := [3]int{1, 2, 3} 生成数组, 再使用 s[:] 转成切片)甚至更简单的
s := []int{1,2,3}
一个切片 s 可以这样扩展到它的大小上限,如果再扩大的话就会导致运行时错误
s = s[:cap(s)]
问题 7.2
给定切片 b:= []byte{‘g’, ‘o’, ‘l’, ‘a’, ‘n’, ‘g’},那么 b[1:4]、b[:2]、b[2:] 和 b[:] 分别是什么?
package main
import "fmt"
func main() {
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
fmt.Print(string(b), "\n")
fmt.Print(string(b[1:4]), "\n")
fmt.Print(string(b[:2]), "\n")
fmt.Print(string(b[2:]), "\n")
fmt.Print(string(b[1:]), "\n")
}
结果:
用 make() 创建切片
当相关数组还没有定义时,我们可以使用 make() 函数来创建一个切片,同时创建好相关数组。
make() 接受 2 个参数:元素的类型以及切片的元素个数。
var slice1 []type = make([]type, len)
slice1 := make([]type, len)
练习 7.4
fibonacci_funcarray.go: 为练习 7.3 写一个新的版本,主函数调用一个使用序列个数作为参数的函数,该函数返回一个大小为序列个数的 Fibonacci 切片。
package main
import (
"fmt"
)
var fb [50]int
func getF(num int) []int {
return fb[:num]
}
func main() {
for i, _ := range fb {
if i == 0 || i == 1 {
fb[i] = 1
} else {
fb[i] = fb[i-1] + fb[i-2]
}
}
ff := getF(15)
fmt.Print(ff)
}
new()和make()的区别
看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
new(T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 *T 的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体它相当于 &T{}。
make(T) 返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片、map 和 channel
换言之,new() 函数分配内存,make() 函数初始化;下图给出了区别:
byte包
byte切片,包含一个十分有用的类型 Buffer:
import "bytes"
type Buffer struct {
...
}
这是一个长度可变的 bytes 的 buffer,提供 Read() 和 Write() 方法,因为读写长度未知的 bytes 最好使用 buffer。
Buffer 可以这样定义:
var buffer bytes.Buffer
或者使用 new() 获得一个指针:
var r *bytes.Buffer = new(bytes.Buffer)。
或者通过函数:
func NewBuffer(buf []byte) *Buffer
通过 buffer 串联字符串,类似于 Java 的 StringBuilder 类。
在下面的代码段中,我们创建一个 buffer,通过 buffer.WriteString(s) 方法将字符串 s 追加到后面,最后再通过 buffer.String() 方法转换为 string:
var buffer bytes.Buffer
for {
if s, ok := getNextString(); ok { //method getNextString() not shown here
buffer.WriteString(s)
} else {
break
}
}
fmt.Print(buffer.String(), "\n")
这种实现方式比使用 += 要更节省内存和 CPU,尤其是要串联的字符串数目特别多的时候。
练习 7.5
给定切片 sl,将一个 []byte 数组追加到 sl 后面。写一个函数 Append(slice, data []byte) []byte,该函数在 sl 不能存储更多数据的时候自动扩容。
package main
import (
"bytes"
"fmt"
)
func Append(slice, data []byte) []byte {
var buffer bytes.Buffer
buffer.Write(slice)
for _, v := range data {
buffer.WriteByte(v)
}
num := len(slice) + len(data)
sl := make([]byte, num)
buffer.Read(sl)
return sl
}
func main() {
var nums = [10]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sl := nums[:5]
sl1 := nums[5:]
sl2 := Append(sl, sl1)
fmt.Print(sl, "\n")
fmt.Print(sl1, "\n")
fmt.Print(sl2, "\n")
}
结果:
练习 7.6
把一个缓存 buf 分片成两个切片:第一个是前 n 个 bytes,后一个是剩余的,用一行代码实现。
import (
"bytes"
"fmt"
)
func main() {
var nums = [10]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var buffer bytes.Buffer
sl := nums[:]
buffer.Write(sl)
//一行代码:
pre_sl, end_sl := buffer.Bytes()[:5], buffer.Bytes()[5:]
fmt.Print(pre_sl, "\n")
fmt.Print(end_sl, "\n")
}
结果:
切片的for range
问题 7.6
通过使用省略号操作符 … 来实现累加方法。
package main
func add(a ...int) (res int) {
for _, v := range a {
res += v
}
return res
}
func main() {
res := add(1, 2, 3, 4, 5, 6, 7, 8, 9)
print(res)
}
练习 7.7 sum_array.go
a) 写一个 Sum() 函数,传入参数为一个 float32 数组成的数组 arrF,返回该数组的所有数字和。
如果把数组修改为切片的话代码要做怎样的修改?如果用切片形式方法实现不同长度数组的的和呢?
b) 写一个 SumAndAverage() 方法,返回两个 int 和 float32 类型的未命名变量的和与平均值。
package main
func sum(nums []float32) float32 {
var res float32
for _, v := range nums {
res += v
}
return res
}
func SumAndAverange(a, b int, c float32) (sum, averge float32) {
b_f, a_f := float32(b), float32(a)
sum = b_f + a_f + c
averge = sum / 3
return
}
func main() {
nums := [10]float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
res := sum(nums[:])
print(res, "\n")
s, a := SumAndAverange(3, 5, 7.0)
print(s, "\t", a)
}
练习 7.8 min_max.go
写一个 minSlice() 方法,传入一个 int 的切片并且返回最小值,再写一个 maxSlice() 方法返回最大值。
package main
func minSlice(a []int) (min int) {
min = 100000000000000
for _, v := range a {
if v < min {
min = v
}
}
return
}
func maxSlice(a []int) (max int) {
max = 0
for _, v := range a {
if v > max {
max = v
}
}
return
}
func main() {
var nums = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
max := maxSlice(nums[:])
min := minSlice(nums[:])
print(max, "\t", min)
}
切片的复制和追加
复制copy()函数
将后面的切片的值复制到前一个切片
slFrom := []int{1, 2, 3}
slTo := make([]int, 10)
n := copy(slTo, slFrom)
fmt.Print(slFrom, "\t", slTo, "\t", n)
追加append()函数
func append(s[]T, x …T) []T 其中 append() 方法将 0 个或多个具有相同类型 s 的元素追加到切片后面并且返回新的切片;追加的元素必须和原切片的元素是同类型。如果 s 的容量不足以存储新增元素,append() 会分配新的切片来保证已有切片元素和新增元素的存储。因此,返回的切片可能已经指向一个不同的相关数组了。append() 方法总是返回成功,除非系统内存耗尽了。
如果你想将切片 y 追加到切片 x 后面,只要将第二个参数扩展成一个列表即可:x = append(x, y…)。
如果想追加字符串要将字符串转化为字节切片
s:=[]byte(string)
然后追加字符
s=append(s,string...)
append() 非常有用,它能够用于各种方面的操作:
将切片 b 的元素追加到切片 a 之后:
a = append(a, b...)
复制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a))
copy(b, a)
删除位于索引 i 的元素:
a = append(a[:i], a[i+1:]...)
切除切片 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)
练习 7.9 magnify_slice.go
给定一个切片 s []int 和一个 int 类型的因子 factor,扩展 s 使其长度为 len(s) * factor。
package main
import "fmt"
func add(s []int, f int) []int {
res := make([]int, len(s)*f)
copy(res, s)
return res
}
func main() {
s := []int{1, 2, 3}
factor := 5
fmt.Print(s, "\n")
s = add(s, factor)
fmt.Print(s, "\n")
}
练习 7.10 filter_slice.go
用顺序函数过滤容器:s 是前 10 个整型的切片。构造一个函数 Filter,第一个参数是 s,第二个参数是一个 fn func(int) bool,返回满足函数 fn 的元素切片。通过 fn 测试方法测试当整型值是偶数时的情况。
package main
import "fmt"
func oshu(i int) bool {
if i%2 == 0 {
return true
} else {
return false
}
}
func filter_slice(s []int, f func(int2 int) bool) []int {
res := make([]int, 1)
var ok bool = true
for i, v := range s {
if f(v) && ok {
copy(res, s[i:i+1])
ok = false
} else if f(v) {
res = append(res, s[i])
}
}
return res
}
func main() {
nums := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
ss := nums[:]
aa := filter_slice(ss, oshu)
fmt.Print(aa)
}
结果:
练习 7.11 insert_slice.go
写一个函数 InsertStringSlice() 将切片插入到另一个切片的指定位置。
package main
import "fmt"
func InsertStringSlice(sl_bein, sl_in []int, flag int) ([]int, bool) {
if flag < len(sl_bein) {
pre_sl, after_sl := sl_bein[:flag], sl_bein[flag:]
res := append(pre_sl, sl_in...)
res = append(res, after_sl...)
return res, true
} else {
var res = make([]int, len(sl_bein)+len(sl_in))
return res, false
}
}
func main() {
var num = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := num[:]
s2 := num[3:7]
res, ok := InsertStringSlice(s1, s2, 3)
if ok {
fmt.Print(res)
}
}
结果:
练习 7.12 remove_slice.go
写一个函数 RemoveStringSlice() 将从 start 到 end 索引的元素从切片中移除。
package main
import "fmt"
func RemoveStringSlice(sl []int, begin, end int) ([]int, bool) {
if end < len(sl) {
before, _, after := sl[:begin], sl[begin:end+1], sl[end+1:]
res := append(before, after...)
return res, true
} else {
return sl, false
}
}
func main() {
var num = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := num[:]
res, ok := RemoveStringSlice(s1, 2, 3)
if ok {
fmt.Print(res)
}
}
结果:
字符串的修改
Go 语言中的字符串是不可变的,也就是说 str[index] 这样的表达式是不可以被放在等号左侧的。如果尝试运行 str[i] = ‘D’ 会得到错误:cannot assign to str[i]。
因此,您必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换回字符串格式。
例如,将字符串 “hello” 转换为 “cello”:
s := "hello"
c := []byte(s)
c[0] = 'c'
s2 := string(c) // s2 == "cello"
字符串的比较
Compare() 函数会返回两个字节数组字典顺序的整数对比结果
func Compare(a, b[]byte) int {
for i:=0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
// 数组的长度可能不同
switch {
case len(a) < len(b):
return -1
case len(a) > len(b):
return 1
}
return 0 // 数组相等
}
切片的排序和搜索
标准库提供了 sort 包来实现常见的搜索和排序操作。您可以使用 sort 包中的函数 func Ints(a []int) 来实现对 int 类型的切片排序。例如 sort.Ints(arri),其中变量 arri 就是需要被升序排序的数组或切片。为了检查某个数组是否已经被排序,可以通过函数 IntsAreSorted(a []int) bool 来检查,如果返回 true 则表示已经被排序。
sort.Ints(arri)
sort.IntsAreSorted(a []int) bool
类似的,可以使用函数 func Float64s(a []float64) 来排序 float64 的元素,或使用函数 func Strings(a []string) 排序字符串元素。
sort.Float64s(arrf)
sort.Float64sAreSorted(a []float64) bool
sort.Strings(arrf)
sort.StringsAreSorted(a []string) bool
想要在数组或切片中搜索一个元素,该数组或切片必须先被排序(因为标准库的搜索算法使用的是二分法)。然后,您就可以使用函数
func SearchInts 进行搜索,并返回对应结果的索引值。
sort.SearchInts(a []int, n int) int
sort.SearchFloat64s(a []float64, n int) int
sort.SearchStrings(a []string, n int) int
练习 7.12 split_string.go
编写一个函数,要求其接受两个参数,原始字符串 str 和分割索引 i,然后返回两个分割后的字符串。
package main
import "fmt"
func split_string(s string, flag int) (s1, s2 string, ok bool) {
ss := s[:]
if flag < len(s) {
s1 = ss[:flag]
s2 = ss[flag:]
ok = true
} else {
s1 = ss[:]
s2 = ""
ok = false
}
return
}
func split_string1(s string, flag int) (s1, s2 string, ok bool) {
ss1 := []byte(s)
if flag < len(s) {
s1 = string(ss1[:flag])
s2 = string(ss1[flag:])
ok = true
} else {
s1 = string(ss1[:])
s2 = ""
ok = false
}
return
}
func main() {
s := "dadahjkdahdkajs"
s1, s2, ok := split_string(s, 5)
if ok {
fmt.Print(s1, "\n", s2, "\n")
}
s3, s4, ok1 := split_string1(s, 5)
if ok1 {
fmt.Print(s3, "\n", s4, "\n")
}
}
结果:
练习 7.13 string_split2.go
假设有字符串 str,那么 str[len(str)/2:] + str[:len(str)/2] 的结果是什么?
package main
import “fmt”
func main() {
str := “qwertyuiop” //偶数
str1 := “qwertyuio” //奇数
ss := str[len(str)/2:] + str[:len(str)/2]
fmt.Print(str, "\n")
fmt.Print(ss, "\n")
ss1 := str1[len(str1)/2:] + str1[:len(str1)/2]
fmt.Print(str1, "\n")
fmt.Print(ss1, "\n")
}
结果:
练习 7.14 string_reverse.go
编写一个程序,要求能够反转字符串,即将 “Google” 转换成 “elgooG”(提示:使用 []byte 类型的切片)。
如果您使用两个切片来实现反转,请再尝试使用一个切片(提示:使用交换法)。
如果您想要反转 Unicode 编码的字符串,请使用 []int32 类型的切片。
package main
import "fmt"
func main() {
str := "Google"
ss := []byte(str)
for i := 0; i < len(str)/2; i++ {
ss[i], ss[len(str)-1-i] = ss[len(str)-1-i], ss[i]
}
s := string(ss)
fmt.Print(s)
}
练习 7.15 Q29_uniq.go
编写一个程序,要求能够遍历一个字符数组,并将当前字符和前一个字符不相同的字符拷贝至另一个数组。
package main
import "fmt"
func check(s []byte) (res []byte) {
res = append(res, s[0])
for i := 1; i < len(s); i++ {
if s[i] != s[i-1] {
res = append(res, s[i])
}
}
return
}
func main() {
str := "Googleasdlkjkk;aassdhsjjd"
ss := []byte(str)
s := check(ss)
fmt.Print(string(s))
}
结果:
练习 7.16 bubblesort.go
编写一个程序,使用冒泡排序的方法排序一个包含整数的切片(算法的定义可参考 维基百科)。
package main
import "fmt"
func bubblesort(a []int) []int {
for i := 1; i < len(a); i++ {
for j := 1; j < len(a)-i+1; j++ {
if a[j] < a[j-1] {
temp := a[j]
a[j] = a[j-1]
a[j-1] = temp
}
}
}
return a
}
func main() {
num := []int{123, 456, 132, 456, 132, 65498, 123, 684, 1, 26}
fmt.Print(num, "\n")
num = bubblesort(num)
fmt.Print(num, "\n")
}
结果:
练习 7.17 map_function.go
在函数式编程语言中,一个 map-function 是指能够接受一个函数原型和一个列表,并使用列表中的值依次执行函数原型,公式为:map ( F(), (e1,e2, . . . ,en) ) = ( F(e1), F(e2), … F(en) )。
编写一个函数 mapFunc 要求接受以下 2 个参数:
一个将整数乘以 10 的函数
一个整数列表
最后返回保存运行结果的整数列表。
package main
import "fmt"
func m10(a int) (res int) {
res = a * 10
return
}
func map_function(f func(int) int, a []int) []int {
for i, v := range a {
a[i] = f(v)
}
return a
}
func main() {
num := []int{123, 456, 132, 456, 132, 65498, 123, 684, 1, 26}
fmt.Print(num, "\n")
num = map_function(m10, num)
fmt.Print(num)
}