Go in a nutshell
  • Go in a nutshell
  • Введение
  • Родословная или Should I Go?
  • Hello, world!
  • Базовые типы данных
  • Go - установка, сборка
  • Алфавит и пакеты
  • Синтаксис
  • Приведение типов
  • Операторы
  • Константы
  • Управление потоком
  • Массивы, срезы, отображения
  • Именованные типы и структуры
  • Указатели и оператор new
  • Функции
  • Работа с параметрами командной строки
  • Завершение работы программы
  • Методы
Powered by GitBook
On this page
  • Операторы
  • Присваивание кортежу
  • Битовые операторы
  • XOR shifer
  • Вишенка на торте

Was this helpful?

Операторы

Снова - фундаменталочка

Как ни подойду я к "Go in a nutshell", как воображаемому спортивному снаряду, какой отличный ни возьму, казалось бы, старт, но нет - превзойти вторую космическую скорость и воспарить к звёздному небу высокого знания и больших свершений никак не удаётся. На деле же всё просто - от победного рывка нас удерживает необходимость формирования фундаментальных представлений, которые, как всякое знание, за плечами-то не носить, но прекрасно играют роль балласта при всякой надежде лёгкого старта. В общем, разговора об операторах нам не избежать, благо, в рассматриваемом нами языке Go сей предмет не имеет ярко выраженных особенностей. В награду же, я постараюсь привести по теме во множестве интересные практические примеры, дабы оживить наше скучное школярство.

Операторы

Дам вам сразу дефиницию - statement precedence (приоритетность операторов). Запомните это выражение, ибо оно применимо ко всем языкам программирования, и, будучи произнесённым, всегда способно создать вам амплуа матёрого кодера со знанием английского.

Приведём же сразу соответствующую таблицу бинарных операторов всех типов (арифметические, логические, битовые) в порядке уменьшения:

1

2

3

4

5

6

*

/

%

<<

>>

&

7

8

9

10

11

12

&^

+

-

|

^

==

13

14

15

16

17

18

!=

<

<=

>

&&

||

Для ясности могут (и при уместности должны) применяться скобки.

Существуют унарные операторы + и -. Их назначение ничем не отличается от обычной арифметики. Унарный плюс не несёт никакого математического смысла, унарный же минус сделает смену значения своего операнда на противоположное положительное/отрицательное.

package main

import "fmt"

func main() {
	var (
		a = -1
		b = 1
	)

	fmt.Println(-a)
	fmt.Println(-b)
	fmt.Println(+a)
}
//Результат:
//1
//-1
//-1

Существуют также такие наследия С-образных языков, как инкремент и декремент ++ и --, а также сокращённые арифметические операторы +=, -=, *=, /=. Единственное, что, пожалуй, следует запомнить, так это то, что инкремент и декремент в Go не имеют префиксных форм.

package main

import "fmt"

func main() {
	var a = 0
	a = a + 1
	fmt.Println(a) //1
	a = 0
	a += 1
	fmt.Println(a) //1
	a++
	fmt.Println(a) //2
	a--
	fmt.Println(a) //1
	++a //syntax error: unexpected ++
	--a //syntax error: unexpected --
}

Сокращённые формы применимы также и с прочими арифметическими операторами, включая битовые.

package main

import "fmt"

func main() {
	var a = 10
	a %= 3 
	fmt.Println(a) // 1
	a <<=1 
	fmt.Println(a) // 2

}

Оператор %, если кто позабыл, означает остаток от деления и, несмотря на обозначение, никакого отношения к процентам не имеет. Именно поэтому, в примере выше, 10 % 3 даёт нам в результате единицу. Оператор +, будучи применённым к строкам, вызывает их конкатенацию. Как видите, всё довольно ясно и предсказуемо. Остаётся только поблагодарить за это С-like генетику этого языка.

Присваивание кортежу

В языке Go существует интересная и полезная возможность, называемая "присваивание кортежу". Математически это выглядит просто: a, b = b, a. И происходит обмен переменных их значениями "на месте". Эта возможность становиться полезной для реализации некоторых алгоритмов, а также весьма полезна при работе с массивами и срезами, давая возможность экономить память. Но этот момент мы подробнее раскроем в главе о срезах и массивах. Сделаем лишь небольшое замечание, касающееся стиля программирования: избегайте неоправданного использования присваивания кортежу; последовательность отдельных инструкций в сложных выражениях будет читаться легче.

