В системном программировании постоянно приходится производить манипуляции с битами и байтами. Именно о таких манипуляциях пойдет речь в этой статье.
Типы
В Go есть типы, которые позволяют точно задать размер числовой переменной:
- uint8 – 1 байт
- uint16 – 2 байта
- uint32 – 4 байта
- uint64 – 8 байт
Префикс "u" означает, что значение переменной беззнаковое, то есть 0, 1, 2 и так далее.
Без префикса "u" число будет знаковым, для int8 от -128 до 127. Отрицательные числа кодируются дополнительным кодом.
Типы int и uint лучше не использовать, так как на разных платформах они имеют разный размер, обычно 32 бита или 64 бита. Тип переменной нужно прописывать явно:
var x uint16 = 123
Если не указать тип uint16, то переменная будет типа int.
Поскольку тип uint8 хранит байт, то для этого типа есть синоним – byte. То есть byte и uint8 – это одно и тоже. Там, где нужно передать byte, можно передавать uint8, и наоборот, ошибки не будет.
Набор байт можно представить ввиде массива:
b := []byte{0, 1, 2, 3}
Многобайтовые последовательности можно хранить как в перменных типа uintN, так и в массивах. Но при использовании uintN нужно задуматься о порядке байт: little-endian или big-endian. Порядок байт зависит от платформы. Например, переменная типа uint16 содержит число, состоящее их 2-x байт, в памяти эти два байта могут располагаться по-разному, в зависимости от платформы (процессора, операционной системы). При переборе ячеек памяти, младший байт может идти перед старшим, а может наоборот. Чаще всего младший байт идет вперед, такой порядок называется little-endian.
В пакете encoding/binary есть методы для преобразования uintN в массив байт и обратно.
Функции для преобразования числа (uint16) в массив байт:
binary.LittleEndian.Uint16([]byte) uint16
binary.BigEndian.Uint16([]byte) uint16
binary.NativeEndian.Uint16([]byte) uint16
Функции для преобразования массива байт в число (uint16):
binary.LittleEndian.PutUint16([]byte, uint16)
binary.BigEndian.PutUint16([]byte, uint16)
binary.NativeEndian.PutUint16([]byte, uint16)
Числа
Go позволяет записывать числа в двоичном и шестнадцетиричном формате. А так же в восьмеричном (но кому он нужен?).
Для удобства восприятия, разряды можно разделять подчеркиванием.
Примеры:
- двоичное число: 0b1010_1111
- шестнадцетиричное число: 0xF010_1234
Битовые операции
Операция | Пример |
---|---|
И | result = a & b |
ИЛИ | result = a | b |
Ислючающее ИЛИ | result = a ^ b |
Инверсия | result = ^a |
И-НЕ | result = a &^ b |
Сдвиг влево | result = a << b |
Сдвиг вправо | result = a >> b |
Эти операции в сочетании с присваиванием:
Операция | Эквивалент |
---|---|
b &= a | b = b & a |
b |= a | b = b | a |
b ^= a | b = b ^ a |
b &^= a | b = b &^ a |
b >>= a | b = b >> a |
b <<= a | b = b << a |
В отличии от языка C в Go появилась новая операция – И-НЕ. Это операция И, но с инверсий второго аргумента, то есть "a И (НЕ b)". Такая операция часто используется для обнуления бита:
a &^= 1<<n
Пример. В результате следующей операции в переменной "a" биту под номером "n" будет присвоен 0.
a := 0b1111
a &^= (1 << 2) // Результат: 1011
Биты считаются с нуля справа налево, как и в Си.
Строковое представление
Различные байтовые и битовые представления можно выводить в консоль или получать ввиде строки, с помощью форматирования строк:
// печатает в консоль
fmt.Printf(format, a, b, c)
// возвращает строку
fmt.Sprintf(format, a, b, c)
format – форматная строка, определяет формат результирующей строки.
// Напечатает "1 2 3"
fmt.Printf("%d %d %d", 1, 2, 3)
В форматной строке можно использовать следующий подстановки:
- %x – данные в шестнадцетиричном виде, буквы в нижнем регистре (a-f)
- %X – данные в шестнадцетиричном виде, буквы в верхнем регистре (A-F)
- %#b, %#x или %#X – аналогично предыдущим, но благодаря решетке, в начале добавляется префикс 0b, 0x или 0X соответственно.
С помощью этих подстановок можно выводить как обычные числа так и массивы:
// Рузультат: "AB"
fmt.Printf("%X", 0xAB)
// Рузультат: "ABCD"
fmt.Printf("%X", []byte{0xAB, 0xCD})
Зафиксировать длину числа в строке и дополнить число ведущими нулями можно с помощью подстанвоки "%0w", где w – длина строкового представления числа.
// Рузультат: "00AB"
fmt.Printf("%04X", 0xAB)
Без нуля "0" строка будет дополняться пробелами:
fmt.Printf("%4X", 0xAB) // Рузультат: " AB"