Изображение кода выше просто для привлечения внимания

Услышав похвалу, которую получает Go(lang), и увидев спрос на него, ваш покорный слуга решил изучить его. А что может быть лучше для изучения языка программирования, чем написать на нем проект? Вот на чем будет написан мой проект под названием DAM (Data Action Model).

Проект — по крайней мере в идеале — предназначен для предоставления бесконечно гибкого решения для хранения и доступа к данным. Да, я знаю, мне это еще предстоит задокументировать, поэтому мы пропустим некоторые детали, но DAM придется работать с большими двоичными объектами данных с непредсказуемой структурой. И поэтому было немного разочарованием обнаружить, что Go не совсем язык для арифметики указателей. Указатели существуют и могут использоваться, но Go является строго типизированным языком, в котором тип данных, с которым связан указатель, тщательно отслеживается, и никакие легкомысленные модификации значения указателя или ассоциации его типов не допускаются.

Давайте воспользуемся Go Playground (действительно очень удобный ресурс), чтобы посмотреть, какие варианты доступны разработчику, которому нужна возможность гибкой обработки данных.

Сначала давайте попробуем посмотреть, можем ли мы просто назначать значения указателя друг другу для прямого доступа к данным. Давайте попробуем запустить код ниже в Playground.

package main
import "fmt"
func main(){
    var byte_arr []byte = []byte{'a', 'b', 'c', 'd'}
    var b_ptr *[]byte = &byte_arr
    // Trying to transfer a 4-byte arr into a 32-bit unsigned integer.
    var i0 uint32
    var i0_ptr *uint32
    i0_ptr = b_ptr
    i0 = *i0_ptr
    fmt.Printf("i0 = %x\n", i0)
}

Как и ожидалось, мы получили следующий результат:

./prog.go:13:16: cannot use b_ptr (type *[]byte) as type *uint32 in assignment
Go build failed.

Столкнувшись с этой проблемой, ваш покорный слуга решил искать другие варианты. Оказалось, что понятия union (как в C/C++) в Go не существует. Затем я подумал об использовании эквивалента недействительного указателя, то есть универсального указателя. Кажется, это тоже не вариант в Go.

Go предлагает концепцию интерфейса, которая очень похожа на интерфейс в других языках, таких как Java, — описание данных, методов и функций, которые должны быть реализованы в соответствии с различными типами данных. Это также реализует перегрузку. Go также предоставляет концепцию пустого интерфейса:

interface{}

Переменной, определенной как пустой интерфейс, также можно присвоить любое значение. Но работает ли это в обратном порядке — даже если типы совместимы по размеру?

package main
import (
    "fmt"
)
func main() {
    var ei0 interface{}
    var ei1 interface{}
    var ui32_0 uint32 = 165
    var fl32_0 float32 = 0.134
    var fl32_1 float32
    ei0 = ui32_0
    ei1 = fl32_0
    fl32_1 = ei0
    fmt.Printf("fl32_1 = %f\n", fl32_1)
}

Выполнение вышеуказанного дает следующее:

./prog.go:16:9: cannot use ei0 (type interface {}) as type float32 in assignment: need type assertion
Go build failed.

Таким образом, попытка отобразить 4-байтовое целое число в 4-байтовое число с плавающей запятой через пустой интерфейс не удалась. Посмотрим, сработает ли преобразование типов.

package main
import (
    "fmt"
)
func main() {
    var ei0 interface{}
    var ei1 interface{}
    var ui32_0 uint32 = 165
    var fl32_0 float32 = 0.134
    var fl32_1 float32
    ei0 = ui32_0
    ei1 = fl32_0
    fl32_1 = float32(ei0)
    fmt.Printf("fl32_1 = %f\n", fl32_1)
}

Результат:

./prog.go:16:18: cannot convert ei0 (type interface {}) to type float32: need type assertion
Go build failed.

Так что это тоже не работает. Да, Go тщательно отслеживает типы! Таким образом, перекрестные типы сопоставления на основе указателей не подходят.

Итак, в конечном итоге приходится использовать массивы в качестве ссылки и математические функции для сопоставления типов данных с плавающей запятой.

Давайте теперь посмотрим, как можно манипулировать массивами по ссылке.

package main
import (
    "fmt"
)
func byte_arr_modify(a *[4]byte, position int, new_value byte){
    a[position] = new_value
}
func main() {
    var b_arr [4]byte
    b_arr[0] = 'a'
    b_arr[1] = 'b'
    b_arr[2] = 'c'
    b_arr[3] = 'd'
    fmt.Printf("prior to modification: b_arr = %s\n", b_arr)
    byte_arr_modify(&b_arr, 2, 'S')
    fmt.Printf("after modification: b_arr = %s\n", b_arr)
}

Обратите внимание, что массив b_arr передается по ссылке. Теперь давайте рассмотрим вывод:

prior to modification: b_arr = abcd
after modification: b_arr = abSd

Итак, у нас есть способ изменить любой байт в массиве байтов любым удобным для нас способом. Вы также можете использовать побитовые операторы для изменения любого целочисленного типа.

Например, здесь мы можем использовать тот же 4-байтовый массив для представления 32-битного целого числа и манипулировать им.

package main
import (
   "fmt"
)
func byte_arr_modify(a *[4]byte, incrementby uint32){
    var int_value uint32 = 0
    int_value += uint32((*a)[0])
    int_value += uint32((*a)[1]) << 8
    int_value += uint32((*a)[2]) << 16
    int_value += uint32((*a)[3]) << 24
    int_value += incrementby
    (*a)[0] = byte(int_value & 0x000000ff)
    (*a)[1] = byte((int_value >> 8) & 0x000000ff)
    (*a)[2] = byte((int_value >> 16) & 0x000000ff)
    (*a)[3] = byte((int_value >> 24) & 0x000000ff)
}
func main() {
    var b_arr [4]byte
    b_arr[0] = 'a'
    b_arr[1] = 'b'
    b_arr[2] = 'c'
    b_arr[3] = 'd'
    fmt.Printf("prior to modification: b_arr = %s\n", b_arr)
    byte_arr_modify(&b_arr, 256)
    fmt.Printf("after modification: b_arr = %s\n", b_arr)
}

Запуск приведенного выше кода дает нам следующее:

prior to modification: b_arr = abcd
after modification: b_arr = accd

Как мы видим, код действовал, как и ожидалось, рассматривая 4-байтовый массив как представление 32-битного целого числа без знака в модели адресации с прямым порядком байтов — мы добавили к нему 256, и он передвинул позицию второго байта на 1.

Теперь давайте повеселимся с числами с плавающей запятой, хранящимися в виде байтов.

package main
import  "fmt"
import "math"
func main(){
    var b0 byte = 0x08
    var b1 byte = 0xAA
    var b2 byte = 0x11
    var b3 byte = 0x22
    var i1 uint32
    var i2, i3 uint32
    var f1 float32
    var f2 float32
    var f3 float32
    i1 = (uint32(b1) << 8 | uint32(b0)) | (uint32(b2) << 16) | (uint32(b3) << 24)
    f1 = (float32)(i1)
    fmt.Printf("i1 = %x\n", i1) 
    fmt.Printf("f1 = %e\n", f1)
    f2 = math.Float32frombits(i1)
    f3 = math.Float32frombits(i1 ^ 0x80000000)
    i2 = math.Float32bits(f2)
    i3 = math.Float32bits(f3)
    fmt.Printf("f2 = %e\nf3 = %e\ni2 = %x\ni3 = %x\n", f2, f3, i2, i3)
}

Выход:

i1 = 2211aa08
f1 = 5.715830e+08
f2 = 1.974118e-18
f3 = -1.974118e-18
i2 = 2211aa08
i3 = a211aa08

Как видите, с помощью функций math.Float32frombits и math.Float32bits можно преобразовать 32-битное целое число в число с плавающей запятой и наоборот. Преобразование работает туда и обратно правильно. Я добавил двоичный битовый флип, чтобы переключить знак для значения с плавающей запятой, закодированного как целое число, и это тоже сработало.

Представленные выше методы позволяют хранить и получать доступ ко всем основным типам данных в массиве байтов в Go.

Ссылки

Go

Go: fmt пакет

Пакет Займись математикой

Игровая площадка Го

Почему вам следует использовать язык программирования Go
Рутва Сафи, Softweb Solutions Inc., 13 октября 2020 г.

ДАМ (GitHub)

Endianess
freeCodeCamp

Ссылки на социальные сети

Местные

Гэб

Разумы

Геттр

Фейсбук

Веб-сайт

Борисепштейн.инфо

Поддержка

Подписаться

Патреон

Первоначально опубликовано здесь 4 октября 2021 г.