Туториал по Golang

Aus archium.org
Wechseln zu: Navigation, Suche



Inhaltsverzeichnis

😈 Язык программирования Go ("Golang") 😈

Почему иммено Go? Внимание - субъективно!

Go- это придуманный с нуля эстетически приятный язык программирования.

Оффициальный маскот – Go-Gopher, созданный Реной Френч (Renee French)

Это перевод текста! Показать оригинал (De.)

Если посмотреть на популярные рейтинги языков программирования (напр. PyPl-Index, Tiobe-Index, и Languish) то можно заметить, что, несмотря на неточность подсчётов, такие давние лидеры, как C/C++ и Java, всё же теряют популярность. Несомненный фаворит разработчиков и новый номер 1 на 2022 год — это язык Python. И это не удивительно, ведь программировать на таком языке удобно и просто. Но Python - это язык скриптов , и ограничен в производительности. Сегодня продвинулось много новых компиляторов, в которых разработчики начали придавать большее значение безопасности типов, чем разработчики компиляторов языков с динамическими интерпретаторами (напр. Python, PHP и Perl).Одной из причин этого может быть более медленное увеличение производительности при разработке аппаратного обеспечения, а это означает, что (как и 30 лет назад) повышение производительности должно достигаться за счет оптимизации кода, как описал Эдоардо Паоло Скалафиотти на Hackernoon (Eng.) 16 октября 2016 г.

Так же интересно, что несмотря на то, что большие IT-гиганты могут позволить себе разработку собственных языков программирования, в 2024, к счастью, они всё ещё обязаны делать эти разработки с открытым исходным кодом. Из таких языков я нахожу перспективными Swift (от Apple), Go (от Google) и Rust (от Mozilla). Основные возможности языка Go кратко изложены ниже. По моему личному мнению, по сравнению со Swift и Rust в пользу Go говорит ресурсосберегающий и в то же время очень эстетичный минимализм, а также по-новому или иначе задуманная объектная ориентация. А это значит, что разработчики используют привычные конструкции типа определение класса, но для перехвата исключений блоки try-catch больше не нужны. Go сочетает в себе, среди прочего, безопасность типов и модель пакетов как в Java, прагматичный синтаксис как в Python и указатели как в C. Документация также невероятно прозрачна. И ни с каким другим языком программирования параллелизм и взаимодействие между параллельно работающими подпрограммами не являются столь же тривиальными, как с Go.

Но поскольку критические вопросы часто предшествуют самому яркому энтузиазму, здесь также следует прочитать «5 вещей, которые вы можете (но не должны) ненавидеть в Go», которые Кристоф Энгельберт опубликовал 10 апреля 2018 года на jaxenter.de. Таким образом, каждый может понять, видит ли он в особенностях этого языка преимущества или же недостатки.

Шпаргалка по Golang ("Cheat Sheet")

Компилятор

Gc

Gc это компилятор языка Go. С версии 1.5. был переписан на самом Go. Он создает статические двоичные файлы, но также позволяет выполнять программу напрямую — как и в случае с языками сценариев!

Создайте статический двоичный файл

$ go build byebyeJava.go
$ ./byebyeJava

Сразу же запустите "скрипт"

$ go run byebyeJava.go

Скомпилируйте внешние пакеты

$ go install [packages]

Загрузите внешние пакеты из репозитория

$ go get github.com/therecipe/qt
$ go get -u github.com/golang/lint/golint # -u = Загрузить обновление для существующих пакетов

Загляните в Assembler-Code

Посмотрите https://golang.org/doc/asm (Eng.)

$ go build -gcflags -S byebyeJava.go

Компиляция с Makefile

# Basic Go makefile

GOCMD=/usr/local/bin/go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get


all: build test clean

build: 
        $(GOBUILD) -v
dbg-build:
        $(GOBUILD) -v -gcflags=all="-N -l" -tags debug
test: 
        $(GOTEST) -v ./...
clean: 
        $(GOCLEAN)

Запустите, например, с помощью:

$ make
$ make all
$ make build

Gccgo

Gccgo — это внешний интерфейс (фронтэнд) GCC для Go. Компилятор написан на С++. Gccgo отстает от Gc с точки зрения реализации области языка. Так же Gccgo может быть медленнее, но при компиляции он генерирует более эффективный машинный код и динамически подключаемые двоичные файлы!

$ gccgo -Wall -O2 -o byebyeJava byebyeJava.go
$ ./byebyeJava

Cross-Compiling

(См. https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 для полного списка переменных среды GOOS и GOARCH!!)

В Linux создайте 32bit-.exe-Файл для Windows

$ GOOS=windows GOARCH=386 go build byebyeJava.go

В Linux создайте 64bit-.exe-Файл для Windows

$ GOOS=windows GOARCH=amd64 go build byebyeJava.go

Создайте исполняемый файл для Raspberry PI2 (ARMv6 (De.))

$ GOOS=linux GOARCH=arm GOARM=6 go build byebyeJava.go

Создайте исполняемый файл для Raspberry PI3 (ARMv7 (De.))

$ GOOS=linux GOARCH=arm GOARM=7 go build byebyeJava.go

Создайте исполняемый файл для 64-разрядных процессоров ARM (ARMv8, например Rock64Ubuntu-Touch на телефоне Volla)

$ GOOS=linux GOARCH=arm64 go build byebyeJava.go # GOARM=8 было бы недопустимо!

См. https://github.com/golang/go/wiki/GoArm

Создайте 64-битный исполняемый файл для Linux в Windows

$ GOOS=linux GOARCH=amd64 go build byebyeJava.go

Кросс-компиляция в Linux для Windows с помощью MXE

MXE (M cross environment) значительно упрощает генерацию кода для многих (бесплатных) библиотек Windows (например, SDL, Qt, GTK, ImageMagick и т. д.) под Linux, просто создавая необходимую инфраструктуру компиляторов и библиотек через Makefile:

  • Установка MXE
$ cd /mein/wunsch/pfad/zu/
$ git clone https://github.com/mxe/mxe.git
$ cd mxe
  • Создать напр. все пакеты для всех целей установки
$ make MXE_TARGETS='i686-w64-mingw32.static i686-w64-mingw32.shared x86_64-w64-mingw32.static x86_64-w64-mingw32.shared'
  • Но изначально достаточно сгерировать GCC-компилятор только для требуемой цели установки
$ make MXE_TARGETS='x86_64-w64-mingw32.static' gcc
Примечание
переменная среды MXE_TARGETS также может быть определена в файле «settings.mk», после чего указывать ее не требуется.
  • Дополнительные пакеты могут быть легко установлены. Например, qt5. (Также возможны несколько заданий и процессов компиляции!)
$ make qt5 --jobs=4 JOBS=2
Примечание
подробная информация о целях установки и доступных для выбора пакетах: https://mxe.cc/#usage и https://mxe.cc/#packages
  • Скомпилируйте go на 64-битном Linux для 32-битного Windows
$ cd /mein/projekt/pfad
$ CGO_ENABLED=1 CC=/mein/wunsch/pfad/zu/mxe/usr/bin/i686-w64-mingw32.static-gcc CXX=/mein/wunsch/pfad/zu/mxe/usr/bin/i686-w64-mingw32.static-g++ GOOS=windows GOARCH=386 go build -o meineApp.exe .
Конкретно
CGO_ENABLED=1 CC=/usr/local/src/mxe/usr/bin/i686-w64-mingw32.shared-gcc CXX=/usr/local/src/mxe/usr/bin/i686-w64-mingw32.shared-g++ GOOS=windows GOARCH=386 go build -o meineApp.exe

Пакеты

Структура каталогов

Важные переменные среды

Установка системных файлов Go
$ export GOROOT='/usr/local/go'
Рабочее пространство для своего кода
$ export GOPATH='/home/me/workspace/'
Собственные Git-репозитории для модулей go
$ export GOPRIVATE='*.schnulli.com,git.home.at/myZeugs'

Каталоги, которые Go создает в рабочем пространстве

src
Исходный код (расширение .go)
Этот каталог содержит пакеты в подкаталогах. Он содержит собственные исходные коды.
bin
Бинарный код (без расширения)
Этот каталог содержит исполняемые программы с любым именем.
pkg
Бинарный код (расширение .a)
Этот каталог содержит внешние объекты пакета в скомпилированной форме.

Импортирование пакетов

package main

import (
	"fmt"
	_ "image" // Загружено, но не используется (нет сообщения об ошибке)
	"math"
	"math/rand"
	sc "strconv"
	. "strings"
	t "time"
)

/*
// Альтернативное написание
import "fmt"
import _ "image"
*/

/*
//Локальные пути — это относительные пути, которые начинаются с "./"
import ./MyPackage
*/

func main() {
	fmt.Println(math.Sqrt(9))
	rand.Seed(t.Now().UnixNano())        // Генератор случайных чисел требует ввода переменной
	fmt.Println(rand.Intn(100))          // А не что-то вроде: math/rand.Intn(100)
	fmt.Println(ToLower("BYE BYE JAVA")) // А не: fmt.Println(strings.ToLower("BYE BYE JAVA"))
	fmt.Printf("%s сейчас строка\n", sc.FormatFloat(0.12345, 'G', -1, 64))
}

Создание пакетов

Наименование

package myPackageName // Пакет всегда объявляется в заголовке 
                      // Исходники (допускаются пробелы и предшествующие комментарии)
package main // Исполняемые файлы всегда должны принадлежать пакету *main*
package rand // Имя пакета — это всегда последнее имя пути импорта. (import path math/rand => package rand)

Разброс данных

  • Если разные файлы с расширением .go назначены одному и тому же пакету внутри каталога, компилятор интерпретирует их как один непрерывный файл. ⇒ Таким образом, например, функции можно легко сохранять в отдельных файлах во время написания кода.

модули Go

  • Начиная с версии Go 1.11 стало возможным, а с версии 1.13 стало стандартным создание проектов вне $GOPATH. Информация о пути находится в файле go.mod в папке проекта. Файл go.mod создается через:
$ go mod init mein/projekt/pfad

Соответственно

$ go mod init

если import comment после оператора package в коде указывает на этот путь:

// goModTest project main.go
package main // import "mein/projekt/pfad"
Пример
  • Создайте код Go ...
// goModTest project main.go
package main // import "goModTest"

import (
	"fmt"
	"os"

	"github.com/hackebrot/turtle"
)

func main() {
	name := "angry"
	emoji, ok := turtle.Emojis[name]

	if !ok {
		fmt.Fprintf(os.Stderr, "Эмоджи \"%v\" не найден\n", name)
		os.Exit(1)
	}

	fmt.Println(emoji.Char) //😠
}
  • ... и создайте файл go.mod ...
$ go mod init
  • Загрузите зависимости
$ go mod tidy
  • Создается следующий файл go.mod:
module goModTest

require (
        github.com/google/go-cmp v0.2.0 // indirect
        github.com/hackebrot/go-repr v0.0.0-20170331152400-586d894a5734 // indirect
        github.com/hackebrot/turtle v0.1.0
)
  • Затем код также может быть скомпилирован за пределами $GOPATH.
  • Собственные репозитории Git можно открыть с помощью $GOPRIVATE.

Видимость переменных и функций

  • Идентификаторы, начинающиеся с заглавных букв, являются общедоступными(public), поэтому они видны в других пакетах
  • идентификаторы, начинающиеся со строчных букв, являются частными(private), т. е. остаются невидимыми для других пакетов

Стандартные функции

func main(){} // Исполняемые программы всегда должны иметь функцию main().
func init(){} // Функция init() находится внутри пакета и выполняется при загрузке пакета.

Несколько функций init() также можно вызывать одну за другой, а глобальным переменным можно присваивать значения:

package main

import "fmt"

var counter int

func init() {
	fmt.Println("Первая функция init была вызвана") //1
	counter++
}

func init() {
	defer fmt.Println("была вызвана")  //3
	fmt.Print("Вторая функция init ") //2
	counter++
}

func main() {
	defer fmt.Println("(А затем с помощью defer функция может выполняться даже *после* основной функции!)") //6
	fmt.Println("Функция main была вызвана")                                                              //4
	fmt.Printf("Функция init была вызвана %d раз(-а)\n", counter)                                         //5
}

/*
Первая функция init была вызвана
Вторая функция init была вызвана
Функция main была вызвана
Функция init была вызвана 2 раз(-а)
(А затем с помощью defer функция может выполняться даже *после* функции main!)
*/

Функции

Простые функции

// передача параметров ...
func functionName0si(param1 string, param2 int) {}

// ... можно представить в упрощенном виде с тем же типом.
func functionName0ii(param1, param2 int) {}

// Возвращаемое значение объявляется внизу функции ...
func functionName1() int {
	return 123
}

// ... и может возвращать *несколько* параметров!
func functionName2() (int, string) {
	return 123, "Blabla"
}

var x, y = functionName2()

Возвращаемые параметры также могут быть именованными, тогда оператор return не получает никаких аргументов, а возвращаемые параметры объявляются в заголовке функции:

func functionName2() (i int, s string) {
	i = 123
	s = "Blabla"
	return // возвращает i и s
}

var x, y = functionName2()

