Туториал по Golang
Inhaltsverzeichnis
- 1 😈 Язык программирования Go ("Golang") 😈
- 2 Шпаргалка по Golang ("Cheat Sheet")
- 2.1 Компилятор
- 2.1.1 Gc
- 2.1.2 Gccgo
- 2.1.3 Cross-Compiling
- 2.1.3.1 В Linux создайте 32bit-.exe-Файл для Windows
- 2.1.3.2 В Linux создайте 64bit-.exe-Файл для Windows
- 2.1.3.3 Создайте исполняемый файл для Raspberry PI2 (ARMv6 (De.))
- 2.1.3.4 Создайте исполняемый файл для Raspberry PI3 (ARMv7 (De.))
- 2.1.3.5 Создайте исполняемый файл для 64-разрядных процессоров ARM (ARMv8, например Rock64 uи Ubuntu-Touch на телефоне Volla)
- 2.1.3.6 Создайте 64-битный исполняемый файл для Linux в Windows
- 2.1.3.7 Кросс-компиляция в Linux для Windows с помощью MXE
- 2.2 Пакеты
- 2.3 Функции
- 2.4 Управление памятью
- 2.4.1 Встроенные типы(Built-in Types)
- 2.4.2 Литералы
- 2.4.2.1 Строковые литералы (string literals)
- 2.4.2.2 Отдельные символы (rune literals)
- 2.4.2.3 Целые числа (integer literals)
- 2.4.2.4 Числа с плавающей запятой (floating point literals)
- 2.4.2.5 Мнимые числа (imaginary literals)
- 2.4.2.6 Разделители в числах (digits separators) – Без дальнейших функций, только косметика
- 2.4.3 Обьявления
- 2.4.4 Указатель (Pointer)
- 2.4.5 Тайпкэстинг (type casting)
- 2.4.6 Arrays, Slices, Strings, Maps, Ranges
- 2.4.6.1 Arrays
- 2.4.6.2 Slices
- 2.4.6.2.1 Различия между new() и make()
- 2.4.6.2.2 Создание слайса из массива
- 2.4.6.2.3 Сопирование слайсов с помощью copy()
- 2.4.6.2.4 Копирование части слайса в часть другого слайса
- 2.4.6.2.5 Расширение слайса с помощью append()
- 2.4.6.2.6 Обнуляет все элементы среза с помощью clear()
- 2.4.6.2.7 Слайсы являются не чем иным, как «указателями на массивы».
- 2.4.6.2.8 Вот почему так важно учитывать вместимость!
- 2.4.6.3 Strings
- 2.4.6.4 Maps
- 2.4.6.5 Ranges
- 2.4.7 "Generics" - комбинированные типы данных.
- 2.4.8 структуры
- 2.5 Операторы
- 2.6 Управляющие структуры
- 2.7 Объектноориентированность
- 2.8 Интерфейсы
- 2.8.1 Интерфейс не имеет специального типа данных
- 2.8.2 Интерфейс требует реализации методов для локального типа данных.
- 2.8.3 Интерфейс с типом данных struct
- 2.8.4 Интерфейс и приемник указателя
- 2.8.5 Интерфейс может быть реализован по-разному
- 2.8.6 Утверждения типа
- 2.8.7 Переключатели типа
- 2.8.8 Полиморфизм
- 2.9 Обработка ошибок
- 2.10 Горутины|Goroutine (параллелизм)
- 2.11 go test
- 2.12 Проверка управления памятью
- 2.13 Reflect — ссылки на ваш собственный код
- 2.14 Добавление кода C
- 2.15 GUI
- 2.16 Распределенные системы
- 2.17 IDE
- 2.18 Ссылки (всё на английском)
- 2.19 Benchmark
- 2.1 Компилятор
😈 Язык программирования Go ("Golang") 😈
Почему иммено Go? Внимание - субъективно!
- это придуманный с нуля эстетически приятный язык программирования.
Это перевод текста! Показать оригинал (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, например Rock64 uи Ubuntu-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) и явного синтаксиса наследования.
- Однако свойства объекта могут быть представлены с помощью трех основных компонентов:
- Типы данных: в Go любой самоопределяемый тип данных может стать объектом, т. е. принять свойства, которые класс обычно имеет в других языках ООП.
- Методы: в отличие от других языков ООП, методы в Go не являются функциями, определенными внутри класса. Вместо этого функции в Go определяются вне типа данных и объявляются как метод этого типа данных путем указания типа данных получателя.
- Интерфейсы: несколько методов могут быть связаны вместе для формирования интерфейсов в 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.
Ссылки (всё на английском)
- Оффициальные сайты:
- golang.org (в частности Specs и Packages) оффициальный сайт с документацией, туториалом и блогом
- godoc.org содержит документацию по коду для пакетов Go из Bitbucket, GitHub, Google Project Hosting и Launchpad
- goissues.org Здесь сообщают об ошибках в пакетах экосистемы Go, собирают и обсуждают их.
- gochanges.org показывает изменения в пакетах Go.
- Туториалы и учебники:
- Оффициальный туториал
- Очень подробный и иллюстрированный учебник Go от Big Yuuta на https://go-book.appspot.com
- Matt Aimonetti: Go Bootcamp — все, что вам нужно знать, чтобы начать работу с Go. (Онлайн-версия доступна бесплатно)
- Эффективное программирование на Go
- SliceTricks — помощь в работе с слайсами/массивами.
- Блоги:
- appliedgo.net – Applied Go — выходите за рамки туториалов по Go (СУПЕР блог для еще большего погружения в мир го!)
- A Gopher's Reading List
- Библиотеки и фрэймворки:
- Примеры кода и сравнения с другими языками:
- gobyexample.com (Учебник на все случаи жизни, понятный с первого взгляда.)
- https://yourbasic.org/ (Просто отличная сокровищница с Go)
- golangbyexample.com (Очередной туториал на все случаи жизни, более приятный, местами более подробный, главное можно найти не с первого взгляда 😉)
- gowebexamples.com (Go для Web-Разработчиков)
- govspy.peterbe.com (Go для Python-Разработчиков)
- godbolt.org Великолепный обзорщик компиляторов godbolt! Идеальный инструмент для проверки версий компиляторов, сравнения языков компиляторов и оптимизации кода.
- go-vs-python
- benchmark-go-vs-python-pypy
- Foren: