Konwertuj między plastrami różnych typów
Dostaję kawałek bajtowy ([]byte
) z gniazda UDP i chcę traktować go jako kawałek całkowity ([]int32
) bez zmiany podstawowej tablicy i odwrotnie. W C (++) po prostu rzuciłbym między typami wskaźników; jak to zrobić w Go?
6 answers
Jak mówili inni, rzucanie wskaźnika jest uważane za złą formę w Go. Oto przykłady właściwej drogi Go i odpowiednika rzutu tablicy C.
Uwaga: cały kod jest niesprawdzony.
The Right Way
W tym przykładzie używamy pakietu encoding/binary
do konwersji każdego zbioru 4 bajtów na int32
. Jest to lepsze, ponieważ określamy endianness. Nie używamy również pakietu unsafe
do łamania systemu typów.
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 (C array casting)
W tym przykładzie mówimy, aby Go zignorował system typów. Nie jest to dobry pomysł, ponieważ może zawieść w innej implementacji Go. Zakłada, że rzeczy nie mieszczą się w specyfikacji języka. Ten jednak nie wykonuje pełnej kopii. Ten kod używa niebezpiecznego dostępu do "SliceHeader", który jest powszechny we wszystkich plasterkach. Nagłówek zawiera wskaźnik do danych (tablica C), długość i pojemność. Zamiast konwertować nagłówek do nowego typu slice, najpierw musimy aby zmienić długość i pojemność, ponieważ jest mniej elementów, jeśli traktujemy bajty jako nowy typ.
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))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-13 04:50:37
Krótka odpowiedź jest taka, że nie możesz. idź i nie pozwól, aby rzucać plasterek jednego typu na plasterek innego typu. Będziesz mieć pętlę przez tablicę i utworzyć inną tablicę typu, który chcesz podczas odlewania każdego elementu w tablicy. Jest to ogólnie uważane za dobrą rzecz, ponieważ typówbezpieczeństwo jest ważną cechą go.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-12 19:33:35
Robisz to, co robisz w C, z jednym wyjątkiem-Go nie pozwala na konwersję z jednego typu wskaźnika na inny. Tak, ale musisz użyć niebezpiecznego.Wskaźnik mówi kompilatorowi, że jesteś świadomy, że wszystkie reguły są łamane i wiesz, co robisz. Oto przykład:
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)
}
OczywiĹ "cie naleĹźy uwaĺźaä ‡ na uĹźywanie pakietu" unsafe", poniewaĹź Go kompilator nie trzyma juĹź za rÄ ™ kÄ ™ - na przykĹ 'ad moĹźesz tu napisaÄ ‡ pi := (*[3]uint32)(up)
i kompilator by nie narzekaĺ', ale będzie w tarapatach.
Również, jak już zauważyli inni, bajty uint32 mogą być układane inaczej na różnych komputerach, więc nie powinieneś zakładać, że są to układy tak, jak ich potrzebujesz.
Najbezpieczniejszym podejściem byłoby odczytanie tablicy bajtów jeden po drugim i zrobienie z nich wszystkiego, czego potrzebujesz.
Alex
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-13 00:24:04
Miałem problem z nieznanym rozmiarem i poprawiłem poprzednią metodę niebezpieczną poniższym kodem. podany kawałek bajtu b ...
int32 slice is (*(*[]int)(Pointer(&b)))[:len(b)/4]
W przykładzie array to slice może być podana fikcyjna duża stała i granice slice używane w ten sam sposób, ponieważ żadna tablica nie jest przydzielana.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-30 10:10:22
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, ", "))
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-19 10:30:15
Możesz to zrobić za pomocą pakietu "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
*/
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-11 17:57:51