Переменные параметры (вариативные функции)

  • Три точки ... непосредственно перед последним типом в заголовке функции позволяют функции принимать неограниченное количество параметров этого типа. Затем функция вызывается так же, как и любая другая функция, с той лишь разницей, что мы можем передать ей любое количество аргументов.
  • Три точки ... сразу после переменной при вызове функции передают в качестве аргументов элементы списка по отдельности.
  • Она так же может не иметь параметров для передачи
package main

import (
	"fmt"
)

func main() {
	fmt.Println(adder(1, 2, 3)) // 6
	fmt.Println(adder(9, 9))    // 18
	fmt.Println(adder())        // 0

	nums := []int{10, 20, 30}
	fmt.Println(adder(nums...)) // 60
}

func adder(args ...int) int {
	total := 0
	for _, v := range args { // Перебирание всех аргументов
		total += v
	}
	return total
}

Лямбда-функции или тернарный оператор? Литералы функций! (анонимные функции как переменные)

//Функции как значения
func main() {
	// Помните, что функции также являются типом данных! Можно объявлять переменные 
	// как функцию и вызывать функцию через переменную:
	var add func(int, int) int
	add = func(a, b int) int { return a + b }
	fmt.Println(add(3, 4))

	// Немного короче с выводом типа:
	min := func(a, b int) int {
		if a < b {
			return a
		} else {
			return b
		}
	}
	fmt.Println(min(1,2)) // min(1,2) = 1

	// Но даже это не обязательно. Параметры можно передавать
 	// напрямую в анонимные функции: 
	fmt.Println(func(a, b int) int { if a > b { return a } else { return b }}(3, 4))// max(3,4) = 4
}

Видимость переменных в анонимных функциях

package main

import (
	"fmt"
)

func altera(a int) {
	a = 2
	fmt.Println(a)
}

func main() {
	a := 1
	fmt.Println(a) // Значение переменной 1
	// Использованние названной функции
	altera(a)      // Поменялось на 2
	fmt.Println(a) // Снова 1

	// Анонимная функция без отправки
	func() {
		a = 3
		fmt.Println(a) // Поменялось на 3
	}()
	fmt.Println(a) // Значение осталось 3 !!!

	// Отправка в анонимную функцию
	func(a int) {
		a = 4
		fmt.Println(a) // поменялось на 4
	}(a)
	fmt.Println(a) // снова 3
}

  • Анонимные функции — единственный случай, когда можно объявить функцию внутри другой функции!

Тип данных функции

  • В Go функция также имеет специфичный для нее тип данных.
package main

import "fmt"

func main() {
	var p func(...interface{}) (int, error)
	p = fmt.Print //Функция БЕЗ передачи параметров = тип данных функции
	p("Hello")

	pl := fmt.Println //На практике, конечно, это немного компактнее
	pl(" World")
} //Hello World

Функции с памятью: замыкания (когда функции возвращают функции)

  • Замыкающие функции используют возможность чтения переменных в открывающем контексте и записи в свои собственные копии этих переменных! То, как это работает, логично только на второй взгляд: включенная функция будет читать переменную из контекста открытия при первом вызове, делать копию и работать со своей копией при всех последующих вызовах. Она запомнит статус собственной копии!
package main

import (
	"fmt"
)

func main() {
	var cf func() int
	cf = myFunction()
	// то же самое, что и:
	// cf := myFunction()

	fmt.Println(cf()) //1
	fmt.Println(cf()) //2
	fmt.Println(cf()) //3

	fmt.Println(myFunction()()) //1
	fmt.Println(myFunction()()) //1
	fmt.Println(myFunction()()) //1
}

func myFunction() func() int { // Функция myFunction возвращает анонимную функцию, которая возвращает значение типа int.
	// Но учтите: это тип не "int", а "func() int",и вместе "func() int" образуют возвращаемое значение!
	// Чистый вид функции: "func myFunction() (func() int){}"
	var outerVar = 0 // outerVar := 0
	myAnswer := func() int {
		outerVar++
		return outerVar
	}
	return myAnswer
}

  • Конечно, параметры передачи также разрешены:
package main

import (
	"fmt"
	"strconv"
)

func main() {
	cf := myFunktion()
	fmt.Println(cf("a")) //a1
	fmt.Println(cf("b")) //b2
	fmt.Println(cf("c")) //c3
}

func myFunktion() func(string) string {
	outerVar := 0
	myAnswer := func(inputline string) string {
		outerVar++
		return inputline + strconv.Itoa(outerVar)
	}
	return myAnswer
}

  • При замыканиях обратите внимание, когда и где происходит изменение! Как и все подчиненные языковые структуры, переменные объявляются снаружи, но не изменяются внутри, а копируются!
package main

import (
	"fmt"
)

func main() {
	a, b := outerFun()
	c := outerFun2()

	fmt.Println(a(), b) // Внимание, не забудьте скобки!
	fmt.Println(a, b)   // ... в противном случае возвращает адрес

	// Соответственно, для прямого вызова функции нужны *две* скобки!
	fmt.Println(outerFun2()()) // => 1
	fmt.Printf("%v %v %v\n", c(), c(), c()) // => 2 3 4
}

func outerFun() (func() int, int) {
	outerVar := 4
	innerFun := func() int {
		outerVar += 10 // Изменение переменной видно только внутри функции
		return outerVar
	}
	return innerFun, outerVar // => 14, 4
}

var i int // При объявлении вне функции externalFun2 переменная i также видна анонимной функции внутри ...

func outerFun2() func() int {
	// ... как бы это выглядело, если бы оно было объявлено в функции externalFun2:
	//	var i int
	return func() int {
		i++
		return i
	}
}

  • Однако переменные всегда должны быть объявлены заранее
// Пример неправильного кода:
func outerFun3() func() int {
	// Здесь отсутствует обьявление переменной (Например такое, как "var i int" или "i := 0")
	return func() int {
		i += 1
		return i
	}
}

Рекурсивный вызов функции

Пример:

package main

import "fmt"

//Отсылка на песню iron maiden - seventh son of a seventh son
func SeventhSonofaSeventhSon(age int8, probability float32) float32 {
	age++
	probability = probability * 1 / 7
	fmt.Printf("Поколение: %d; Вероятность: %.5f%%\n", age, probability)
	if age < 7 {
		SeventhSonofaSeventhSon(age, probability)
	}
	if age != 1 {
		return probability
	} else {
		return 100
	}

}

func main() {
	fmt.Printf("%.0f%% Iron Maiden 👍😁\n", SeventhSonofaSeventhSon(0, 100))
}

Управление памятью

примечание
new(Type) это встроенная функция для выделения (или резервирования) памяти. new(Type) возвращает указатель *Type на адрес памяти.
a := new(int)
*a = 23
fmt.Println(a,*a) //0xc42009c010 23
make(Type, Length, Capacity) встроенная функция для выделения (или резервирования) памяти для слайсов, карт и каналов. make(Type, Length, Capacity) возвращает значения типа Type.

Встроенные типы(Built-in Types)

default: false

bool // (true или false)

default: ""

string

default: 0

int8 // (от -128 до 127)
int16 // (от -32768 до 32767)
int32 // (от -2147483648 до 2147483647)
int64 // (от -9223372036854775808 до 9223372036854775807)
int // соответствует int32 или int64 в зависимости от архитектуры системы.
uint8 // (от 0 до 255)
uint16 // (от 0 до 65535)
uint32 // (от 0 до 4294967295)
uint64 // (от 0 до 18446744073709551615)
uint // uint соответствует uint32 или uint64 в зависимости от архитектуры системы.
byte // другое имя uint8
rune // другое имя int32, представляет кодовую точку Unicode
uintptr  // целое число без знака, достаточно большое, чтобы содержать
         // растровое изображение любого другого указателя. 
         // Нужен для арифметики указателей.

default: 0.0

float32 // Числа с плавающей точкой. Согласно IEEE-754 размером 32 бита (8-битная экспонента, 23-битная мантисса)
float64 // Числа с плавающей точкой. Согласно IEEE-754 размером 64 бита (экспонента 11 бит, мантисса 52 бита)

default: (0+0i)

complex64 // Комплексные числа с вещественной частью float32.
          // Вводить значение надо включая мнимую часть i, например: (1+2i)
complex128 // Комплексные числа с вещественной частью float64.

default: nil

pointers
functions
interfaces
arrays
slices
channels
maps
error // Тип для обработки ошибок

Литералы

Строковые литералы (string literals)

fmt.Println("Для строк требуется обратная косая черта в качестве \"кода перехода(Escape-Code)\" и в ней распознаются точки Unicode, такие как \u039B или \U0000039B, а так же символы перехода, такие как табуляция\t или новая строка\n")
fmt.Println(`Необработанные строки также содержат "кавычки", но не знают точек Unicode, например \u039B. Так же эти строки не могут распознавать символы перехода, например \n`)

Примечание о многострочных строках
Необработанные строки(Raw-Strings) могут состоять из нескольких строк! Простые строки(Strings) могут состоять только из одной строки, а отдельные строки должны быть соединены с помощью +.
Сравните 
`Строка 1
Строка 2
Строка 3`
 с 
"Строка 1" +
"Строка 2" +
"Строка 3"

Отдельные символы (rune literals)

fmt.Println(string('H'), 'a', string('l'), 'l', 'o') //H 97 l 108 111
fmt.Println([]rune{'A', 'B', 'C'})                   //[65 66 67]
fmt.Println([]byte{68, 69, 70})                      //[68 69 70]
fmt.Println(string([]rune{'A', 'B', 'C'}))           //ABC
fmt.Println(string([]byte{68, 69, 70}))              //DEF
fmt.Println(rune('ŋ'))                               // 331
fmt.Println(string(rune('ŋ')))                       // ŋ
fmt.Println(([]rune(string("ŋ")))[0])                // 331 // Преобразование строки в руну

Целые числа (integer literals)

fmt.Println(255, 0xff, 0Xff, 0o377, 0O377, 0377, 0b11111111, 0B11111111)
/* Число 255 в десятичной, шестнадцатеричной, восьмеричной и двоичной системе счисления.
Обратите внимание, что до версии go1.13 восьмеричное число записывалось без о/O; 
это обозначение остается действительным!*/

Числа с плавающей запятой (floating point literals)

fmt.Println(1e3, 1e-3, 1.234, 1.234e+0, 1.23e-4, .123, .123456E+5) //1000 0.001 1.234 1.234 0.000123 0.123 12345.6

/*Числа с плавающей запятой также могут быть записаны в шестнадцатеричном виде, 
начиная с версии go1.13. За мантиссой должен следовать показатель степени в десятичной
системе счисления, разделенный p/P. Показатель степени возводит
основание 2 в степень значения показателя степени:*/
fmt.Println(0x8.ap3, 0xf.7p-2) //69 (= 0x8,a * 2³ ) 3.859375 (= 0xf,7 * 2⁻²)
//Альтернативное написание:
fmt.Println((0x8.ap0)*math.Exp2(3), (0xf.7p0)*math.Exp2(-2)) //69 3.859375

Мнимые числа (imaginary literals)

fmt.Println(0i, 11i, 1.234i, 1.234e+0i, 1.23e-4i, 1E2i, .123i, .123E+4i) //(0+0i) (0+11i) (0+1.234i) (0+1.234i) (0+0.000123i) (0+100i) (0+0.123i) (0+1230i)
fmt.Println(0xai, 0b101i, 0o7i)                                          //(0+10i) (0+5i) (0+7i); 
/*Мнимые числа также могут быть записаны в шестнадцатеричном, 
  восьмеричном или двоичном формате (начиная с версии 1.13).*/

Разделители в числах (digits separators) – Без дальнейших функций, только косметика

fmt.Println(1_234.56_78_90) //1234.56789

Обьявления

Переменные

var a int // Обьявление без инициализации
var b int = 42 // Обьявление с инициализацией
var a, b int = 42, 1302 // Объявление одинаковых типов и инициализация через список
var MaxUint = ^uint(0) // Результаты операций также могут быть присвоены
var MinUint = 0
var MaxInt = int(MaxUint >> 1)
var MinInt = -MaxInt - 1
var c = 42      // Объявление и инициализация без указания типа
c := 42         // то же, альтернативное написание
var c = int(42) // то же самое, но с указанием типа

код не рабочий, просто примеры использования

Константы

package main

const (
	constant          = "Обьявление типа не обязательно ..."
	constanter string = "... ведь инициализация принудительно выполняется путем вывода типа, " +
                            "поскольку значение известно."
)

const (
	a = iota        // a = 0 // Первое присваивание йоты обязательно, ...
	b = iota        // b = 1 // ... все остальные можно не писать, ...
	c               // c = 2 // ... подсчет продолжается в объявлении const()
	d int    = iota // d = 3
)

const e = iota // e = 0

const (
	f = 1 << iota // 1
	g = 1 << iota // 2
	h = 1 << iota // 4
	i = 1 << iota // 8
	j = 1 << iota // 16
)

func main() {
	println(a, b, c, d, e) //0 1 2 3 0
	println(f, g, h, i, j) //1 2 4 8 16
}

Примечание
Автоматическое перечисление можно реализовать, присвоив константе ключевое слово iota.

Обьявление типов

  • Объявленный тип не идентичен производному типу!
package main

import "fmt"

