您的位置 首页 golang

Golang 切片,函数,追加和复制

> Photo by Kaboompics .com from Pexels

我正在浏览A Go of Go修改一些基础知识,并遇到了一个练习,在该练习中,您必须编写一个简单的结构来实现Go io. Reader 接口。 io.Reader具有单个Read(b [] byte)(int,error)方法,该方法应从接口实现保存的数据中读取并写入b。

我首先尝试使用Go内置的附加功能对Read方法进行简单的实现,但这种方法不起作用,最终我意识到我应该使用内置的复制功能。 但是,我也意识到,在修改切片时,我经常会犯错,并且想了解,并写下创建,传递给函数并使用附加或复制函数进行修改时实际发生的情况。

本文中的代码示例仅用于调试/学习目的,并不打算在实际程序中使用。

Go函数参数

在Go函数中,参数(和接收器)是通过副本而不是引用传递的,这意味着该函数将接收调用该函数时传递的值的副本。 如果在函数内部修改了类似struct的值,则应该为函数提供指向该结构的指针,否则只能在函数内部修改该结构的副本,而更改不会反映在函数外部。

 func main() {  
  m := MyStruct{"foo"}
  ModifyStructFunc(m, "bar")
  fmt.Printf("In main: m is %+v, pointer to m is %p", m, &m)
}
func ModifyStructFunc(m MyStruct, s string) {
  m.X = s
  fmt.Printf("In ModifyStructFunc m is %+v, pointer to m is %p", m, &m)
}
type MyStruct struct { X string }
// Outputs:
// In ModifyStructFunc: m is {X:bar}, pointer to m is 0x40c140
// In main: m is {X:foo}, pointer to m is 0x40c138
// Here, the change made in ModifyStructFunc is not seen in main
// The different pointers show that those are different structs  

切片也通过副本传递,但是由于切片不保存实际数据,因此仅指向基础数组的指针,因此副本仍将指向同一数组,并且可以通过一个切片指向另一个数组来更改数组 指向同一数组的切片。

 func main() {  
  s := []int{0}
  ModifySliceFunc(s)
  fmt.Printf("In main: s is %v, pointer to s is %p", s, &s)
}
func ModifySliceFunc(s []int) {
  s[0] = 999
  fmt.Printf("In ModifySliceFunc: s is %v, pointer to s is %p\n", s, &s)
}
// Outputs:
// In ModifySliceFunc: s is [999], pointer to s is 0x40a0f0
// In main: s is [999], pointer to s is 0x40a0e0
// Here, the change made in ModifySliceFunc is visible in main too
// The different pointers show that these are different slices  

内建附加功能

因此,我认为我可以使用一个非常简单的Read方法进行测试,使用append来修改byte slice参数:

 func main() {
  b := make([]byte, 8)
  m := MyReader{}
  m.Read(b)
  fmt.Printf("b in main: %v\n", b)
}
func (m *MyReader) Read(b []byte) (int, error) {
  b = append(b, byte('A')) // Placeholder data
  fmt.Printf("b in Read: %v\n", b)
  return 1, nil // Some placeholder values
}
type MyReader struct {}
// Outputs:
// b in Read: [0 0 0 0 0 0 0 0 65]
// b in main: [0 0 0 0 0 0 0 0]
// Does not work- after calling Read method, b still has the initial value  

上面的代码不起作用的原因与Go slice以及内置的make和append函数的工作方式有关。

切片包含对基础数组,长度(其指向的数组段的长度)和容量(从切片段中第一个元素开始的底层数组的总长度)的引用。 可以调整切片的大小,使其指向基础数组的不同段。

make(slice [] T,len,cap int)函数可用于初始化新的切片。 它接受三个参数:类型,长度和可选容量(默认情况下,容量等于长度)。 它将使元素个数上限的数组归零,并返回一个切片,该切片指向长度为len的数组段。

append(slice [] T,args…T)[] T函数将args附加到slice。 它使用切片的调整大小功能-如果在切片的分段之后,底层数组的部分中有足够的容量,则将在切片的最后一个元素之后将args写入现有数组,并且将调整切片的大小以包含args和 回。 如果没有足够的容量,将创建一个新的数组,切片中的元素以及写入新数组的args,并返回指向新数组的切片。

在上面的代码示例中,b:= make([] byte,8)创建一个长度为8的数组和一个长度也为8的slice b。调用append时,没有足够的容量来写入byte(’A’ )到旧数组,从而创建一个新数组,将sliceb和byte(A)的内容写入新数组,最后返回指向该新数组的切片的指针。 同时,main中的切片b仍指向旧数组。

我想观察数组的变化,找到Go的%p打印动词,它对一个切片显示第0个元素的地址,非常有用:

 func main() {
  b := make([]byte, 8)
  r := MyReader{}
  r.Read(b)
  fmt.Printf("%p\n", b) // 0x40e020
}
func (m *MyReader) Read(b []byte) (int, error) {
  fmt.Printf("%p\n", b) // 0x40e020
  b = append(b, byte('A'))
  fmt.Printf("%p\n", b) // 0x40e040
  return 1, nil
}
type MyReader struct {}
// After running append, memory address of b[0] has changed  

