web-dev-qa-db-fra.com

Convertir entre des tranches de types différents

Je reçois une tranche d'octet ([]byte) d'un socket UDP et je souhaite la traiter comme une tranche d'entier ([]int32) sans changer le tableau sous-jacent, et inversement En C (++), je lancerais simplement entre les types de pointeur; comment ferais-je cela dans Go?

35
slartibartfast

Comme d'autres l'ont dit, le fait de placer le pointeur est considéré comme une mauvaise forme dans Go. Voici des exemples de la méthode Go appropriée et de l’équivalent de la conversion du tableau C.

ATTENTION: tout le code n'a pas été testé.

Le droit chemin

Dans cet exemple, nous utilisons le package encoding/binary pour convertir chaque ensemble de 4 octets en un int32. C'est mieux parce que nous spécifions la finalité. Nous n'utilisons pas non plus le package unsafe pour détruire le système de types.

import "encoding/binary"

const SIZEOF_INT32 = 4 // bytes

data := make([]int32, len(raw)/SIZEOF_INT32)
for i := range data {
    // assuming little endian
    data[i] = int32(binary.LittleEndian.Uint32(raw[i*SIZEOF_INT32:(i+1)*SIZEOF_INT32]))
}

The Wrong Way (casting de matrice C)

Dans cet exemple, nous demandons à Go d’ignorer le système de types. Ce n'est pas une bonne idée car cela pourrait échouer dans une autre implémentation de Go. En supposant que les choses ne soient pas dans la spécification du langage. Cependant, celui-ci ne fait pas une copie complète. Ce code utilise unsafe pour accéder à "SliceHeader" qui est commun dans toutes les tranches. L'en-tête contient un pointeur sur les données (tableau C), la longueur et la capacité. Au lieu de convertir simplement l'en-tête en nouveau type de tranche, nous devons d'abord modifier la longueur et la capacité, car il y a moins d'éléments si nous traitons les octets comme un nouveau type.

import (
    "reflect"
    "unsafe"
)

const SIZEOF_INT32 = 4 // bytes

// Get the slice header
header := *(*reflect.SliceHeader)(unsafe.Pointer(&raw))

// The length and capacity of the slice are different.
header.Len /= SIZEOF_INT32
header.Cap /= SIZEOF_INT32

// Convert slice header to an []int32
data := *(*[]int32)(unsafe.Pointer(&header))
34
Stephen Weinberg

La réponse courte est que vous ne pouvez pas. Go ne vous laissera pas lancer une tranche d'un type sur une tranche d'un autre type. Vous allez parcourir le tableau et créer un autre tableau du type que vous voulez lors de la conversion de chaque élément du tableau. Ceci est généralement considéré comme une bonne chose puisque la sécurité-types est une caractéristique importante de go.

8
Jeremy Wall

Vous faites ce que vous faites en C, à une exception près - Go ne permet pas de convertir un type de pointeur en un autre. Eh bien, oui, mais vous devez utiliser unsafe.Pointer pour indiquer au compilateur que vous savez que toutes les règles sont enfreintes et que vous savez ce que vous faites. Voici un exemple:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    b := []byte{1, 0, 0, 0, 2, 0, 0, 0}

    // step by step
    pb := &b[0]         // to pointer to the first byte of b
    up := unsafe.Pointer(pb)    // to *special* unsafe.Pointer, it can be converted to any pointer
    pi := (*[2]uint32)(up)      // to pointer to the first uint32 of array of 2 uint32s
    i := (*pi)[:]           // creates slice to our array of 2 uint32s (optional step)
    fmt.Printf("b=%v i=%v\n", b, i)

    // all in one go
    p := (*[2]uint32)(unsafe.Pointer(&b[0]))
    fmt.Printf("b=%v p=%v\n", b, p)
}

Évidemment, vous devriez faire attention à l’utilisation du paquet "unsafe", parce que le compilateur Go ne vous tient plus la main - par exemple, vous pourriez écrire pi := (*[3]uint32)(up) ici et le compilateur ne se plaindrait pas, mais vous auriez des problèmes.

En outre, comme d'autres personnes l'ont déjà souligné, les octets de uint32 peuvent être mis en forme différemment sur différents ordinateurs. Par conséquent, vous ne devez pas supposer qu'il s'agit de la mise en forme telle que vous en avez besoin.

L’approche la plus sûre serait donc de lire votre tableau d’octets un par un et d’en tirer tout ce dont vous avez besoin.

Alex

5
alex

J'ai eu le problème de taille inconnue et peaufiné la méthode non sécurisée précédente avec le code suivant. donné une tranche d'octet b ...

int32 slice is (*(*[]int)(Pointer(&b)))[:len(b)/4]

Il est possible de donner à l'exemple de matrice à tranche une grande constante fictive et d'utiliser les mêmes limites de tranche, car aucun tableau n'est alloué. 

2
Tony Wilson

Vous pouvez le faire avec le package "unsafe"

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var b [8]byte = [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
    var s *[4]uint16 = (*[4]uint16)(unsafe.Pointer(&b))
    var i *[2]uint32 = (*[2]uint32)(unsafe.Pointer(&b))
    var l *uint64 = (*uint64)(unsafe.Pointer(&b))

    fmt.Println(b)
    fmt.Printf("%04x, %04x, %04x, %04x\n", s[0], s[1], s[2], s[3])
    fmt.Printf("%08x, %08x\n", i[0], i[1])
    fmt.Printf("%016x\n", *l)
}

/*
 * example run:
 * $ go run /tmp/test.go
 * [1 2 3 4 5 6 7 8]
 * 0201, 0403, 0605, 0807
 * 04030201, 08070605
 * 0807060504030201
 */
0
Matteo Croce

http://play.golang.org/p/w1m5Cs-ecz

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := []interface{}{"foo", "bar", "baz"}
    b := make([]string, len(s))
    for i, v := range s {
        b[i] = v.(string)
    }
    fmt.Println(strings.Join(b, ", "))
}
0
KIM Taegyoon