type unsigned_short uint8

func main() {
	var a uint8
	var b byte
	var c unsigned_short

	a = 12
	b = a	
	c = 12 // "c = a" или "c = b" не допускается

	fmt.Println(a + uint8(b) + (uint8)(c)) // требуется приведение типа
}

Практическое руководство по применению: замена типа данных enum (C++, Java и ост.)
package main

import "fmt"

type (
	sheepType string // To ensure, that the set of constants is limited (a kind of enumeration)
	dogType   string
)

const (
	SHEEP1 sheepType = "Shaun"
	SHEEP2 sheepType = "Charly"
	SHEEP3 sheepType = "Timmy"
	DOG    dogType   = "Bitzer"
	PIG    string    = "Oink"
)

func doSomethingWithSheepsOnly(s sheepType) {
	fmt.Println(s)
}

func main() {
	doSomethingWithSheepsOnly(SHEEP1)
	doSomethingWithSheepsOnly(SHEEP2)
	doSomethingWithSheepsOnly(SHEEP3)
	//doSomethingWithSheepsOnly(DOG) //... cannot use DOG (type dogType) as type sheepType in argument to doSomethingWithSheepsOnly
	//doSomethingWithSheepsOnly(PIG) //... cannot use PIG (type string) as type sheepType in argument to doSomethingWithSheepsOnly
}

Тип-Псевдоним

  • Псевдоним идентичен производному типу!
  • Псевдоним типа упрощает рефакторинг, поскольку изменение определения распространяется на все использования в коде. Очень удобно сделать, например, int64 int32 или int64 float64 без необходимости вручную вносить это изменение во весь код.
package main

import "fmt"

type unsigned_short = uint8

func main() {
	var a uint8
	var b byte
	var c unsigned_short

	a = 12
	b = a
	c = b

	fmt.Println(a + b + c) // допустимо, потому что типы-псевдонимы не требуют приведения типов
}

Указатель (Pointer)

Выделение памяти по указателю на переменную

Примечание
& возвращает адрес памяти переменной
* возвращает значение, хранящееся в адресе памяти
package main

import "fmt"

func main() {
	// Указатель на тип данных
	var a int
	var b *int // Указатель на int, но, конечно, указатели могут быть объявлены и для всех других типов данных.
	a = 10
	b = &a             // b является указателем (тип данных *int) и получает адрес
	fmt.Println(a)     //10
	fmt.Println(b)     //0xc42000e310
	fmt.Println(*b)    //10
	fmt.Println(&*b)   //0xc42000e310
	fmt.Println(*&*b)  //10
	c := &b            // c имеет тип данных **int
	fmt.Println(c)     //0xc42000c028
	fmt.Println(&c)    //0xc42000c038
	fmt.Println(**c)   //10
	fmt.Println(*&c)   //0xc42000c028
	fmt.Println(&*&c)  //0xc42000c038
	fmt.Println(***&c) //10
	**c = 11           // Значение по адресу, на который прямо указывает d или косвенно c, 
                           // присваивается значение. (Поэтому a также равно 11)
	fmt.Println(*b)    //11
	fmt.Println(a)     //11

	// Указатель на массив данных типа
	var e [4]byte
	var f *[4]byte
	var g *byte
	e = [4]byte{22, 33, 44, 55}
	f = &e                     //Помните: если требуется начальный адрес массива -
	g = &e[0]                  //необходимо указывать первый элемент массива! (например, очень важно для CGO!)
	fmt.Println(&e, e, &e[0])  //&[22 33 44 55] [22 33 44 55] 0xc0000141e0
	fmt.Println(f, &(f[0]), g) //&[22 33 44 55] 0xc0000141e0 0xc0000141e0
}

Присваивание значения переменной через указатель памяти

// 1 вариант:
// var c *float64 = new(float64)
// 2 вариант:
// var c *float64; c = new(float64)
// 3 вариант:
c := new(float64)
*c = 123.456
fmt.Println(*c)
Кроме того, присваивание значений указателям также может выполняться косвенно.
// Например через анонимную функцию …
var c *float64
c = func(f float64)*float64 {return &f}(123.456)
// …или переменную
var c *float64
f := 123.456
c = &f

Арифметика указателей (как в C) не существует в Go...

… если вы не используете тип данных uintptr и Package unsafe:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	i := [3]int16{123, 456, 789}
	var pi *[3]int16
	pi = &i

	fmt.Println(pi, *pi)      //&[123 456 789] [123 456 789]
	upi := unsafe.Pointer(pi) //Преобразование обычного (типизированного) указателя в небезопасный.
	fmt.Println(upi)          //0xc42000e2f2
	//fmt.Println(*upi)       //Ошибка: invalid indirect of upj (type unsafe.Pointer)

	pi2 := (*int16)(upi)   // Приведение небезопасного указателя к обычному (типизированному)
	fmt.Println(pi2, *pi2) //0xc42000e2f2 123

	// Затем вы также можете выполнить расчет с типом данных "uintptr":
	uipi := uintptr(upi)         // Преобразование unsafe.Pointer в uintptr
	uipi += 2                    // Вычисление с помощью указателя (в int16: +2 байта)
	upi2 := unsafe.Pointer(uipi) // Преобразовать uintptr в unsafe.Pointer ...
	pi3 := (*int16)(upi2)        // ... а затем в обычный (типизированный) указатель

	fmt.Println(pi3, *pi3)                                                   //0xc42000e2f4 456
	fmt.Println(unsafe.Pointer(uipi+4), *(*int16)(unsafe.Pointer(uipi + 2))) //0xc42000e2f8 789

	println(unsafe.Sizeof(i))  //6 (Нахождение размера всей переменной в байтах)
	println(unsafe.Alignof(i)) //2 (выравнивание указателя/памяти в зависимости от типа)

	type struktur struct {
		a int8
		b string
	}
	j := struktur{123, "abc"}

	println(unsafe.Offsetof(j.a)) //0 (Только для конструкций: смещение элемента относительно основания)
	println(unsafe.Offsetof(j.b)) //8
	println(unsafe.Alignof(j.a))  //1 (1 байт в int8)
	println(unsafe.Alignof(j.b))  //8
	println(unsafe.Alignof(j))    //8
	// Обратите внимание, что фактические требования к объему памяти могут отличаться от выравнивания памяти!
	println(unsafe.Sizeof(j.a)) //1
	println(unsafe.Sizeof(j.b)) //16
	println(unsafe.Sizeof(j))   //24
}

Тайпкэстинг (type casting)

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// То же самое с выводом типа
j := 42
g := float64(i)
v := uint(f)

// Также допустимое написание
w := (int)(u)
x := (float64)(1234)

Arrays, Slices, Strings, Maps, Ranges

Arrays

Обьявление и присваивание
var a [10]int // Объявление массива целых чисел c длиной 10
a[3] = 42     // Присвоение значения четвертому(!) элементу массива
i := a[3] 	  // Присвоение данных из массива
var a [3]int = [3]int{11, 22, 33} // Обьявление и Присвоение значений
var a = [3]int{11, 22, 33} // То же самое, но короче
a := [3]int{11, 22, 33}    // То же самое.
a := [...]int{11, 22, 33}  /* А на этот раз компилятор считает длину массива 
                            за нас — но работает только с ":=" */
Многомерные массивы
// С объявлением
var multia [2][2]string
multia = [2][2]string{{"α", "β"}, {"γ", "δ"}}
// С ":="
multib:=[2][2]string{{"α", "β"}, {"γ", "δ"}}

Указатели на массивы
var a *[2]int // Указатель на массив из int
var b [2]*int // Массив указателей на int

a = new([2]int) // Необходимо для выделения памяти

a[0] = 11
c := 12
b[0] = &c
fmt.Println(a, *a, (*a)[0]) // &[11 0] [11 0] 11
fmt.Println(b, b[0], *b[0]) // [0xc420012168 <nil>] 0xc420012168 12

Slices

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

var a []int // Обьявление слайса.
var a = []int {1, 2, 3, 4} // Присвоение и инициализация
a := []int{1, 2, 3, 4} // Другой вариант
chars := []string{0:"a", 2:"c", 1: "b"} // ["a", "b", "c"]
var b = a[1:4] // Слайсы с индексами от 1 до 3 (Этот вариант так же имеет смысл - 4-1)
var b = a[:3]  // Отсутствует low-Index = 0
var b = a[3:]  // Отсутствует high-Index = len(a)
var b = a[lo:hi] // Границы срезов указаны переменными. (Обратите внимание на hi-1!)
Различия между new() и make()
package main

import "fmt"

func main() {
	var a []int
	// Создание слайса с "new"
	a = new([10]int)[0:5]

	//Создание того же слайса с "make"
	a = make([]int, 5, 10)         // Первый аргумент = длинна, второй = вместимость
	fmt.Println(a, cap(a), len(a)) // [0 0 0 0 0] 10 5
	a = make([]int, 5)             // Указание вместимости не является обязательной
	fmt.Println(a, cap(a), len(a)) // [0 0 0 0 0] 5 5

	var v []int = make([]int, 10) /* Слайс ссылается на массив из 10 целых
                                         чисел с 0 в качестве содержимого*/
	fmt.Println(v)                // [0 0 0 0 0 0 0 0 0 0]

	var m [][]string = make([][]string, 10) // Многомерные срезы также могут быть выделены
	m[0] = make([]string, 2)
	m[0] = []string{"123", "abc"}
	fmt.Println(m) // [[123 abc] [] [] [] [] [] [] [] [] []]

	// Создание указателя на слайс c new
	var p *[]int = new([]int) // Выделяет слайс, содержащий nil, и возвращает указатель(*p == nil)
	fmt.Println(*p)           // []
	*p = make([]int, 2, 2)    // make по-прежнему требуется для выделения памяти
	(*p)[0] = 10
	(*p)[1] = 20
	fmt.Println(*p, cap(*p), len(*p)) // [10 20] 2 2
}

Создание слайса из массива
x := [3]string{"α","β","γ"}
s := x[:] // Слайс относится к области памяти массива x
Сопирование слайсов с помощью copy()
array := [3]string{"a","b","c"}
slice := []string{"x", "y", "z"}
copy(array[:], slice[:]) // Здесь слайс копируется в массив
fmt.Println(array) //[x y z]
Копирование части слайса в часть другого слайса
package main

import "fmt"

var a = []string{"J", "a", "v", "a"}
var b = []string{"G", "o", "p", "h", "e", "r"}

func main() {
	fmt.Println(a) //[J a v a]
	fmt.Println(b) //[G o p h e r]

	copy(a[2:4], b[0:2])
	fmt.Println(a) //[J a G o]
}

Расширение слайса с помощью append()
slice := []string{"a", "b", "c"}
slice = append(slice, "d") // Слайс получает дополнительный элемент
fmt.Println(slice)         //[a b c d]
anotherSlice := []string{"e", "f", "g"}
slice = append(slice, anotherSlice...) // Три точки ...
// разделяют слайс на отдельные элементы, тем самым
// позволяя добавить их (элементы) к другому слайсу
fmt.Println(slice) //[a b c d e f g]
Обнуляет все элементы среза с помощью clear()
var a = []int {1, 2, 3, 4}
var b = []string {"1", "2", "3", "4"}
fmt.Println(a) //[1 2 3 4]
fmt.Println(b) //[1 2 3 4]
clear(a) //Обнуляет все элементы среза
clear(b)
fmt.Println(a) //[0 0 0 0]
fmt.Println(b) //[   ]
Слайсы являются не чем иным, как «указателями на массивы».
  • Массив, которого не существует, создается в фоновом режиме.
Сравните массив ...
a := [4]byte{'J', 'a', 'v', 'a'} // Массив
s := a[1:] // Слайс
s[0] = 'e'
s[1] = 'r'
s[2] = 'k'
fmt.Printf("%s\n", a)
... со слайсом
var a = []byte{'J', 'a', 'v', 'a'} // Слайс, создает анонимный массив в фоне.
s := a[1:] // Слайс
s[0] = 'e'
s[1] = 'r'
s[2] = 'k'
fmt.Printf("%s\n", a)
Вот почему так важно учитывать вместимость!

Слайс может быть расширен по желанию. Однако, когда пределы емкости (= пределы анонимного массива) достигнуты, слайс автоматически копируется в новый анонимный массив с удвоенной емкостью. (что является "дорогой" операцией, ведь она забирает место на диске и вычислительную мощность!)

package main

import "fmt"

func main() {
	s := make([]int, 3, 5) // Первый аргумент = длинна, второй = вместимость
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s[0] = 10
	s[1] = 21
	s[2] = 32
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s = append(s, 43)
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s = append(s, 54)
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s = append(s, 65) // Здесь вместимость удваивается
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s = append(s, 76)
	s = append(s, 87)
	s = append(s, 98)
	s = append(s, 109)
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s = append(s, 120) // Здесь вместимость удваивается
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
	s = append(s, 131)
	fmt.Printf("Длинна: %v; Вместимость: %v; Содержание: %v\n", len(s), cap(s), s)
}

Результат выполнения программы:

