web-dev-qa-db-ja.com

golangで引数として配列を渡す

なぜこれが機能しないのですか?

package main

import "fmt"

type name struct {
    X string
}

func main() {
    var a [3]name
    a[0] = name{"Abbed"}
    a[1] = name{"Ahmad"}
    a[2] = name{"Ghassan"}

    nameReader(a)
} 

func nameReader(array []name) {
    for i := 0; i < len(array); i++ {
        fmt.Println(array[i].X)
    }
}

エラー:

.\structtest.go:15: cannot use a (type [3]name) as type []name in function argument
36
user1721803

スライスを引数として受け入れるように関数を定義しましたが、その関数の呼び出しで配列を渡そうとしています。これに対処するには、2つの方法があります。

  1. 関数を呼び出すときに、配列からスライスを作成します。このように呼び出しを変更するだけで十分です:

    nameReader(a[:])
    
  2. 関数シグネチャを変更して、スライスではなく配列を取得します。例えば:

    func nameReader(array [3]name) {
        ...
    }
    

    この解決策の欠点は、関数が長さ3の配列のみを受け入れるようになり、呼び出し時に配列のコピーが作成されることです。

アレイとスライスの詳細、およびそれらを使用する際の一般的な落とし穴については、こちらをご覧ください。 http://openmymind.net/The-Minimum-You-Need-To-Know-About-Arrays-And-Slices -In-Go /

43

@ james-henstridgeの答えはすでにあなたがそれを機能させる方法をカバーしているので、私は彼が言ったことを複製しませんが、私は彼の答えが機能するwhyを説明します。

Goでは、配列は他のほとんどの言語とは少し異なります(はい、配列とスライスがあります。スライスについては後で説明します)。 Goでは、コードで使用する配列は固定サイズです(したがって、_[3]int_は_[4]int_とは異なる型です)。さらに、配列はvaluesです。つまり、ある場所から別の場所に配列をコピーすると、実際には配列のすべての要素がコピーされます(他のほとんどの言語のように、同じ配列への別の参照を作成する代わりに)。例えば:

_a := [3]int{1, 2, 3} // Array literal
b := a               // Copy the contents of a into b
a[0] = 0
fmt.Println(a)       // Prints "[0 2 3]"
fmt.Println(b)       // Prints "[1 2 3]"
_

ただし、お気づきのように、Goにはスライスもあります。スライスは、2つの重要な方法を除いて、配列に似ています。最初に、それらは可変長です(したがって_[]int_は任意の数の整数のスライスのタイプです)。次に、スライスはreferencesです。つまり、スライスを作成すると、スライスの内容を表すためにメモリが割り当てられ、スライス変数自体は実際にはそのメモリへの単なるポインタになります。次に、そのスライスをコピーすると、実際にはポインターをコピーしているだけです。つまり、スライスをコピーしてから値の1つを変更すると、すべてのユーザーの値が変更されます。例えば:

_a := []int{1, 2, 3} // Slice literal
b := a              // a and b now point to the same memory
a[0] = 0
fmt.Println(a)      // Prints "[0 2 3]"
fmt.Println(b)      // Prints "[0 2 3]"
_

実装

その説明が非常に簡単に理解できる場合は、これがどのように実装されているかを知りたいかもしれません(理解できない場合は、詳細が混乱する可能性があるため、ここでは読みません。).

内部では、Goスライスは実際には構造体です。前述したように、割り当てられたメモリへのポインタがありますが、他の重要なコンポーネントである長さと容量もあります。 Goの用語で説明されている場合、次のようになります。

_type int-slice struct {
    data *int
    len  int
    cap  int
}
_

長さはスライスの長さであり、len(mySlice)を要求できるようになっています。また、Goが実際にスライスにない要素にアクセスしていないことを確認できるようにします。ただし、容量はもう少しわかりにくいです。それでは、もう少し詳しく見ていきましょう。

最初にスライスを作成するときに、スライスに必要な要素をいくつか指定します。たとえば、make([]int, 3)を呼び出すと、3つのintのスライスが得られます。これは、メモリ内のスペースを3つのintに割り当て、データへのポインタ、長さ3、容量3の構造体を返します。

ただし、Goでは、スライシングと呼ばれる処理を実行できます。これは基本的に、古いスライスの一部のみを表す古いスライスから新しいスライスを作成する場所です。 _slc[a:b]_構文を使用して、インデックスslcで始まりインデックスaの直前で終わるbのサブスライスを参照します。したがって、たとえば:

_a := [5]int{1, 2, 3, 4, 5}
b := a[1:4]
fmt.Println(b) // Prints "[2 3 4]"
_

このスライシング操作が内部で行うことは、aに対応する構造体のコピーを作成し、ポインターをメモリ内の1整数を指すように編集することです(新しいスライスはインデックス1で始まるため)長さが以前よりも2短くなります(古いスライスの長さは5で、新しいスライスの長さは3であるため)。それで、これは今メモリでどのように見えますか?さて、レイアウトされた整数を視覚化できれば、次のようになります。