Битовые операторы

Битовые операторы никто не любит.

Одни слова чего стоят: амперсанд, циркумфлекс, ОРЫ/КСОРЫ... Нет, конечно, ухо любомудрого математика согреет, спору не будет. Слово доброе оно и кошке приятно. А мы для себя сначала выпишем всё в табличку, затем попытаемся разобраться, а потом и развлечься полезными примерами. Любовь зла, полюбишь и битовые операторы.

Оператор

Название

Описание

&

Побитовое И (AND)

Возвращает 1 в тех разрядах, которые у обоих операндов были равны 1.

|

Побитовое ИЛИ (OR)

Возвращает 1 в тех разрядах, которые хотя бы у одного из операндов были равны 1.

^

Побитовое исключающее ИЛИ (XOR)

Возвращает 1 в тех позициях, которые только у одного из операндов были равны 1. Имеет также унарную форму ^x, что даёт инвертирование битов операнда.

&^

Сброс бита (И НЕ) AND NOT

Сброс бита.

<<

Сдвиг влево

x << n эквивалентно x * 2^n

>>

Сдвиг вправо

x >> n эквивалентно x / 2^n

Каковы же причины использования? Во-первых, это низкоуровневое программирование, как выражаются люди причастные, "микроконтроллеров для электробалалаек". То есть такие случаи, когда необходимо опираться на двоичное представление чисел. Другой часто приводимый пример - это использование целых чисел в качестве битовых полей (масок) для компактного представления наборов каких-либо флагов-значений. Оператор XOR нередко используется для простейшего так называемого XOR-шифрования, а также как один из алгоритмов для вычисления контрольных сумм для байтовых дейтаграмм.

Представим, что некое число в пределах 00000000 - 11111111 является битовым полем, где каждый разряд является неким значимым флагом, к примеру, это могут быть права доступа, принадлежность к группе пользователей, доступ к информационной системе из -интер или интранета и тому подобные значения. Рассмотрим такой код:

package main

import "fmt"

var x uint8 = 1<<2 | 1<<5 // установка флагов

func main(){
    
  fmt.Println("Dec:", x)
	fmt.Printf("HEX: %#x\n", x)
	fmt.Printf("Bit mask: %08b\n", x)//битовое поле, дополняем разряды до 8
	
	//Вычислим, в каких позициях (точка отсчёта = 0)
	//установлены флаги
	for i:=uint(0); i <8; i++{
		if x&(1<<i) != 0{
			fmt.Println(i)
		}	
	}
}
//Результат:
//Dec: 36
//HEX: 0x24
//Bit mask: 00100100
//2
//5

Теперь рассмотрим такое техническое описание, взятое из ECR (electronic cash registrar) протокола HYPERCOM:

package main

import (
	"fmt"
)

const (
	STX = 0x02
	ETX = 0x03
)

func CreateLRC(buffer []byte) byte {
	var lrc byte
	for i, v := range buffer {
		if i == 0 {
			lrc = v
		} else {
			lrc = lrc ^ v
		}
	}
	return lrc
}

func main() {
	data := []byte{STX, 0x50, 0x4e, 0x47, 0x31, 0x30, 0x2e, 0x1c, ETX}
	lrc := CreateLRC(data[1:])
	fmt.Printf("LRC: %#x", lrc)
}
//LRC: 0x69

XOR shifer

Чаще всего изучение битовых операторов грешит схематичным, формальным, оторванным от практики подходом. Продолжим же борьбу с этим! Выше мы рассмотрели серьёзную техническую задачу - реализацию на языке Go расчёта LRC для дейтаграмм в рамках конкретного байтового протокола. Настало время сделать ещё один рывок в освоении битовых операторов в реальном контексте. Привет, симметричное XOR шифрование! Возьмём некий закрытый ключ, и попытаемся реализовать алгоритм. В коде также используются функции, циклы, срезы.

package main

import (
	"fmt"
)

func encode(pText, pKey string) []byte {
	txt := []byte(pText)
	key := []byte(pKey)
	res := make([]byte, len(txt))

	for i := 0; i < len(txt); i++ {
		res[i] = (txt[i] ^ key[i%len(key)])
	}

	return res

}