Длинна: 3; Вместимость: 5; Содержание: [0 0 0]
Длинна: 3; Вместимость: 5; Содержание: [10 21 32]
Длинна: 4; Вместимость: 5; Содержание: [10 21 32 43]
Длинна: 5; Вместимость: 5; Содержание: [10 21 32 43 54]
Длинна: 6; Вместимость: 10; Содержание: [10 21 32 43 54 65]
Длинна: 10; Вместимость: 10; Содержание: [10 21 32 43 54 65 76 87 98 109]
Длинна: 11; Вместимость: 20; Содержание: [10 21 32 43 54 65 76 87 98 109 120]
Длинна: 12; Вместимость: 20; Содержание: [10 21 32 43 54 65 76 87 98 109 120 131]

Остальные подробности (Eng.) https://blog.golang.org/go-slices-usage-and-internals

Strings

Строки похожи на массивы тем, что срез может отображать часть строки. Однако следует помнить, что специальные символы (вне таблицы ASCII) могут занимать в строке более одного байта!

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	fmt.Println("Bye bye Java"[8:10])         // "Ja"
	fmt.Println("Bye bye Java"[8])            // 74, что означает 'J'
/* Если в аргументах слайса стоит 1 число, то тип данных у этого слайса будет unit8(аналог rune)
А если в аргументах стоит диапазон, слайс будет иметь тип String*/
	fmt.Printf("%T\n", "Bye bye Java"[8:9])   //uint8
	fmt.Printf("%T\n", "Bye bye Java"[8])     //String
	fmt.Println(string("Bye bye Java"[8]))    // "J"
	fmt.Println([]byte("Bye bye Java"[8:12])) // [[74 97 118 97] bzw. []byte{'J', 'a', 'v', 'a'})
	fmt.Println(string("ABC"[0:3]))           //ABC
	fmt.Println(string("ÄÖÜ"[0:3]))           //Ä�
	fmt.Println(string("ÄÖÜ"[0:4]))           //ÄÖ
	fmt.Println(string("ÄÖ語"[0:6]))           //ÄÖ��
	fmt.Println(string("ÄÖ語"[0:7]))           //ÄÖ語
	fmt.Println(len("ÄÖ語"),utf8.RuneCountInString("ÄÖ語")) //7 3
	for _, c := range "ÄÖ語" {
		fmt.Println(string(c), rune(c)) // Ä 196, Ö 214, 語 35486
	}
}

Maps

  • Карте(Map) могут быть присвоены все типы:
package main

import "fmt"

func main() {
	var m map[string]int
	m = make(map[string]int)
	//	m := make(map[string]int,10) // Можно так же создать с емкостью
	m["Answer"] = 42 // Присвоение значения
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer") // Удаление элемента карты
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"] // Если элемент cодержит значение, ok является истинным. А если нет, то ложным
	fmt.Println("The value:", v, "Present?", ok)

	clear(m) //удаляет все элементы с карты
	fmt.Println(m)

	mi := map[string]int{
		"a": 1,
		"b": 2,
		"c": 3,
	}
	fmt.Println(mi)

	pm := &m                     // Создание указателя на карту
	fmt.Println((*pm)["Answer"]) // Внимание! Необходимо использовать скобки,
	// ведь *pm["Answer"] было бы недопустимым или же эквивалентным *(pm["Answer"])

	mdm := map[string]map[string]map[int]int{ // Многомерные карты возможны, хотя и немного более громоздки, чем словари в Python или массивы в PHP.
		"color": {"green": {1: 0x00ff00, 2: 0x00ee00}}}
	fmt.Println(mdm)

	//var mdm2 map[string]map[string]map[int]int
	//mdm2 = make(map[string]map[string]map[int]int)
	// oder
	mdm2 := make(map[string]map[string]map[int]int)
	mdm2["color"] = make(map[string]map[int]int)
	mdm2["color"]["red"] = make(map[int]int)
	mdm2["color"]["red"][1] = 0xff0000
	fmt.Println(mdm2["color"]["red"][1])
}

  • Функции так же могут быть присвоены!
package main

import "fmt"

func main() {
	mf := make(map[int]func(float64, float64) float64)

	mf[1] = add
	mf[2] = mult
	mf[3] = sub
	mf[4] = div

	for i := 1; i <= 4; i++ {
		fmt.Println(mf[i](2, 3)) //5 ⇒ 6 ⇒ -1 ⇒ 0.6666666666666666
	}
}

func add(a float64, b float64) float64 {
	return a + b
}

func mult(a float64, b float64) float64 {
	return a * b
}

func sub(a float64, b float64) float64 {
	return a - b
}

func div(a float64, b float64) float64 {
	return a / b
}

Ranges

Итерация аргументов Array, Slice или Map.

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

for i, v := range pow {
	fmt.Printf("2**%d = %d\n", i, v)
}

for j := range pow { // Идентичный результат, значение может быть опущено
	fmt.Printf("2**%d = %d\n", j, pow[j])
}

"Generics" - комбинированные типы данных.

Начиная с версии go1.18 можно передавать универсальные типы данных в функции, что устраняет давно упоминаемый недостаток Go по сравнению с другими языками, такими как Java или C++.

package main

import "fmt"

//Аргументы типа определяются в квадратных скобках перед объявлением переменной.
func divideAllNumbers[num int8 | int16 | int32 | int64 | int | float32 | float64](a,b num) (c num){
	c = a/b
	return c
}

// Сложные комбинации типов данных упрощаются за счет ограничений типов.
// Ограничения типов являются просто заполнителями для правил,
// описывающих комбинацию других данных.
// Объединение разных типов данных осуществляется с помощью |-Operator
type NumberConstraint interface { // Создание ограничения типа
   int8 | int16 | int32 | int64 | int | float32 | float64
}
// !!Тем не менее, ограничения типа НЕ являются самостоятельными типами данных!!
// "var num1 NumberConstraint" не допустимо!


// Аргументы типа задаются в порядке объявления переменных в параметрах функции.
// Для упрощения цепочки типов данных, они могут быть описанны ограничениями типов
func FunktionWithMultipleArguments[idx string|[]byte, str string, num NumberConstraint](i idx, m map[str]num) (z num) {
	z = m[str(i)] //Обратите внимание на преобразование типов в универсальный тип!
    return z
}

func main() {

	d := divideAllNumbers(8.0,2.0) // Когда это возможно, типы данных передаются с помощью вывода типа.
	fmt.Printf("%v, %T\n",d,d) //4, float64

	//e := divideAllNumbers(8,2.0) // Если это невозможно, выдается ошибка:
	//fmt.Printf("%v, %T\n",e,e) //default type float64 of 2.0 does not match inferred type int for num

	f := divideAllNumbers(float32(8),2.0) // Решающим фактором является то, что типы данных могут работать друг с другом в смысле определения функции.
	fmt.Printf("%v, %T\n",f,f) //4, float32

	g := divideAllNumbers[int8](8,2.0) // Если вывод типа невозможен, должны быть переданы аргументы типа (в квадратных скобках)!
	fmt.Printf("%v, %T\n",g,g) //4, int8

	h := FunktionWithMultipleArguments("indexI", map[string]float64{"indexI":12.345})
	fmt.Printf("%v, %T\n",h,h) //12.345, float64

	i := FunktionWithMultipleArguments[[]byte,string,int8]([]byte("indexII"), map[string]int8{"indexII":127})
/*Аргументы типа предназначены только для иллюстрации; здесь они не нужны, 
поскольку вывод типа возможен благодаря уникальным типам данных. Порядок аргументов типа основан 
на порядке объявления переменных в параметрах функции.*/
	fmt.Printf("%v, %T\n",i,i) //127, int8
}

Предопределенные ограничения типов

Все типы данных, которые можно сравнивать с помощью == и != - comparable (совместимые).
any - Все типы данных. (any это псевдоним типа interface{})

структуры

Объявление и доступ

package main

import "fmt"

type programmingLanguages struct {
	name    string
	rating  int
	future  bool
	float64 //Анонимное поле
	string  //Анонимное поле
}

func main() {

	// Безымянное присвоение в порядке типов
	fmt.Println(programmingLanguages{"php", 4, true, 0.815, "interpreter"}) //{php 4 true 0.815 interpreter}

	// Именованное присвоение в любом порядке
	fmt.Println(programmingLanguages{rating: 2, future: true, name: "Python"}) //{Python 2 true}

	// Указатель на структуру
	fmt.Println(&programmingLanguages{name: "Java", rating: 3, future: false}) //&{Java 3 false}

	// Неполное назначение допустимо, неуказанные элементы получают значения по умолчанию/нулевые значения
	fmt.Println(programmingLanguages{name: "C/C++"}) //{C/C++ 0 false}

	// Доступ к элементу структуры
	var a programmingLanguages
	a = programmingLanguages{"Go", 1, true, 1.23, "compiler"}
	fmt.Println(a.name) //Go

	// Указатели …
	b := &a
	//… автоматически отслеживаются, поэтому здесь написано «b.rating», а не «*b.rating»:
	fmt.Println(b.rating) //1

	// Структуры могут быть изменены
	b.name = "Golang"
	fmt.Println(b.name) //Golang
	fmt.Println(a)      //{php 4 true 0.815 interpreter} (Указатель на b!)

	//Проверка на пустые структуры
	if a != (programmingLanguages{}) { // Обратите внимание на дополнительные скобки ()
		fmt.Println("Структура не пустая")
	}
}

Тип данных funktion в структурах

package main

import "fmt"

type dataType struct {
	s string
	i int
	b bool
	f func(string) string
}

func main() {
	var data dataType
	data = dataType{s: "Hello", i: 12345, b: true, f: func(a string) string { return data.s + " " + a }}
	fmt.Println(data.f("World"), data.i, data.b) //Hello World 12345 true
}

Вложенные структуры

package main

import (
	"fmt"
)

func main() {
	// Вложенные структуры могут быть определены индивидуально ...
	type c struct {
		ca string
		cb bool
	}

	type nested struct {
		a int
		b float64
		c
	}

	var sn nested
	sn = nested{a: 1, b: 1.23, c: c{ca: "xyz", cb: true}}
	fmt.Println(sn) //{1 1.23 {xyz true}}

	// ... или определены "анонимно" вместе с родительской структурой.
	type nestedanonymous struct {
		a int
		b float64
		d struct {
			da string
			db bool
		}
		e []struct {
			ea string
			eb bool
		}
	}

	var sa, sb nestedanonymous
	sa.a = 2
	sa.b = 3.45
	sa.d.da = "abc"
	sa.d.db = true
	sa.e = make([]struct {
		ea string
		eb bool
	}, 2)
	sa.e[0].ea = "def"
	sa.e[0].eb = true
	sa.e[1].ea = "ghi"
	sa.e[1].eb = false
	fmt.Println(sa) //{2 3.45 {abc true} [{def true} {ghi false}]}

	sb = nestedanonymous{a: 3, b: 4.56, d: struct {
		da string
		db bool
	}{da: "αβγ", db: true}, e: []struct {
		ea string
		eb bool
	}{{ea: "δεζ", eb: true}, {ea: "ηθι", eb: false}}}
	fmt.Println(sb) //{3 4.56 {αβγ true} [{δεζ true} {ηθι false}]}
}

Теги

  • Элементы структур могут получать дополнительные теги и использоваться с помощью пакета Reflect.
package main

import (
	"fmt"
	"reflect"
)

type programmingLanguages struct {
	name   string `json:"fieldName"`
	rating int    `xml:"rating"`
	future bool   `abc:""`
}

func main() {
	a := programmingLanguages{name: "Go", future: true, rating: 1}
	ra := reflect.TypeOf(a)

	fmt.Println(ra)                             //main.programmingLanguages
	fmt.Println(ra.Field(0))                    //{name main string json:"fieldName" 0 [0] false}
	fmt.Println(ra.Field(0).Tag.Get("json"))    //fieldName
	fmt.Println(ra.Field(0).Tag.Lookup("json")) //fieldName true
	fmt.Println(ra.Field(2).Tag.Lookup("abc"))  // true
}

Операторы

Арифметика

+ Сложение
- Вычитание
* Умножение
/ Деление
% Остаток от деления 
& Побитовое "И" (6 & 3 = 2, 00000110 AND 00000011 = 00000010)
| Побитовое "ИЛИ" (6 | 3 = 7, 00000110 OR 00000011} = 00000111)
^ Побитовое "Исключающее ИЛИ"  (6 ^ 3 = 5, 000000110 XOR 00000011 = 00000101)
&^ Побитовое "И НЕ" (6 &^ 3 = 4, 00000110 AND NOT 00000011 = 000000100)
^() Побитовое отрицание (^byte(1) = 254, 00000001 -> 11111110)
<<  побитовый сдвиг влево (left shift) (6<<3 = 48, da  00000110 сдвиг на три позиции влево = 00110000)
>>  побитовый сдвиг вправо (right shift) (6>>2 = 1, da  00000110 сдвиг на 2 позиции вправо = 00000001)
Примечание
Арифметические операторы можно комбинировать с оператором присваивания.
x += y эквивалентно x = x + y
x |= y эквивалентно x = x | y

Сравнения

== равно
!= не равно
<  меньше 
<= меньше или равно
>  больше
>= больше или равно

Логические операторы

&& Логическое И
|| Логическое ИЛИ
!  Логическое НЕ

Особенности