_  begin     end  // a
  v         v
[ 1 2 3 4 5 ]
    ^     ^
    begin end    // b
_

bの終わりの後にまだ1つのintがあることに注意してください。まあそれは容量です。参照してください、使用するためにメモリが残っている限り、すべてを使用できる可能性があります。そのため、長さが短いスライスのみを使用している場合でも、元に戻したい場合のために、より多くの容量があることを覚えています。したがって、たとえば:

_a := []int{1, 2, 3}
b := a[0:1]
fmt.Println(b) // Prints "[1]"
b = b[0:3]
fmt.Println(b) // Prints "[1 2 3]"
_

最後に_b[0:3]_を行う方法を参照してください。 bの長さは、実際にはこの時点で3よりもlessです。そのため、Goができるのはそれだけです基礎となるメモリに、実際にはより多くの容量が予約されているという事実を追跡しました。そうすれば、それのいくつかを取り戻すように頼むとき、それは幸福に義務づけることができます。

26
joshlf

代替アプローチ

スライス上で variadic function を呼び出して、nameReader関数の個々の引数として名前のリストを入力できます。例:

package main

import "fmt"

type name struct {
    X string
}

func main() {
    a := [3]name{{"Abbed"}, {"Ahmed"}, {"Ghassan"}}
    nameReader(a[:]...)
}

func nameReader(a ...name) {
    for _, n := range a {
        fmt.Println(n.X)
    }
}
6
XaxD

サイズ付き配列を宣言する代わりに、スライスを宣言します。それにメモリを割り当て、それを埋めます。それが得られたら、元の参照はスライスにまだあり、関数に渡すことができます。この簡単な例を考えてみましょう

package main

import "fmt"

func main() {
  // declare a slice
  var i []int

  i = make([]int, 2)
  i[0] = 3
  i[1] = 4

  // check the value - should be 3
  fmt.Printf("Val - %d\n", i[0])

  // call the function  
  a(i)

  // check the value again = should be 33
  fmt.Printf("Val - %d\n", i[0])  
}

func a(i []int) {
  // check the value - should be 3
  fmt.Printf("Val - %d\n", i[0])  

  // change the value
  i[0] = 33

  // check the value again = should be 33
  fmt.Printf("Val - %d\n", i[0])  
}

ご覧のとおり、配列は(参照として)渡され、対応する関数によって変更できます。

出力は次のようになります。

Val - 3
Val - 3
Val - 33
Val - 33

プログラム全体は次の場所にもあります。 http://play.golang.org/p/UBU56eWXhJ

2
lsu_guy

パラメータとして配列を渡します。

配列の値は単一のユニットとして扱われます。配列変数はメモリ内の場所へのポインタではなく、配列要素を含む「メモリブロック」全体を表します。これには、配列変数が再割り当てされるか、関数パラメーターとして渡されるときに、配列値の新しいコピーを作成する意味があります。 Goプログラミングの学習、Vladimir Vivien著

これにより、プログラムのメモリ消費に望ましくない副作用が生じる可能性があります。これを修正するには、「ポインター型」を使用して配列値を参照します。例えば:

代わりにこれを行います:

var numbers [1024*1024]int

あなたがする必要があります:

type numbers [1024*1024]int
var nums *numbers = new(numbers)

覚えておいてください:

https://golang.org/pkg/builtin/#new

新しい組み込み関数はメモリを割り当てます。最初の引数は値ではなく型であり、返される値はその型の新しく割り当てられたゼロ値へのポインタです。

これで、メモリ消費の副作用なしで関数に配列ポインタを渡し、必要に応じて使用できます。

nums[0] = 10
doSomething(nums)

func doSomething(nums *numbers){
  temp := nums[0]
  ...
}

留意すべきことの1つは、配列型はGoの低レベルのストレージ構造であり、ストレージプリミティブの基礎として使用され、スペースの消費を最小限にするために厳密なメモリ割り当て要件があることです。要件がパフォーマンスに依存している場合、スライスではなく配列(前の例のような)を使用する必要があります。

0

スライスを使用するだけです。ここでコードを実行します: http://play.golang.org/p/WtcOvlQm01

覚えておく必要がある[3]nameは配列です。 []nameはスライスです。

package main

import "fmt"

type name struct {
    X string
}

func main() {
    a := []name{name{"Abbed"}, name{"Ahmad"}, name{"Ghassan"}}
    nameReader(a)
} 

func nameReader(array []name) {
    for i := 0; i < len(array); i++ {
        fmt.Println(array[i].X)
    }
}

さらなる参考資料: Go Shades of Go:Traps、Gotchas、and Common Mistakes for New Golang Devs

0
Vince Yuan