func decode(pText []byte, pKey string) string {

	res := make([]byte, len(pText))
	key := []byte(pKey)

	for i := 0; i < len(pText); i++ {
		res[i] = (pText[i] ^ key[i%len(pKey)])
	}

	return string(res)

}

func main() {
	str := "str := "Lorem ipsum dolor sit amet"
	key := "qwerty" //private key
	enc := encode(str, key)
	dec := decode(enc, key)
	fmt.Println("Encoded: " +string(enc))
	fmt.Println("Decoded: " +dec)
}
//Encoded: =YY	YR
//Decoded: Lorem ipsum dolor sit amet 

Вишенка на торте

Вишенка на торте - изыскание алгоритма для создания генератора лицензий для эмулятора ключей Sentinel super pro!

MultiKey - универсальный сборный эмулятор электронных ключей. Предназначен для более-менее точного воспроизведения (эмуляции) действий электронных ключей при работе с защищаемым приложением. Запись реестра при этом используется как хранилище дампа памяти аппаратного ключа и имеет следующий вид:

Дедукция а-ля Шерлок Холмс, метод научного подбора, проб и манипуляций со значениями дампа и системным временем, подсказывают указанную выше интерпретацию значений дампа. Далее "переворачиваем" (интерпретируем как little endian) ключ 0x63 0x0C -> 0x0C 0x63, это частая необходимость в таких случаях, всегда стоит помнить об этом. Зная дату истечения действия ключа и соответствующее ему значение 0x54 0x03, проведя пару-тройку бессонных ночей в размышлениях над алгоритмом, я получил такой генератор:

package main

import (
	"fmt"
	"encoding/binary"
	"bytes"
)

//Функция для представления int32 в виде двухбайтового "слова"
func int32To2byteArray(amount int) []byte {
	buf := new(bytes.Buffer)
	err := binary.Write(buf, binary.LittleEndian, uint16(amount))
	if err != nil {
		fmt.Println("binary.Write failed:", err)
	}
	return buf.Bytes()
}
//Декоративная функция вывода значений среза или мессива в HEX
func printBuf(b []byte){
	for _, val:=range b {
		fmt.Printf("%#x ", val)
	}
}

func main() {
//вот он, алгоритмище!
res:=12 * ((0xc63&0xFF) - (2028 - 2000)) + ((0xc63 >> 8) & 0xFF) - (9 + 1) + 1 // Y 28, M 9 example

regOptions := make([]byte, 2)
regOptions=int32To2byteArray(res)
printBuf(regOptions)

}
//Результат
//0x57 0x3

Как мы могли видеть, в каждом случае нам очень помогли битовые операторы. Вышло так, что мы уделили им весьма пристальное внимание. Автор очень надеется, что ему удалось изложить тему битовых операторов максимально разнообразно и приближённо к реальным условиям использования. Очень хотелось вырваться из рамок обычного в этом вопросе формализма и схоластики. Надеюсь, это в определённой мере удалось.

PreviousПриведение типовNextКонстанты

Last updated 4 years ago

Was this helpful?

Данный протокол - низкоуровневый байт-код, представляющий собой дейтаграммы. Ранее, в таблице соответствий , вы могли познакомиться с такими значениями, как STX (0x02) и ETX (0x03). Для лучшего понимания уточню, что данные ASCII коды имеют семантическое значение соответственно "Start of text" и "End of transmission". LRC же в свою очередь не является ASCII кодом, а обозначает разновидность контрольной суммы - Longitudinal Redundancy Check. Для исчисления LRC нет единого алгоритма, тем более если брать во внимание всё множество байтовых и текстовых протоколов, обычно используемых в тех или иных уровнях сетевой модели или при программировании последовательных портов. В данном случае мы имеем дело с так называемой XOR-based реализацией. В коде используем константы, цикл, срез и функцию, так что, просьба сохранять спокойствие. Снимем ограничитель простоты кода в разумных приделах, и, полагаю, всё разложится по полкам в своё время. Нарочитое упрощение с определённого момента создаёт ощущение ненужного инфантилизма. Перейдём к коду.

Hex to ASCII text conversion table
й