&  Адрес содержимого
*  Указатель на адрес содержимого
<- оператор отправки/получения
_  Заполнитель, пустота

Круглые скобки

() ограничивают область выражения. Например, в структурах скобки определяют назначение 
   адреса указателя. (Без скобок указатель всегда указывает на последний элемент!)
   Сравните: (*strukture).element или *strukture.element

Управляющие структуры

Управление структурами изнутри осуществляется через команды:

return
Выйти из функции
break
Используется для прерывания всего цикла
continue
Выполняет переход к следующей итерации

OuterLoop:
	for i = 0; i < n; i++ {
	InnerLoop:
		for j = 0; j < m; j++ {
			switch a[i][j] {
			case nil:
				break OuterLoop
			default:
				continue InnerLoop

fallthrough
Выполняет переход к следующему кейсу в case-switch. (Без дальнейшей проверки условий!)
x := 2 //попробуйте так же с 1 и 0
	switch x {
	case 2:
		fmt.Println("Два")
		fallthrough
	case 1:
		fmt.Println("Один")
	case 0:
		fmt.Println("Ноль")
		fallthrough
	default:
		fmt.Println("Результат по умолчанию")
	}

Условия

if x > 0 {
	return x
} else {
	return -x
}
if a := b + c; a < 42 {
	return a
} else {
	return a - 42
}

Циклы

  • В Go есть только один, но очень гибкий for, так что в while больше нет необходимости.
for i := 1; i < 10; i++ {
}
var i int
for ; i < 10; { // while - loop
}
var i int
for i < 10 { // точки с запятой можно опустить
}
for { // безусловный цикл - тоже самое, что и while(true)
}

Switch

// Переключение с проверкой выражения ...
switch m {
case 0, 2, 4, 6:
	func1()
default:           // Расположение условия по умолчанию
	funcDefault()  // не имеет значения
case 1, 3, 5, 7:
	func2()
}
// ... или с возвращаемым значением функции в виде выражения
switch liefereM() {
case 0, 2, 4, 6:
	func1()
default:
	funcDefault()
case 1, 3, 5, 7:
	func2()
}
// Switch без выражения (по умолчанию true)
switch {
case a < b:
	func1()
	fallthrough // Следующий кейс так же выполнится 
case a <= b: 
	func2()
case a > b:
	func3()
case a == b:
	func4()
}
// Так же, как и остальные операторы, switch может создавать локальные переменные
switch os := getMyOS(); os {
	case "windows": fmt.Println("😱")
	case "mac": fmt.Println("😚")
	case "linux": fmt.Println("😊")
	case "bsd": fmt.Println("😎")
	default: fmt.Println("👻")
	}
// Объявления переменных также можно размещать перед переключателем без выражения 
// (при этом обязательно надо ставить точку с запятой '';'')
switch os := getMyOS(); {
	case os == "windows": fmt.Println("😱")
	case os == "mac": fmt.Println("😚")
	case os == "linux": fmt.Println("😊")
	case os == "bsd": fmt.Println("😎")
	default: fmt.Println("👻")
	}

Видимость переменных в управляющих структурах

Во всех управляющих структурах можно создавать локальные переменные, которые видны только внутри управляющей структуры.

switch i := 123; i { ... }

Также можно:

package main

import (
	"fmt"
)

var i int = 123

func main() {
	switch i := i * 2; i {
	default:
		fmt.Println(i) // Здесь i равна 246
	}
	fmt.Println(i) // А здесь i снова 123
}

Запрещено, но возможно: goto

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
Label:
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	if i := r.Intn(100); i <= 98 {
		fmt.Println(i)
		goto Label
	} else {
		fmt.Println("Наконец-то!")
	}
}

Объектноориентированность

В Go нет классов, нет модификаторов доступа (public, private, protected) и явного синтаксиса наследования.

  • Однако свойства объекта могут быть представлены с помощью трех основных компонентов:
    1. Типы данных: в Go любой самоопределяемый тип данных может стать объектом, т. е. принять свойства, которые класс обычно имеет в других языках ООП.
    2. Методы: в отличие от других языков ООП, методы в Go не являются функциями, определенными внутри класса. Вместо этого функции в Go определяются вне типа данных и объявляются как метод этого типа данных путем указания типа данных получателя.
    3. Интерфейсы: несколько методов могут быть связаны вместе для формирования интерфейсов в Go.
  • Видимость объекта определяется написанием идентификаторов на уровне модуля.
  • И свойства наследуются путем создания новых составных типов из существующих составных типов.

Наследование

package main

import "fmt"

// Составной тип наследует определение класса
type noClass struct {
	a string
	b int
	c bool
}

//
func (self noClass) method() int {
	return self.b + self.b // "self" не является ключевым словом в go, переменная-получатель названа здесь только для иллюстративных целей.
}

// Указатели более эффективны, и они позволяют изменять переменную, называя ее место в памяти, а не копируя ее.
func (this *noClass) pointerMethod() int {
	return this.b * this.b // "this" не является ключевым словом в go, переменная-получатель названа здесь только для иллюстративных целей.
}

type noClass2 struct {
	noClass // В noClass2 записываются свойства noClass!
	d       float64
}

type noClass3 struct {
	noClass2 // Работает так же, как и в прошлом типе - в noClass3 записываются свойства noClass2 (а так же и noClass)
	e        rune
}

func main() {
	// Установка параметров аналогична передаче параметров конструктору ООП.
	entity := noClass{a: "Ерунда", c: true, b: 123}
	entity2 := noClass2{noClass: entity, d: 4.56789}
	entity3 := noClass3{noClass2: entity2, e: '😹'}

	// Обратите внимание на множество способов доступа к собственным и унаследованным элементам:
	fmt.Println(entity.a, entity.b, entity.c)                                                                                                                                                                                                                                                                                                                                                                                                                                    // Ерунда 123 true
	fmt.Println(entity2.a, entity2.b, entity2.c, entity2.d, " | ", entity2.noClass.a, entity2.noClass.b, entity2.noClass.c, " | ", entity.a, entity.b, entity.c)                                                                                                                                                                                                                                                                                                                 // Ерунда 123 true 4.56789  |  Ерунда 123 true  |  Ерунда 123 true
	fmt.Println(entity3.a, entity3.b, entity3.c, entity3.d, entity3.e, " | ", entity3.noClass.a, entity3.noClass.b, entity3.noClass.c, " | ", entity3.noClass2.a, entity3.noClass2.b, entity3.noClass2.c, entity3.noClass2.d, " | ", entity3.noClass2.noClass.a, entity3.noClass2.noClass.b, entity3.noClass2.noClass.c, " | ", entity2.a, entity2.b, entity2.c, entity2.d, " | ", entity2.noClass.a, entity2.noClass.b, entity2.noClass.c, " | ", entity.a, entity.b, entity.c) // Ерунда 123 true 4.56789 128569  |  Ерунда 123 true  |  Ерунда 123 true 4.56789  |  Ерунда 123 true  |  Ерунда 123 true 4.56789  |  Ерунда 123 true  |  Ерунда 123 true

	// Следует отметить, что Go автоматически выполняет преобразование между значениями и указателями при вызове методов и всегда возвращает значения!
	entityp := &entity
	entity2p := &entity2
	entity3p := &noClass3{noClass2: *entity2p, e: '😆'}
	fmt.Println(entity.method(), "==", entityp.method())                                   //246 == 246
	fmt.Println(entity.method(), "==", entity2p.noClass.method())                          //246 == 246
	fmt.Println(entity3p.noClass2.noClass.pointerMethod(), "==", entity2p.pointerMethod()) //15129 == 15129

	// Перезапись параметров класса действует только в самом классе.
	entity2.a = "Чушь"
	entity2.b = 246
	fmt.Println(entity3.a, entity3.b, entity3.c, entity3.d, entity3.e, " | ", entity3.noClass.a, entity3.noClass.b, entity3.noClass.c, " | ", entity3.noClass2.a, entity3.noClass2.b, entity3.noClass2.c, entity3.noClass2.d, " | ", entity3.noClass2.noClass.a, entity3.noClass2.noClass.b, entity3.noClass2.noClass.c, " | ", entity2.a, entity2.b, entity2.c, entity2.d, " | ", entity2.noClass.a, entity2.noClass.b, entity2.noClass.c, " | ", entity.a, entity.b, entity.c) // Ерунда 123 true 4.56789 128569  |  Ерунда 123 true  |  Ерунда 123 true 4.56789  |  Ерунда 123 true  |  Чушь 246 true 4.56789  |  Чушь 246 true  |  Ерунда 123 true

	// То же самое работает и для указателей.
	entityp.a = "Околесица"
	entityp.b = 369
	fmt.Println(entity3p.a, entity3p.b, entity3p.c, entity3p.d, entity3p.e, " | ", entity3p.noClass.a, entity3p.noClass.b, entity3p.noClass.c, " | ", entity3p.noClass2.a, entity3p.noClass2.b, entity3p.noClass2.c, entity3p.noClass2.d, " | ", entity3p.noClass2.noClass.a, entity3p.noClass2.noClass.b, entity3p.noClass2.noClass.c, " | ", entity2p.a, entity2p.b, entity2p.c, entity2p.d, " | ", entity2p.noClass.a, entity2p.noClass.b, entity2p.noClass.c, " | ", entityp.a, entityp.b, entityp.c) // Ерунда 123 true 4.56789 128518  |  Ерунда 123 true  |  Ерунда 123 true 4.56789  |  Ерунда 123 true  |  Чушь 246 true 4.56789  |  Чушь 246 true  |  Околесица 369 true
}

Где необходимы указатели-приемники

  • Что-бы указанное значение применялось только к одной функции, надо использовать тип без указателя. А если же вы хотите, чтобы это значение применилось так же ко всем последующим функциям, используйте тип с указателем.
package main

import "fmt"

type noClass struct {
	X int
}

func (this noClass) setter1() {
	this.X = 1
}
func (this *noClass) point_setter1() {
	this.X = 2
}
func (this noClass) getter1() int {
	return this.X
}
func (this *noClass) point_getter1() int {
	return this.X
}

func main() {
	var a noClass = noClass{}
	a.setter1()
	fmt.Println(a.getter1())       // 0 а не 1
	fmt.Println(a.point_getter1()) // 0 а не 1
	a.point_setter1()
	fmt.Println(a.getter1())       // 2
	fmt.Println(a.point_getter1()) // 2

	// или

	var b *noClass = &noClass{}
	b.setter1()
	fmt.Println(b.getter1())       // 0
	fmt.Println(b.point_getter1()) // 0
	b.point_setter1()
	fmt.Println(b.getter1())       // 2
	fmt.Println(b.point_getter1()) // 2
}

Статические методы или вызов статического метода

package main

package main

import "fmt"

type myStructType struct {
	X int
}

func (s myStructType) doSomething(v int) {
	fmt.Println("I got", v)
	s.X = v
}
func (s *myStructType) setValue(v int) {
	fmt.Println("I set", v)
	s.X = v
}
func (s *myStructType) getValue() int {
	return s.X
}

func main() {
	var a myStructType = myStructType{123}
	//Вызов статического метода, первым дополнительным аргументом является объект!
	fmt.Println("I read", (*myStructType).getValue(&a)) // 123
	myStructType.doSomething(a, 1)                      // 1
	(*myStructType).setValue(&a, 2)                     // 2
	fmt.Println("I read", (*myStructType).getValue(&a)) // 2
}

Каждый самоопределяемый тип данных становится объектом и может получать методы

  • Методы могут быть применены ко всем типам данных, объявленным в одном и том же исходном файле.
package main

import "fmt"

type noClass int // Объявление нового типа данных берет на себя задачи "класса". В этом примере со свойствами типа int.
func (self noClass) calcMethod() int { // "self" для обозначения переменной-получателя - плохой стиль, но в примере это помогает пониманию!
	return int(self * self)
}
func (self noClass) calcMethodWithArgument(argument int) int {
	return int(int(self) * argument)
}

func main() {
	var instanz noClass
	instanz = 2
	//или:
	//instanz := noClass(2)                        // Присваивание при реализации напоминает "конструктор"
	fmt.Println(instanz.calcMethod())              // 4
	fmt.Println(instanz.calcMethodWithArgument(3)) // 6
}

Интерфейсы

  • Интерфейс — это тип данных.
  • Интерфейс — это тип данных с методическими свойствами.
  • Интерфейс — это тип данных, который необходимо реализовывать вместе со всеми его методами.
  • Экземпляр интерфейса знает только методы, а не поля, зависящие от типа!
  • Методы интерфейса могут быть назначены различным типам данных приемника. В результате интерфейс, не ограниченный никакими методами, в принципе может хранить значения всех типов данных!
  • Интерфейс требуется, если в качестве параметров должны передаваться не значения, а методы работы со значениями.
  • Интерфейс также можно использовать для включения полиморфизма — когда функции (или методы) с одинаковыми именами и одинаковыми параметрами передачи могут выполнять разные задачи.

Интерфейс не имеет специального типа данных

package main

import "fmt"

type datentyper interface {}

func main() {
	var a datentyper

	a = "abc"
	fmt.Println(a) // abc

	a = 123
	fmt.Println(a) // 123
}

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

package main

import (
	"fmt"
	"strconv"
	"time"
)

type datentyper interface {
	dosomething() string
}

type number int

func (receiver number) dosomething() string {
	return strconv.Itoa(int(receiver) + 456)
}

type text string

func (receiver text) dosomething() string {
	return string(receiver + "def")
}

/* Выдает ошибку:
./main.go:27:6: cannot define new methods on non-local type time.Time
func (receiver time.Time) dosomething() string {
	return string(receiver.String())
}

Вместо этого объявите локальный тип:
*/

type mytime time.Time

func (receiver mytime) dosomething() string {
	return string(time.Time(receiver).String())
}

func main() {
	var a datentyper

	a = text("abc")
	fmt.Println(a.dosomething()) //abcdef

	a = number(123)
	fmt.Println(a.dosomething()) // 579

	a = mytime(time.Now())
	fmt.Println(a.dosomething()) // 2020-04-14 16:17:52.159587904 +0200 CEST m=+0.000060994
}

Интерфейс с типом данных struct

package main

import (
	"fmt"
	"strconv"
)

type datentyper interface {
	dosomething() string
}

type datentyp struct {
	number int
	text   string
}

func (receiver datentyp) dosomething() string {
	return strconv.Itoa(int(receiver.number)) + string(receiver.text)
}

func main() {
	var a datentyper

	a = datentyp{number: 123, text: "abc"}
	fmt.Println(a.dosomething()) //123abc
}

Почему интерфейсы с типом данных struct все еще не являются классами

В отличие от классов в объектно-ориентированных языках программирования, таких как C++, Java или Python, интерфейсы Go разрешают доступ к своим методам, но не к своим атрибутам!

Следующий пример взят из головы и не исполняемый:

package main

import (
	"fmt"
	"strconv"
)

type datentyper interface {
	dosomething() string
	number        int    // НЕВЕРНО
	text          string // НЕВЕРНО
}

type datentyp struct {
	number int
	text   string
}

func (receiver datentyp) dosomething() string {
	return strconv.Itoa(int(receiver.number)) + string(receiver.text)
}

func main() {
	var a datentyper

	a = datentyp{number: 123, text: "abc"}
	fmt.Println(a.dosomething()) //123abc
	fmt.Println(a.number)        //123 ? НЕВЕРНО!
	fmt.Println(a.text)          //abc ? НЕВЕРНО!
}

Интерфейс и приемник указателя

package main

import "fmt"

type datatyper interface {
	set(int)
	pset(int)
	get() int
	pget() int
}

type datatype struct {
	X int
}

func (receiver datatype) set(i int) {
	receiver.X = i
}
func (receiver *datatype) pset(i int) {
	receiver.X = i
}
func (receiver datatype) get() int {
	return receiver.X
}
func (receiver *datatype) pget() int {
	return receiver.X
}

func main() {
	var b datatyper
	//b = datatype{} // неверно
	b = &datatype{} // верно
	b.set(1)
	fmt.Println(b.get())  // 0 а не 1
	fmt.Println(b.pget()) // 0 а не 1
	b.pset(2)
	fmt.Println(b.get())  // 2
	fmt.Println(b.pget()) // 2
}

Интерфейс может быть реализован по-разному

Три почти одинаковых примера:

Реализация необходимых методов через структуру; интерфейс "io.Writer" требует только реализации функции "write"

package main

import (
	"errors"
	"fmt"
)

type myStruct struct{}

func (ms myStruct) Write(p []byte) (int, error) {
	fmt.Println(p, string(p)) // [71 111 32 114 111 99 107 115] Go rocks
	return 1, errors.New("Какая-то ошибка")
}

func main() {
	var w myStruct
	fmt.Fprintf(w, "Go rocks")
}

Реализация интерфейса с самоопределяемыми свойствами

package main

import (
	"errors"
	"fmt"
)

type myInterface interface {
	Write(p []byte) (int, error)
}

type myStruct struct{}

func (ms myStruct) Write(p []byte) (int, error) {
	fmt.Println(p, string(p)) // [71 111 32 114 111 99 107 115] Go rocks
	return 1, errors.New("Какая-то ошибка")
}

func main() {
	var w myInterface
	w = myStruct{}
	fmt.Fprintf(w, "Go rocks")
}

Реализация интерфейса с унаследованными свойствами

package main

import (
	"errors"
	"fmt"
	"io"
)

type myInterface interface {
	io.Writer
}

type myStruct struct{}

func (ms myStruct) Write(p []byte) (int, error) {
	fmt.Println(p, string(p)) // [71 111 32 114 111 99 107 115] Go rocks
	return 1, errors.New("Какая-то ошибка")
}

func main() {
	var w myInterface
	w = myStruct{}
	fmt.Fprintf(w, "Go rocks")
}

Примеры реализации через наследование

Метод os.Stdout реализует интерфейс "ReadWriter":

package main

import (
	"fmt"
	"os"
)

type Reader interface {
	Read([]byte) (int, error)
}

type Writer interface {
	Write([]byte) (int, error)
}

type ReadWriter interface {
	Reader
	Writer
}

func main() {
	var w ReadWriter

	w = os.Stdout // Здесь интерфейс ReadWriter наследует и реализует свойства интерфейса Writer,
	// поскольку "Stdout", как переменная пакета "os", также реализует тип данных "File", 
	// объявленный в пакете «os». Read() и Write() являются методами типа данных "File" и
	// поэтому соответствуют требованиям интерфейсов.
	// Интерфейс io.Writer ожидается от
	// func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
	// (см. https://golang.org/pkg/fmt/# fprintf и https://golang.org/pkg/io/#Writer)
	fmt.Fprintf(w, "Лучше, чем в Java!\n")
}

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

package main

import (
	"fmt"
	"io"
	"os"
)

type ReadWriter interface {
	io.Reader
	io.Writer
}

func main() {
	var w ReadWriter

	w = os.Stdout
	fmt.Fprintf(w, "Лучше, чем в Java!\n")
}

Утверждения типа

package main

import "fmt"

func main() {
	var t interface{}
	t = 123
	a := t.(int)
	fmt.Println(a) // 123

	/*В случае успеха утверждения типа возвращают "true" в качестве
	второго параметра, в противном случае — "false".*/
	ai, ok := t.(int)
	fmt.Println(ai, ok) // 123 true
	af, ok := t.(float64)
	fmt.Println(af, ok) // 0 false

	// Пример: утверждение типа в блоке if-else в качестве проверки типа
	var b interface{}
	// Попробуйте оба варианта:
	//b = "Blabla"
	b = 123
	if str, ok := b.(string); ok {
		fmt.Printf("Строка b содержит значение: %s\n", str)
	} else {
		fmt.Println("b не является строкой")
	}
}

Переключатели типа

package main

import (
	"fmt"
)

var t interface{}

func main() {
	a := 123 // Попробуйте так же с 123.456 oder "123" ...
	t = &a   // ... и стереть &.

	//switch t.(type) {
	switch u := t.(type) { // то же самое, но с присвоением значения
	default:
		fmt.Printf("неизвестный тип %T\n", u)
	case bool:
		fmt.Printf("boolean (%t)\n", u)
	case int:
		fmt.Printf("integer (%d)\n", u)
	case float64:
		fmt.Printf("float64 (%g)\n", u)
	case string:
		fmt.Printf("string (%s)\n", u)
	case *bool:
		fmt.Printf("указатель на boolean (%t)\n", *u)
	case *int:
		fmt.Printf("указатель на integer (%d)\n", *u)
	case *float64:
		fmt.Printf("указатель на float64 (%g)\n", *u)
	}
}

Примечание
В type switches оператор fallthrough не допускается

Применение: Составные типы с использованием Interface и Struct

package main

import (
	"fmt"
	"strconv"
)

type universaltype interface{} //Пустой интерфейс универсального типа.

type Number interface {
	String() string    // Метод интерфейса fmt.Stringer теперь также становится методом интерфейса Number.
	latin(bool) string // Метод интерфейса Number
}

type Numb struct {
	universal universaltype
}

type NumbOhne struct {
	universal universaltype
}

//С помощью этого метода Number реализует интерфейс fmt.Stringer, необходимый для fmt.Println, fmt.Printf и т. д.
func (n Numb) String() string {

	switch t := n.universal.(type) {
	default:
		return fmt.Sprintf("Недопустимый тип: %T", t)
	case int:
		return strconv.Itoa(t) + n.latin(false)
	case string:
		return t + n.latin(true)
	}
}

func (n NumbOhne) String() string {

	switch t := n.universal.(type) {
	default:
		return fmt.Sprintf("Недопустимый тип: %T", t)
	case int:
		return strconv.Itoa(t)
	case string:
		return t
	}
}

func (n Numb) latin(romanOnOff bool) string {
	if romanOnOff == true {
		return " (Римское число)"
	} else {
		return " (Арабское число)"
	}

}

func main() {
	var nr1a, nr1b Number // nr1a и nr1b реализуют интерфейс.
	nr1a = Numb{"LXXXVIII"}
	nr1b = Numb{88}
	fmt.Printf("%s = %s\n", nr1a, nr1b) // LXXXVIII (Римское число) = 88 (Арабское число)
	// Все остальные вызовы методов выполняются напрямую или без необходимости реализации интерфейса!
	var nr2a = Numb{"LXXXIX"}
	var nr2b = NumbOhne{89}
	fmt.Printf("%s = %s\n", nr2a, nr2b)                   // LXXXIX (Римское число) = 89
	fmt.Printf("%s = %s\n", NumbOhne{"XC"}, NumbOhne{90}) // XC = 90
	fmt.Println(Numb{123.456})                            // Недопустимый тип: float64
}

Применение: Тип интерфейса как переменный параметр

package main

import (
	"fmt"
)

type universaltype interface{}

func main() {
	fmt.Println(adder(1, 2, 3))       // 6
	fmt.Println(adder("A", "B", "C")) // "ABC"
}

func adder(args ...universaltype) universaltype {
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("Error: %v", err)
		}
	}()

	var (
		i_total int
		s_total string
		total   universaltype
	)

	for _, v := range args { // Перебор всех аргументов
		switch t := v.(type) {
		default:
			panic(fmt.Sprintf("unexpected type %T\n", t))
		case int:
			i_total += v.(int)
			total = i_total
		case string:
			s_total += v.(string)
			total = s_total
		}
	}
	return total
}