我可以通过确保有一些额外的容量来附加byte(’A’)来解决此问题。 在这里,即使运行追加后,切片仍指向同一数组(但main中的切片仍具有初始数组):

 func main() {
  // make a slice of length 0 with underlying array of length 8
  b := make([]byte, 0, 8)
  r := MyReader{}
  r.Read(b)
  fmt.Printf("In main after calling Read: b is %v, addr of b[0] is %p\n", b, b)}
func (m *MyReader) Read(b []byte) (int, error) {
  fmt.Printf("In Read before calling append: b is %v, addr of b[0] is %p\n", b, b)
  b = append(b, byte('A'))
  fmt.Printf("In Read after calling append: b is %v, addr of b[0] is %p\n", b, b)
  return 1, nil
}
type MyReader struct {}
// Outputs:
// In Read before calling append: b is [], addr of b[0] is 0x40e020
// In Read after calling append: b is [65], addr of b[0] is 0x40e020
// In main after calling Read: b is [], addr of b[0] is 0x40e020
// Address of b[0] has not changed after calling append, so
// there has not been a new array created
// However - slice b in main still has the initial value even after calling Read  

上面的代码未按预期工作的原因是因为main中的slice b的长度仍为0,因此我们看不到写入数组的新元素。 我们可以通过在调用Read之后调整main中slice的大小来解决此问题:

 func main() {
  // make a slice of length 0 pointing at an array of length 8
  b := make([]byte, 0, 8)
  r := MyReader{}
  r.Read(b)
  // Resize slice b to include the 0th element of the array
  b = b[:1]
  fmt.Printf("%v\n", b) // [65]
}
func (m *MyReader) Read(b []byte) (int, error) {
  b = append(b, byte('A'))
  return 1, nil
}
type MyReader struct {}  

现在main中的b片具有append所做的更改。 但是,基于附加的解决方案将有许多问题。 必须调整main内部的切片大小以查看写入的值很笨拙。 但最重要的是,实际上应该以某种方式实现Reader接口,即Read(b [] byte)(int,error)函数每次调用都会将len(b)个字节读入b。 这可能无法使用append干净地完成。

复制

copy(d,s [] T)int函数将元素从源切片复制到目标切片,并返回复制的元素数。

 func main() {
  s := []string{"foo", "bar", "baz"}
   d := make([]string, 3)
  // will copy s -> d and return number of elements copied
  n := copy(d, s)
  fmt.Printf("d: %v, n: %d\n", d, n) // d: [foo bar baz], n: 3
s1 := []string{"alpha", "beta", "gamma"}
  d1 := make([]string, 2)
  // len(d1) < len(s), so will only copy len(d) elements
  n1 := copy(d1, s1)
  fmt.Printf("d1: %v, n1: %d\n", d1, n1) // d1: [alpha beta], n1: 2
}  

复制函数可在Read(b [] byte)(int,error)内部使用,以从初始化Reader的任何源复制tolen(b)个元素,并返回复制的元素数。 我们将要跟踪已复制了源中的多少个元素,并在复制了所有元素后返回特定错误(io.EOF)。

 func main() {
  m := NewReader("One two three..")
  // bytes from MyReader will be written to this slice
  b := make([]byte, 8)
  for {
    n, err := m.Read(b)
    // break if all bytes have been read
    if err == io.EOF {
      break
    }
    if err != nil {
      log.Fatalf("Error: %v\n", err)
    }
    fmt.Printf("n: %v, b: %v\n", n, b)
   }
}
// Reads len(b) bytes to b
func (m *MyReader) Read(b []byte) (int, error) {
  if m.index >= len(m.contents) {
    return 0, io.EOF
  }
  c := copy(b, m.contents[m.index:])
  // Move the index forward by the number of bytes copied
  m.index += c
  return c, nil
}
// Initialises a new MyReader
func NewReader(s string) *MyReader {
  return &MyReader{
    contents: []byte(s),
  }
}
// Holds contents slice and index of where to read from in contents
type MyReader struct {
  contents []byte
  index int
}
// Outputs:
// n: 8, b: [79 110 101 32 116 119 111 32]
// n: 7, b: [116 104 114 101 101 46 46 32]  

这是一个简单的测试实现,并且不考虑源包含由多个字节组成的字符时会发生什么情况(因为Read可能不会拆分字符)。 Go字符串包中的Theio.Reader实现显示了如何解决此问题。

结论:

· append(s [] T,args … T)[] T将args附加到切片。 如果容量不足,将创建一个新阵列。 返回修改后的切片

· copy(d,s [] T)int将元素从源复制到目标切片。 如果len(d)<len(s),将复制len(d)个元素。 返回复制的元素数

· 尽管所有Go函数参数都是通过副本传递的,但对参考值(例如切片)的更改可能会影响调用方作用域中的值

· Go%p打印动词对于调试非常有用

(本文翻译自Irbe Krumina的文章《Go slices, functions, append and copy》,参考:

文章来源:智云一二三科技

文章标题:Golang 切片,函数,追加和复制

文章地址:https://www.zhihuclub.com/88463.shtml

关于作者: 智云科技

热门文章

网站地图