Полиморфизм

... возможен с интерфейсами, поскольку интерфейсы предписывают "методы" (функции), но оставляют форму "атрибутов" (переменных) полностью открытой.

package main

import (
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

type confuser interface {
	shuffle(time.Time) string
}

type numerals struct {
	n int
}

type words struct {
	w string
}

func (r numerals) shuffle(t time.Time) string {
	defer fmt.Println(t)

	numerallist := []rune(strconv.Itoa(r.n))

	rand.Seed(time.Now().Unix())
	rand.Shuffle(len(numerallist), func(i, j int) { numerallist[i], numerallist[j] = numerallist[j], numerallist[i] })

	return string(numerallist)
}

func (r words) shuffle(t time.Time) string {
	defer fmt.Println(t)

	wordlist := strings.Split(r.w, " ")

	rand.Seed(time.Now().Unix())
	rand.Shuffle(len(wordlist), func(i, j int) { wordlist[i], wordlist[j] = wordlist[j], wordlist[i] })

	return strings.Join(wordlist, " ")
}

func letsrock(c confuser) {
	fmt.Println(c)
	fmt.Println(c.shuffle(time.Now()))
}

func main() {
	inputn := numerals{1234567890}
	inputw := words{"пингвин Tux ест рыбу"}

	letsrock(inputn) /*
		{1234567890}
		2020-09-26 20:59:38.419783812 +0200 CEST m=+0.000065019
		3265847091
	*/
	letsrock(inputw) /*
		{пингвин Tux ест рыбу}
		2020-09-26 21:18:38.019358421 +0200 CEST m=+0.000169138
		пингвин ест рыбу Tux
	*/
}

Обработка ошибок

Тип данных error

  • Go не полагается на блоки и исключения try-catch-finally при выдаче ошибок, а использует для этого тип данных error.
  • Ниже приведены два примера выдачи ошибок, реализующие функцию (type error interface { Error() string }):
package main

import (
	"fmt"
	"time"
)

type ErrorMessage struct {
	TimeNow time.Time
	Message string
}

func (e *ErrorMessage) Error() string { // Ввод функции интерфейса ошибок Error() как метод структуры ErrorMessage
	return fmt.Sprintf("%v \n%s", e.TimeNow, e.Message)
}

func reportError() error {
	return &ErrorMessage{time.Now(), "Произошла ошибка 123"}
}

func main() {
	if err := reportError(); err != nil {
		fmt.Println(reportError()) 	// 2022-05-09 14:54:09.662400341 +0200 CEST m=+0.000047280
									// Произошла ошибка 123
	}
}

package main

import (
	"fmt"
)

type ErrorMessage string

func (e ErrorMessage) Error() string {
	return string(e)
}

func reportError() error {
	return ErrorMessage("Ошибка!!!")
}

func main() {
	if err := reportError(); err != nil {
		fmt.Println(err) // Ошибка!!!
	}
}

  • Вместо того, чтобы использовать собственную функцию для реализации интерфейса ошибок, вы можете использовать встроенную функцию errors.New() для выдачи ошибки:
package main

import (
	"errors"
	"fmt"
)

func reportError() error {
	return errors.New("Grrr!")
}

func main() {
	if err := reportError(); err != nil {
		fmt.Println(err) // Grrr!
	}
}

Отлавливание ошибок

defer func()
Оператор defer указывает, что функция должна выполняться после оператора return (то есть в момент завершения закрывающей функции). Если есть несколько операторов defer, они выполняются один за другим в обратном порядке (последний вошел, первый вышел) после завершения функции.
panic(interface{})
Встроенная функция для немедленной остановки выполнения функции с сообщением об ошибке.
recover() interface{}
Перехватывает состояние паники(panic) функции, чтобы восстановить контроль. Полезно только внутри функций, запускаемых с помощью defer.
package main

import "fmt"

func fehlerhafteFunktion() {
	defer func() { // 7.
		if err := recover(); err != nil { // Поскольку ошибка была обнаружена ранее и больше не существует, ...
			fmt.Println("Не выводится, потому что ошибка уже была поймана ранее!") // ... выполнение этой строки кода предотвращается.
		}
	}()
	defer fmt.Println("Здесь тоже что-то есть!") // 6.
	defer func() {                               // 4.
		fmt.Printf("Произошла ошибка: \"%v\". Ошибка поймана!\n", recover()) // 5.
	}()
	fmt.Println("До того, как выскочит ошибка") // 2.
	panic("Java")                               // 3.
	fmt.Println("Если бы не было ошибки")
}

func main() {
	fehlerhafteFunktion()            // 1.
	fmt.Println("А это идет после.") // 8.
}

Горутины|Goroutine (параллелизм)

Асинхронное выполнение

Горутины работают в одном и том же адресном пространстве, поэтому доступ к общей памяти должен быть синхронизирован. В основной программе важно дождаться параллельных подпрограмм.

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func say(s string, proc int) {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < 5; i++ {
		time.Sleep(time.Duration(r.Intn(10000)) * time.Millisecond) // Генератор случайных чисел делает разное время выполнения отдельных подпрограмм более заметным.
		fmt.Printf("Процесс=%v, Круг=%v, Результат=[%v]\n", proc, i, s)
	}
	fmt.Printf("\nПроцесс %v закончен 😎\n\n", proc)
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Обычно все процессы выполняются в одном потоке. Сообщите компилятору, сколько потоков нужно открыть.

	go say("Bye", 1) // Когда подпрограмма выполняется с ключевым словом go, основная программа продолжает работу без ожидания.
	go say(" ", 2)
	go say("bye", 3)
	go say(", ", 4)
	go say("Java", 5)
	go say("!", 6)

	// Функция была запущена 6 раз. И наша программа завершена без возможности выполнения ни одной функции say. Вот почему мы прописываем время ожидания для нашей основной программы:
	time.Sleep(time.Second * 60)
	fmt.Println("Готово!")
}

Синхронизация через WaitGroup

Заранее определенное время ожидания, как в прошлом примере, не имеет смысла. Лучше дождаться выполнения отдельных подпрограмм.

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"sync"
	"time"
)

var wg sync.WaitGroup

func say(s string, proc int) {
	defer wg.Done()
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < 5; i++ {
		time.Sleep(time.Duration(r.Intn(10000)) * time.Millisecond) // Генератор случайных чисел делает разное время выполнения отдельных подпрограмм еще более заметным.
		fmt.Printf("Процесс=%v, Круг=%v, Результат=[%v]\n", proc, i, s)
	}
	fmt.Printf("\nПроцесс %v закончен 😎\n\n", proc)
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Обычно все процессы выполняются в одном потоке. Сообщите компилятору, сколько потоков нужно открыть.
	wg.Add(6)
	go say("Bye", 1) // Когда подпрограмма выполняется с ключевым словом go, основная программа продолжает работу без ожидания!
	go say(" ", 2)
	go say("bye", 3)
	go say(", ", 4)
	go say("Java", 5)
	go say("!", 6)
	wg.Wait() // Вместо того, чтобы ждать, нам лучше дождаться окончания последней функции

	fmt.Println("Готово! 👍👍👍")
}

Синхронизация через взаимное исключение ("Mutex")

Mutex (сокр. от mutual exclusion) позволяет устанавливать и снимать блокировки.

package main

import (
	"fmt"
	"sync"
	"time"
)

var mtx sync.Mutex

func doSomething() {
	mtx.Lock()
	for i := 0; i <= 1000; i++ {
		fmt.Println("Текст ПЕРВОЙ функции номер", i)
	}
	fmt.Printf("----------\nГотово!\n\n")
	mtx.Unlock()
}

func doSomethingElse() {
	defer mtx.Unlock()

	mtx.Lock()
	for i := 0; i <= 1000; i++ {
		fmt.Println("Текст ВТОРОЙ функции номер", i)
	}
	fmt.Printf("----------\nГотово!\n\n")
}

func main() {

	// Блокировка предотвращает запуск второй функции до окончания работы первой.
	// Без блокировки обе функции будут одновременно выводить текст.
	go doSomething()
	go doSomethingElse()

	time.Sleep(3 * time.Second)
}

Связь между подпрограммами

  • Связь между подпрограммами осуществляется через каналы. Каналы типизированы (как и переменные). Канал можно понимать как именованный конвейер или семафор.

Типы каналов

chan   // Двусторонний
chan<- // Только отправляющий
<-chan // Только получающий

Объявление канала

var chA chan int
chA = make(chan int)      // Канал обычно определяется в основной программе и передается подпрограммам как переменная.
chB := make(chan int, 10) // Буферизованный канал, который может буферизовать 10 значений.
Примечание о буферах каналов
Если канал не буферизован или достигнуто максимальное количество переменных для буферизации, то:
* Отправитель будет разблокирован, когда канал будет прочитан и станет пустым.
* Получатель будет разблокирован, когда канал перестанет быть пустым и будет иметь значения
* Емкость канала ограничена максимум 100 элементами!

Канал I/O

ch <- v   // Отправка "v" в канал
v := <-ch // Присвоение значения переменной "v" из канала
Примечание
При записи в канал указатель программы остается в этой точке до тех пор, пока канал не будет снова прочитан. Этого можно избежать, если запись выполняется анонимной функцией (go func(){mychannel <- 123}()).
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func think(s string, proc int, c chan string) {
	var timestamp [2]int64
	timestamp[0] = time.Now().Unix()
	for i := 0; i < 5; i++ {
		rand.Seed(time.Now().UnixNano())
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) // Генератор случайных чисел делает разное время выполнения отдельных подпрограмм еще более заметным.
		c <- fmt.Sprintf("Процесс=%v, Круг=%v, Результат=[%v]\n", proc, i, s)
	}
	timestamp[1] = time.Now().Unix()
	c <- fmt.Sprintf("===================================\nПроцесс %v закончен за %v сек.!\n===================================\n", proc, (timestamp[1] - timestamp[0]))
}

func say(l int, c chan string) {
	for i := 1; i <= l; i++ {
		cvalue, cstatus := <-c
		if cstatus {
			fmt.Println(cvalue)
		} else {
			break
		}
	}
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU()) // Обычно все процессы выполняются в одном потоке. Сообщите компилятору, сколько потоков нужно открыть.
	ChanelNo5 := make(chan string, 1)    // Буферизованный вывод здесь не нужен, но возможен

	go think("Bye", 1, ChanelNo5) // Каналы переносятся в подпрограммы как переменные
	go think(" ", 2, ChanelNo5)
	go think("bye", 3, ChanelNo5)
	go think(", ", 4, ChanelNo5)
	go think("Java", 5, ChanelNo5)
	go think("!", 6, ChanelNo5)
	say((6*5 + 6), ChanelNo5)
	close(ChanelNo5) // Каналы могут быть снова закрыты, когда они больше не нужны
	fmt.Println("ГОТОВО!!!!1!!!!1111111!")
}

Выборочное ожидание определенной активности канала

Блок select, очень похожий на структуру switch, позволяет обслуживать несколько каналов. Канал, который готов (как для чтения, так и для записи), имеет приоритет. Если несколько каналов находятся в режиме ожидания, приоритеты выбираются случайным образом. Существует также условие «по умолчанию», которое будет выполнено, если ни один канал не будет готов. Если условие по умолчанию опущено, сделайте блокировку, пока канал не будет готов.

package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func think(s string, proc int, c chan string, stop chan bool) {
	var timestamp [2]int64
	timestamp[0] = time.Now().Unix()
	for i := 0; i < 5; i++ {
		rand.Seed(time.Now().UnixNano())
		time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
		c <- fmt.Sprintf("Процесс=%v, Круг=%v, Результат=[%v]\n", proc, i, s)
	}
	timestamp[1] = time.Now().Unix()
	c <- fmt.Sprintf("\nПроцесс %v закончен за %v сек.!\n\n", proc, (timestamp[1] - timestamp[0]))
	//Пишет в канал,
	stop <- true //и завершает все остальные процессы
}

func say(c chan string, stop chan bool) {
	for {
		select {
		case cvalue, cstatus := <-c:
			if cstatus {
				fmt.Print(cvalue)
			}
		case <-stop:
			return
		default:
			time.Sleep(1 * time.Millisecond)
		}
	}
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ChanelNo5 := make(chan string, 1)
	ChanelNo19 := make(chan bool, 6) // Когда значение этого канала станет true, все процессы будут остановленны.

	go think("Bye", 1, ChanelNo5, ChanelNo19)
	//say(ChanelNo5, ChanelNo19)
	go think(" ", 2, ChanelNo5, ChanelNo19)
	//say(ChanelNo5, ChanelNo19)
	go think("bye", 3, ChanelNo5, ChanelNo19)
	//say(ChanelNo5, ChanelNo19)
	go think(", ", 4, ChanelNo5, ChanelNo19)
	//say(ChanelNo5, ChanelNo19)
	go think("Java", 5, ChanelNo5, ChanelNo19)
	//say(ChanelNo5, ChanelNo19)
	go think("!", 6, ChanelNo5, ChanelNo19)
	say(ChanelNo5, ChanelNo19)

	fmt.Println("ОНО РАБОТАЕТ??!")
	close(ChanelNo5)
	close(ChanelNo19)
}

Предотвращение взаимной блокировки (Deadlock):
select {
case ch <- true:
default:
	fmt.Println("Заполни канал!")
}

или:

if len(ch) == cap(ch) {
	fmt.Println("Заполнил канал!!")
} else {
	fmt.Println("ВСЁ ХОРОШО!")
}

Тип данных Context

Этот тип данных оповещает о прекращении работы и вызывает return

package main 

import (
	"context"
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

const (
	rightString = "Oh Yeah"
	wrongString = "D'oh"
)

func doSth1(ctx context.Context) {
	defer wg.Done()

	select {
	case <-time.After(1 * time.Second):
		fmt.Println("just in time")
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
}

func doSth2(ctx context.Context, k string) {
	defer wg.Done()

	if v := ctx.Value(k); v != nil {
		fmt.Println("found value:", v)
		return
	}
	fmt.Println("key not found:", k)
}

func main() {
	fmt.Println("Context test 1 - Deadline:")
	d := time.Now().Add(time.Duration(3000 * time.Millisecond))
	ctx, cancel := context.WithDeadline(context.Background(), d)
	defer cancel()

	wg.Add(6)
	go doSth1(ctx)
	go doSth1(ctx)
	go doSth1(ctx)
	time.Sleep(3 * time.Second)
	go doSth1(ctx)
	go doSth1(ctx)
	go doSth1(ctx)
	wg.Wait()

	// Context test 1 - Deadline:
	// just in time
	// just in time
	// just in time
	// context deadline exceeded
	// context deadline exceeded
	// context deadline exceeded

	fmt.Println("\nContext test 2 - Value:")
	ctx = context.WithValue(context.Background(), "Oh Yeah", rightString)

	wg.Add(8)
	go doSth2(ctx, rightString)
	go doSth2(ctx, rightString)
	go doSth2(ctx, rightString)
	go doSth2(ctx, rightString)
	go doSth2(ctx, wrongString)
	go doSth2(ctx, wrongString)
	go doSth2(ctx, wrongString)
	go doSth2(ctx, wrongString)
	wg.Wait()

	// Context test 2 - Value:
	// key not found: D'oh
	// found value: Oh Yeah
	// found value: Oh Yeah
	// found value: Oh Yeah
	// key not found: D'oh
	// key not found: D'oh
	// key not found: D'oh
	// found value: Oh Yeah
}

go test

Правила названий файлов и функций

Код

  • Например в файле main.go
package main

import (
	"fmt"
	"math"
)

var input float64

func main() {

	fmt.Println("Enter number:")
	fmt.Scan(&input)
	fmt.Printf("math:     √%f=%.10f\n", input, math.Sqrt(input))
	fmt.Printf("homemade: √%f=%.10f\n", input, Sqrt(input))
}

func Sqrt(radikand float64) float64 {
	answer := float64(2)
	answerOld := float64(0)
	for i := 1; i <= 200; i++ {
		answer = answer - (answer*answer-radikand)/(2*answer)
		// fmt.Println(i, "[", answer, answerOld, "]")
		// Повторяем до тех пор, пока предыдущее число и текущее число не сравняются с точностью до 10 знаков после запятой
		if int(answer*1e10) == int(answerOld*1e10) {
			break
		}
		answerOld = answer
	}
	return answer
}

Выполнение

Модульные тесты

$ go test
=== RUN   TestMyTestingFunction
--- PASS: TestMyTestingFunction (0.00s)
PASS
ok  	mySqrt	0.008s

Benchmarks

$ go test -test.bench=.*
BenchmarkMyBenchmarkingFunctionHomemade-4   	10000000	       121 ns/op
BenchmarkMyBenchmarkingFunctionMath-4       	2000000000	         0.31 ns/op
PASS
ok  	mySqrt	1.996s
Подсказка: Бессистемное измерение времени выполнения сегментов кода
package main

import (
	"log"
	"math"
	"time"
)

func main() {
	timenow1 := time.Now()
	a := math.Sqrt(123456)
	log.Println(time.Since(timenow1)) //2018/11/07 20:30:56 152ns
	_ = a
}

Профилирование

$ go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
$ go tool pprof cpu.prof
Entering interactive mode (type "help" for commands)
(pprof) png > Schaubild.png
(pprof) weblist
(pprof) help

Проверка управления памятью

  • Garbage Collection(GC) выполняется только в динамической памяти (Heap)
  • Массивы (не слайсы!) находятся в стеке (Stack) и могут использоваться, чтобы избежать GC.
  • Присваивание 'nil' (a = nil) освобождает область памяти для GC; GC можно перехитрить, "продолжив использовать" область памяти (a = a[:0]).
  • GC запускается, как только доля вновь выделенных данных по отношению к данным, оставшимся после предыдущего GC, достигнет определенного процента:

import (
	"runtime"
	"runtime/debug"
)

func main() {
	
	debug.SetGCPercent(50) // Как только доля новых данных в heap (с момента последнего GC) достигает 50% доли старых данных, запускается GC
	debug.SetGCPercent(100) // Значение по умолчанию – 100 %.
	debug.SetGCPercent(-1) // GC будет полностью закрыт!
	runtime.GC() // GC запускается вручную
	
}

См. http://stackoverflow.com/questions/12277426/how-to-minimize-the-garbage-collection-in-go(Eng.)

Reflect — ссылки на ваш собственный код

package main

import (
	"fmt"
	"reflect"
)

type aObjectTyp struct {
	Maja  int
	willi float32
	flip  bool
	Max   string
}

var motz int
var Rempel int16

func main() {
	var aObject aObjectTyp = aObjectTyp{123, 456.789, true, "textExpalme1"}
	fmt.Println(reflect.ValueOf(aObject)) // {123 456.789 true textExpalme1}
	fmt.Println(reflect.TypeOf(aObject))  // main.aObjectTyp

	fmt.Println(reflect.ValueOf(aObject).Field(1))            // 456.789
	fmt.Println(reflect.TypeOf(aObject).FieldByName("willi")) // {willi main float32  8 [1] false} true

	fmt.Println(reflect.TypeOf(aObject).Field(2).Type)   // bool
	fmt.Println(reflect.TypeOf(aObject).Field(2).Index)  // [2]
	fmt.Println(reflect.TypeOf(aObject).Field(2).Name)   // flip
	fmt.Println(reflect.TypeOf(aObject).Field(2).Offset) // 12, т.к. 8 (int) + 4 (float32) = 12

	fmt.Println(reflect.TypeOf(aObject).Field(3).Offset) // 16, т.к. 8 (int) + 4 (float32) + 4 (bool) = 16

	fmt.Println(reflect.ValueOf(&aObject).Elem().Field(3)) // textExpalme1

	// Переменные можно описать с помощью указателя, но поля структуры необходимо экспортировать.
	fmt.Println(reflect.ValueOf(&aObject).Elem().Field(3).CanSet()) // true, т.к. "Max" экспортируется (public)
	fmt.Println(reflect.ValueOf(&aObject).Elem().Field(1).CanSet()) // false, т.к. "willi" не экспортируется (private)
	reflect.ValueOf(&aObject).Elem().Field(3).SetString("textExpalme2")
	//reflect.ValueOf(&aObject).Elem().Field(1).SetFloat(1.2345) было бы недопустимо!

	fmt.Println(reflect.ValueOf(&motz).Elem().CanSet())   // true, потому что "motz" — это переменная, а не поле структуры!
	fmt.Println(reflect.ValueOf(&Rempel).Elem().CanSet()) // true

	// При использовании независимого от типа "Set(x Reflect.Value)" поле, считанное из структуры, также должно быть экспортируемым!
	reflect.ValueOf(&motz).Elem().Set(reflect.ValueOf(aObject).Field(0))
}

Объявления через структуры

  • Так как невозможно выполнить объявление через структуры с range, можно использовать пакет Reflect:
package main

import (
	"fmt"
	"reflect"
)

type einObjektTyp struct {
	Maja  int
	willi float32
	flip  bool
	Max   string
}

func main() {
	var einObjekt einObjektTyp = einObjektTyp{123, 456.789, true, "textExpalme1"}

	for i, rType, rValue := 0, reflect.TypeOf(&einObjekt).Elem(), reflect.ValueOf(&einObjekt).Elem(); i < rType.NumField(); i++ {
		fmt.Println(rType.Field(i), rValue.Field(i)) /*
			{Maja  int  0 [0] false} 123
			{willi main float32  8 [1] false} 456.789
			{flip main bool  12 [2] false} true
			{Max  string  16 [3] false} textExpalme1
		*/
	}
}

Добавление кода C

Использование cgo через псевдо-пакет "C"

Флаги компилятора и компоновщика (CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS, DFLAGS, ...)
необходимы и объявляются как аннотации комментариев с директивой #cgo сразу над импортом "C".
package main

import (
	"fmt"
	Go "math"
	"time"
	"unsafe"
)

// #cgo CFLAGS: -O2 -march=native
// #cgo LDFLAGS: -lm
// #include <math.h>
// #include <stdio.h>
// #include <stdlib.h>
// #include <ctype.h>
/*

double mySqrt(int mydata)
{
	return sqrt(mydata);
}

struct
{
  int tag;
  char monat[10];
  int jahr;
  char *anlass;
} termin1;

struct anonym
{
  int tag;
  char monat[10];
  int jahr;
  char *anlass;
};
*/
import "C"

const steps int64 = 100000000

var (
	a  float64
	b  C.double
	pg *float64  // Указатель в Go
	pc *C.double // Указатель в C
	sg string    // Указатель в Go
	sc *C.char   // Указатель в C
	bg []byte    // Слайс байтов в Go
	ag [5]int32  // Int-Массив в Go
	ac [5]C.int  // Int-Массив в C
)

func main() {
	// Использование математики из пакета Go
	t0 := time.Now()
	for i := (int64)(1); i <= steps; i++ {
		a = Go.Sqrt(float64(i))
	}
	fmt.Println(time.Since(t0)) // Приблизительно 170 мс. времени выполнения нативной функции из пакета Go-math. Это значительно быстрее...

	// Использование математики из пакета C
	t1 := time.Now()
	for i := (C.int)(1); i <= C.int(steps); i++ {
		b = C.sqrt(C.double(i))
	}
	fmt.Println(time.Since(t1)) // ... чем при использовании math.h (около 5 сек.)

	// Напишите свою собственную C-функцию
	t2 := time.Now()
	for i := (C.int)(1); i <= C.int(steps); i++ {
		b = C.mySqrt(i)
	}
	fmt.Println(time.Since(t2)) // Прим. 5 сек.

	// Точно так же работают указатели в CGO.
	pc = &b              // Адрес из адресного пространства C
	pg = (*float64)(&b)  // Преобразование указателя C в указатель Go
	fmt.Println(pc, pg)  // Оба адреса идентичны (например, 0x77f3c8 0x77f3c8)
	pc = (*C.double)(pg) // Преобразование указателя Go в указатель C
	fmt.Println(pg)      // Напр. 0x77f3c8

	// обработка строк
	sg = "abcdefghijklmnopqrstuvwxyz"
	sc = C.CString(sg)              // Преобразование строки Go в строку C
	fmt.Println(C.GoString(sc))     // Преобразование строки C в строку Go (abcdefghijklmnopqrstuvwxyz)
	fmt.Println(C.GoStringN(sc, 5)) // Преобразование строки C определенной длины в строку Go (abcde)

	// обработка байтов
	bg = C.GoBytes(unsafe.Pointer(sc), 26) // Преобразование массива байтов из C в Go требует unsafe.Pointer
	fmt.Println(bg)                        // [97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122]
	bc := *(*[26]byte)(C.CBytes(bg))       // bg — небезопасный указатель на начало последовательности байтов. Преобразуйте его в безопасный указатель типа данных []byte и выведите его содержимое в bc. Спецификация длины массива здесь важна, так как C не знает слайсы!
	fmt.Println(bc)                        // [97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122]

	// Memory-Management
	defer C.free(unsafe.Pointer(sc))
	/*
		В C нет сборщика мусора, мы должны снова позаботиться об освобождении
		выделенной области памяти (с помощью malloc(), calloc() или realloc()),
		как в случае с указателями char на символьные строки
		неопределенной длины с помощью cgo когда происходит компиляция
	*/

	// Массивы/срезы - обмениваются указателями на их начальные адреса. Следует отметить, что указатель указывает на первый элемент массива/слайса: "&slice[0]", а не "&slice" !
	ag = [5]int32{1, 2, 3, 4, 5} // Опасность! Необходимо соблюдать правильную длину данных C.int соответствует int32 в Go!
	ac = *(*[5]C.int)(unsafe.Pointer(&ag))
	fmt.Println(ag, ac) // [1 2 3 4 5] [1 2 3 4 5] В случае с int64 результатом преобразования будет [1 2 3 4 5] [1 0 2 0 3]

	// Доступ к именованной структуре C
	C.termin1.tag = 24
	C.termin1.monat = *(*[10]C.char)(unsafe.Pointer(&[10]byte{'D', 'e', 'z', 'e', 'm', 'b', 'e', 'r'}))
	C.termin1.jahr = 2018
	C.termin1.anlass = C.CString("Рождество")
	fmt.Println(C.termin1) // {24 [68 101 122 101 109 98 101 114 0 0] [0 0] 2018 [0 0 0 0] 0x14d9840}

	// Доступ к безымянной структуре C через необязательный тег структуры
	var termin2 C.struct_anonym //Обратите внимание на префикс "C.struct_" перед тегом структуры, без которого анонимная структура не будет адресной!
	termin2.tag = 24
	termin2.monat = *(*[10]C.char)(unsafe.Pointer(&[10]byte{'D', 'e', 'z', 'e', 'm', 'b', 'e', 'r'}))
	termin2.jahr = 2018
	termin2.anlass = C.CString("Рождество")
	fmt.Println(termin2) // {24 [68 101 122 101 109 98 101 114 0 0] [0 0] 2018 [0 0 0 0] 0x14d9860}
}

Ссылка с C на Go

Как правило, требуются два файла кода Go, поскольку CGO генерирует два выходных файла C для экспортируемых функций, и в противном случае все определения C существовали бы там дважды, что недопустимо.

//Файл 1 из 2
package main

/*
#cgo CFLAGS: -O2 -march=native

extern void ccallsme(char *);

void callback2go(char *text)
{
  ccallsme(text);
}
*/
import "C"

func main() {
	C.callback2go(C.CString("Я, функция Go, была вызвана из функции C"))
}

Комментарий //export, расширенный до аннотации, необходим, чтобы CGO мог ссылаться на функцию Go в коде C:

//File 2 von 2
package main

//#cgo CFLAGS: -O2 -march=native
import "C"

import "fmt"

//export ccallsme
func ccallsme(text *C.char) {
	fmt.Println(C.GoString(text))
}

См. https://golang.org/cmd/cgo/(En.)

Совет
использование go tool cgo main.go очень полезно при работе с cgo. Он создает папку _obj в каталоге проекта с объектными кодами, сгенерированными компилятором. Например, файл _obj/_cgo_gotypes.go помогает с не таким простым преобразованием типов между C и Go.
Еще один совет
Параллелизм с подпрограммами, использующими cgo, может привести к непредвиденным ошибкам доступа к памяти, особенно при использовании внешних библиотек C, которые не заметны в отдельно выполняемом процессе. В этом случае фланкирование cgo-кода (глобально определенными!) мьютексами (sync.Mutex.Lock() до и sync.Mutex.Unlock() после) может обеспечить стабильность.

GUI

Распределенные системы

IDE

  • Универсальная среда разработки Microsoft Visual Studio Code настолько хороша, что я рекомендую ее даже самому несгибаемому фанату Linux! Это на данный момент мой выбор для проектов с разными форматами кода.
  • Тем не менее, мой абсолютный фаворит, особенно для Go, — это LiteIDE. Он достаточно "легкий", но при этом удивительно мощный и функциональный, а так же у него нет серьёзных багов. Все интуитивно доступно, а интерфейс очень продуманный. Единственное, чего мне не хватает, так это управления исходным кодом GIT.

Ссылки (всё на английском)

Benchmark

Benchmark Go vs. Java vs. Python