Вольный и расширенный перевод оригинального репозитория Go Cheat Sheet на русский язык, а также решения задач из курса Программирование на Golang.
- Другие ресурсы
- Участники
- Источники
- Описание языка
- Базовый синтаксис
- Параллелизм
- Форматированный вывод (printf)
- Строки (strings)
- Регулярные выражения (regexp)
- Математические вычисления
- Встроенные пакеты
Подборка открытых и бесплатных ресурсов для изучения Go на русском языке:
- Эффективный Go - перевод официальной документации Effective Go (не завершен и устарел).
- Эффективный Go - перевод от сентября 2024 года.
- Тур по Go - перевод официального тура по Go на русский язык.
- Go в примерах - исходный код для сборки статического сайта Go в примерах (форк gobyexample).
- Введение в программирование на Go (веб-версия) - перевод книги An Introduction to Programming in Go.
- Маленькая книга о Go - перевод The Little Go Book.
- Паттерны параллельного программирования Go.
- Курс по изучению Golang для начинающих.
- Обучение программированию на языке Go в тренажере (онлайн компилятор).
- Руководство по языку Go от Metanit.
- Шпаргалка по Go в переводе с Немецкого языка.
- Гайды Uber по написанию кода на Go - русский перевод оригинального репозитория.
- GUI на Golang на GTK+ 3.
Бесплатные курсы:
- Основы Go - курс от Яндекс Практикум (2 модуля на 30 часов) ⭐ 4.5 на Отзовике, IRecommend и TutorTop.
- Основы Go - курс от Хек Слет (34 урока, 97 тестов и 37 упражнений в тренажере).
- Разработка веб-сервисов на Golang - курс по Go от Mail Ru на платформе Coursera.
Курсы от Stepik с получением сертификата:
- Программирование на Golang ⭐ 4.7 (1.1K отзыва, 73K учащихся), 35 уроков, 64 теста, 94 задач.
- PRO Go. Основы программирования - ⭐ 4.8 (214 отзывов, 18K учащихся), 38 уроков, 121 тестов, 191 задач.
- Go - первое знакомство - ⭐ 4.9 (1.1K отзыва, 22K учащихся), 42 урока, 110 тестов, 45 задач.
- Твой Golang ⭐ 5.0 (50 отзывов, 3K учащийся), 154 урока, 29 часов видео, 186 тестов, 58 задач.
Если вы нашли ошибку или хотите расширить список шпаргалок, а также знаете другие источники для изучения, сообщите о них, внеся изменения через Pull Requests.
Большинство примеров кода исходного репозитория взяты из официального тура по Go, который является прекрасным введением для знакомства с языком.
Вы также можете использовать онлайн компилятор на официальном сайте или развернуть собственную песочницу Better Go Playground для запуска и проверки блоков кода.
- Императивный язык, где описывается последовательность шагов (инструкций), которые необходимо выполнить для достижения результата. В отличии от декларативных языков, где описывается результат, который нужно получить, оставляя процесс выполнения скрытым (например, как в
SQLилиHTML). - Используется статическая типизация для проверка типов переменных во время компиляции. Это когда тип переменной не может быть изменен после его присвоения (например, как в
TypeScriptв отличии отJavaScript). - Синтаксис похож на
C(но меньше скобок и нет точек с запятой в конце каждой строки), а структура — наOberon-2. - Компилируется в машинный код без использования промежуточных слоев (
Runtime, например, какJVMвJavaили.NETвC#), который должен быть установлен на машине для работы программы. - Нет классов, но есть структуры с методами.
- Не предоставляет подклассов, основанного на типах, но имеет возможность заимствовать части реализации, встраивая типы в структуру или интерфейс (embedding).
- Функции могут возвращать несколько значений и их можно присваивать переменным, так как они рассматриваются как объекты.
- Функции можно передавать в другие функции в качестве аргументов, а также функции могут возвращать другие функции как результат.
- Имеет замыкания (
closures), которые позволяют функциям хранит и использовать переменные из внешней области видимости, даже если она выполняется в другом контексте (например, за пределами этой области). - Невозможно напрямую изменять значение указателя с помощью арифметических операций (например,
ptr++). Это нужно, чтобы исключить возможные ошибки, такие как выход за пределы памяти или доступ к неправильным участкам памяти. - Встроенные примитивы параллелизма: горутины и каналы.
- Поддерживаются динамические и статические срезы (
slices, аналог списков или массивов в других языках, где элементы хранятся в порядке их добавления и индексируются числами), а также карты (maps, аналог словарей или хэш-таблиц, где содержится уникальный ключ и его значение).
go doc fmt.Println
package fmt // import "fmt"
func Println(a ...any) (n int, err error)
Println formats using the default formats for its operands and writes to
standard output. Spaces are always added between operands and a newline
is appended. It returns the number of bytes written and any write error
encountered.mkdir test
cd test
go mod init test
# Windows
notepad main.go
# Linux
nano main.gopackage main
import "fmt"
func main() {
// Базовый вывод
fmt.Println("Hello \n你好, नमस्ते, Привет, ᎣᏏᏲ")
// Многострочный строковый литерал без экранирования
hellomsg := `
"Hello" in Chinese is 你好 ('Ni Hao')\n
"Hello" in Hindi is नमस्ते ('Namaste')
`
fmt.Println(hellomsg)
}Запуск:
go run main.go
Выведет на экран:
Hello
你好, नमस्ते, Привет, ᎣᏏᏲ
"Hello" in Chinese is 你好 ('Ni Hao')\n
"Hello" in Hindi is नमस्ते ('Namaste')
package main
import "fmt"
func main() {
var name string
var age int
fmt.Print("Введите имя и возраст: ")
fmt.Scan(&name, &age)
fmt.Printf("Имя: %s, возраст: %d\n", name, age)
}| Оператор | Описание |
|---|---|
+ |
сложение |
- |
вычитание |
* |
умножение |
/ |
деление * |
% |
деление, возвращающие только остаток |
& |
побитовое и |
| |
побитовое или |
^ |
побитовое исключающее или * * |
&^ |
очистить бит (и нет) * * * |
<< |
сдвиг влево * * * * |
>> |
сдвиг вправо |
* Если оба операнда имеют целый тип (int, int8, int32, int64), результат также будет целым числом, при этом остаток отбрасывается. Если хотя бы один из операндов имеет тип с плавающей точкой (float32, float64), результат будет дробным числом.
* * Возвращает 0, если биты двух операндов равны, или 1, если биты двух операндов различны.
* * * Возвращает 0, если соответствующий бит второго операнда равен 1, или бит первого операнда (0 или 1), если соответствующий бит второго операнда равен 0.
* * * * Сдвигает все биты числа влево на указанное количество позиций (аналог умножения числа на 2 в степени количества сдвигов), а новые биты справа заполняются нулями.
| Оператор | Описание |
|---|---|
== |
равно |
!= |
не равно |
< |
меньше |
<= |
меньше или равно |
> |
больше |
>= |
больше или равно |
| Оператор | Описание |
|---|---|
&& |
логическое и |
|| |
логическое или |
! |
логическое отрецание |
| Оператор | Описание |
|---|---|
& |
указатель (адрес в памяти на переменную) |
* |
разыменовать указатель |
<- |
оператор отправки / получения |
Тип указывается после идентификатора (названия переменной):
var foo int // объявление без инициализации значения
var foo int = 42 // объявление с инициализацией
var foo, bar int = 42, 1302 // объявить и инициализировать несколько переменных одновременно
var foo = 42 // объявление с инициализацией с пропуском типа данных
foo := 42 // сокращение при объявление переменной (ключевое слово var опущено, тип данных определяется автоматически, работает только внутри функций)
const constant = "Это константа, которая используется для хранения неизменяемых данных"
// iota можно использовать для увеличения числа, начиная с 0
const (
_ = iota
a
b
c = 1 << iota
d
)
fmt.Println(a, b) // 1 2 (0 - пропускается)
fmt.Println(c, d) // 8 16 (2^3, 2^4)В разных лексических блоках возможно заново объявлять переменные с одинаковыми именами. Компилятор считывая ссылку на переменную, ищет ее объявление начиная с текущего блока и выше.
func main() {
var v int = 1
{
fmt.Println(v) // 1
// Переопределяем переменную с новым типом данных во вложенном блоке
var v string = "2"
fmt.Println(v) // 2
}
fmt.Println(v) // 1
}// Простая функция
func functionName() {}
// Функция с параметрами (тип идет после идентификаторов)
func functionName(param1 string, param2 int) {}
// Несколько параметров одного типа
func functionName(param1, param2 int) {}
// Объявление типа для возвращаемого значения (идет после скобок параметров или во вторых скобках, если значений несколько)
func functionName() int {
return 42
}
// Может возвращать несколько значений одновременно
func returnMulti() (int, string) {
return 42, "foobar"
}
var x, str = returnMulti()
// Возвращаем несколько именованных результатов
func returnMulti2() (n int, s string) {
n = 42
s = "foobar"
// Будут возвращены все значения объявленных переменных "n" и "s"
return
}
var x, str = returnMulti2()
func main() {
// Присвоить функцию переменной
add := func(a, b int) int {
return a + b
}
// Используйте имя переменной для вызова функции
fmt.Println(add(3, 4))
}// Дочерние функции могут получить доступ к переменным, объявленным в родительской функции
func scope() func() int {
outer_var := 2
foo := func() int { return outer_var }
return foo
}
func main() {
// Функция возвращяет в результате дочернюю функцию
test := scope()
// Вызываем дочернюю функцию для получения ее результата
fmt.Println(test())
}
func outer() (func() int, int) {
outer_var := 2
inner := func() int {
outer_var += 99 // изменена переменная, взятая из внешней области
return outer_var
}
inner()
return inner, outer_var // вернуть результат дочерней функции (200) и переменной (101)
}
func main() {
int1, int2 := outer()
fmt.Println(int1(), int2)
}Вариативная функция работает и вызывается как любая другая функция, за исключением того, что в нее возможно передать произвольное количество аргументов, используя ... перед типом данных указанного параметра.
package main
import "fmt"
// Функция принимаем любое количество аргументов с типом данных int и возвращяет 2 значения
func adder(args ...int) (int, int) {
sum := 0
// Перебирает все переданные аргументы в цикле
for _, a := range args {
sum += a
}
// Возвращяем 2 значения: количество аргументов и их сумму
return len(args), sum
}
// Функция принимает любое количество аргументов с любым типом данных, используя пустой интерфейс interface{}
func other(args ...any) {
for i := range args {
fmt.Print(args[i], " ")
}
}
func main() {
fmt.Println(adder(2, 2)) // 2 4
nums := []int{10, 20, 30} // создаем срез для передачи в функцию
fmt.Println(adder(nums...)) // 3 60
other(1, 1.2, "string", true) // 1 1.2 string true
}bool // логический тип (принимает true или false)
string // строка (текст)
int int8 int16 int32 int64 // знаковые целые числа (signed integer), могут быть как положительными, так и отрицательными
uint uint8 uint16 uint32 uint64 uintptr // беззнаковые целые числа (unsigned integer), могут быть только положительными или равными нулю
byte // псевдоним для uint8 (диапазон значений от 0 до 255)
rune // псевдоним для типа int32, представляет собой кодовую точку (Unicode code point) - это число, соответствующее символу в стандарте Unicode
// В отличие от char в некоторых языках (например, C/C++), который обычно занимает 1 байт и хранит символы ASCII, rune занимает 4 байта
// и может хранить любой символ Unicode, включая буквы разных алфавитов, эмодзи и спецсимволы
float32 float64 // число с плавающей точкой одинарной и двойной точности
complex64 complex128 // комплексное число (1 + 2i или 3.14 + 4.2i), имеющие реальную и мнимую часть
interfae{} // универсальный тип, который может позволяет работать с переменными неизвестного или изменяющегося типа| Тип данных | Описание | Диапазон значений |
|---|---|---|
| uint8 | Беззнаковые 8-битные целые числа | от 0 до 255 |
| uint16 | Беззнаковые 16-битные целые числа | от 0 до 65535 |
| uint32 | Беззнаковые 32-битные целые числа | от 0 до 4294967295 |
| uint64 | Беззнаковые 64-битные целые числа | от 0 до 18446744073709551615 |
| int8 | Знаковые 8-битные целые числа | от -128 до 127 |
| int16 | Знаковые 16-битные целые числа | от -32768 до 32767 |
| int32 | Знаковые 32-битные целые числа | от -2147483648 до 2147483647 |
| int64 | Знаковые 64-битные целые числа | от -9223372036854775808 до 9223372036854775807 |
Все предварительно объявленные идентификаторы Go определены в пакете builtin.
var i int = 42
var f float64 = float64(i) // преобразуем тип данных int в float64
var u uint = uint(f) // преобразуем тип данных float64 в unit
// Альтернативный синтаксис
i := 42
f := float64(i)
u := uint(f)func main() {
// Базовый
if x > 10 {
return x
} else if x == 10 {
return 10
} else {
return -x
}
// Возможно поставить одно утверждение перед условием
if a := b + c; a < 42 {
return a
} else {
return a - 42
}
// Утверждение (проверка) типа внутри условия
var val interface{} = "foo"
// Проверяется, содержит ли переменная val значение типа string
if str, ok := val.(string); ok {
// Если тип не совпадает, значение не вернется.
// При этом panic не вызывается, т.к. используется безопасное утверждение типа (ok)
fmt.Println(str)
}
}После выполнения условия при использование переключателей, прерывания обрабатываются автоматически.
package main
import (
"fmt"
"os"
)
func main() {
var operatingSystem string = runtime.GOOS
// Используем оператор (ключевое слово) switch
switch operatingSystem {
case "darwin":
fmt.Println("Используется macOS")
case "linux":
fmt.Println("Используется Linux")
// Условие по умолчанию (аналог else в if)
default:
fmt.Println("Используется Windows, OpenBSD, FreeBSD или другая")
}
}
// Как в случае с "for" и "if", возможно иметь оператор присваивания перед значением switch
switch os := runtime.GOOS; os {
case "darwin": ...
}
// Возможно использовать сравнения
number := 42
switch {
case number < 42:
fmt.Println("Переданное значение:", number, "меньше 42 в условие")
case number == 42:
fmt.Println("Переданное значение:", number, "равно 42 в условие")
case number > 42:
fmt.Println("Переданное значение:", number, "больше 42 в условие")
}
// Все случаи могут быть представлены в виде списков, разделенных запятыми
var char byte = '?'
switch char {
case ' ', '?', '&', '=', '#', '+', '%':
fmt.Println("Переданное значение присутствует в списке")
}Переключение типа похоже на обычный оператор switch, но в условиях указывается типы (а не значения), которые сравниваются с типом значения, содержащегося в данном значении интерфейса.
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Число %v равно %v по типу данных\n", v, v*2)
case string:
fmt.Printf("Значение %q равно %v bytes\n", v, len(v))
default:
fmt.Printf("Тип %T неизвестен\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
// Число 21 равно 42 по типу данных
// Значение "hello" равно 5 bytes
// Тип bool неизвестенВ Go используются только универсальные циклы for, другие операторы (например, while или until) отсутствуют.
// Используется 9 интераций с 1 по 9 (до 10)
// for [инициализация счетчика]; [условие проверки счетчика для продолжение (если true) или остановки цикла]; [изменение счетчика]{
for i := 1; i < 10; i++ {
}
// Цикл (loop) - while
for ; i < 10; {
}
// Если есть только условие, точки с запятой опускаются
for i < 10 {
}
// Если опустить условие, равноценно использованию бесконечного цикла while (true)
var i = 1
for {
i++
// Для остановки цикла используется оператор break (при достижение соблюдения условия)
if i >= 10 {
break
}
}
// Использование пропуска и прерывания в цикле
// Метка here (произвольное имя) позволяет указать целевой цикл, на который будут ссылаться операторы continue и break
here:
// Используем 2 интерации во внешнем цикле
for i := 0; i < 2; i++ {
// Используем 2 интерации во внутреннем цикле
for j := i + 1; j < 3; j++ {
if i == 0 {
// Пропустить интерацию внешнего цикла по названию его метки
continue here
}
fmt.Println(j)
if j == 2 {
// Завершить внутренний цикл
break
}
}
}
// 1-я интерация: внешний цикл с значением i=0 в внутреннем цикле пропускает интерацию внешнего цикла, т.к. срабатывает условие i==0
// 2-я интерация: внешний цикл с значением i=1 в внутреннем цикле пропускает условие i==0
// Переменная j получает значение 2, которое печатается и завершает внутренний (текущий) цикл во втором условие
// Программа завершается, т.к. интерации внешнего цикла закончились
there:
for i := 0; i < 2; i++ {
for j := i + 1; j < 3; j++ {
if j == 1 {
// Пропускаем интерацию внутреннего цикла
continue
}
fmt.Println(j)
if j == 2 {
// Завершаем выполнение внешнего цикла
break there
}
}
}
// 1-я интерация внешнего цикла начинается с i=0, в внутреннем цикле j=1 пропускает первую интерацию
// 2-я интерация внутреннего цикла пропускает первое условие, печатает на экран текущее значение j=2 и во втором условие завершает внешний циклpackage main
import "fmt"
// Функция, возвращающая название месяца через условную конструкцию switch
func getMonthName(month int) string {
switch month {
case 1:
return "January"
case 2:
return "February"
case 3:
return "March"
case 4:
return "April"
case 5:
return "May"
case 6:
return "June"
case 7:
return "July"
case 8:
return "August"
case 9:
return "September"
case 10:
return "October"
case 11:
return "November"
case 12:
return "December"
default:
return "Invalid month (range: 1-12)"
}
}
// Второй вариант функции через классическое условие по индеку массива
func getMonthName2(month int) string {
months := []string{"", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
if month >= 1 && month <= 12 {
return months[month]
}
return "Invalid month"
}
func main() {
// Классический цикл из 13-ти итераций
for i := 1; i <= 13; i++ {
fmt.Printf("Month %d: %s\n", i, getMonthName(i))
}
// Увеличение индекса итерации в теле цикла
j := 1
for j <= 13 {
fmt.Printf("Month %d: %s\n", j, getMonthName(j))
j++
}
// Бесконечный цикл
months := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
k := 0
for {
// Пропускаем итерацию, если 6-й месяц (5-й индекс)
if k == 5 {
k++ // переход к следующей итерации
continue
}
// Выходим из цикла, если индекс больше или равен длине массива
if k >= len(months) {
break
}
fmt.Printf("Month %d: %s\n", months[k], getMonthName(months[k]))
k++
}
// Конструкция range используется для перебора всех элементов в коллекциях (срезах, картах и каналах)
// for index, element := range array {}
// range возвращает копию элемента, а не сам элемент массива, чтобы изменить содержимое массива, необходимо обращаться к элементам по индексу
for _, month := range months {
fmt.Printf("Month %d: %s\n", month, getMonthName(month))
}
// Индекс может использоваться для карты (map) как ключ
m := map[string]int{"a": 1, "b": 2, "c": 3}
for index, value := range m {
fmt.Println("Key:", index, "Value:", value)
}
// Перебор строки по символам
s := "string"
for index, char := range s {
fmt.Println("Index:", index, "Char:", string(char))
}
// Перебор канала
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for val := range ch {
fmt.Println(val)
}
}Типы последовательностей представляют собой структуры данных, хранящие упорядоченные наборы значений.
Массив (статические срезы) - фиксированная по размеру последовательность элементов (arr[10]).
var a [10]int // объявить массив int длиной 10 (длина массива является частью типа)
a[3] = 42 // присвоить значение элементу, по его порядковому номеру
i := a[3] // прочитать элементы
// Возможные варианты объявление с инициализацией значений
var a = [2]int{1, 2}
// Массив из двух элементов: [1 2]
a := [2]int{1, 2}
// Многоточие используется компилятором для вычисления длины массива
a := [...]int{1, 2}Срез (динамические массивы) - это последовательность элементов одного типа с динамической структурой, которая может быть получена из массивов или других срезов с помощью операции среза (arr[start:end]). Срезы могут иметь явное указание длины и емкости, которые можно задать с помощью встроенный функции make, например, чтобы инициализировать элементы среза нулевыми значениями или заранее выделить нужное количество памяти (количество элементов в срезе не ограничивается, но потребует выделения новой памяти).
package main
import "fmt"
func main() {
var a []int // объявить срез
var b = []int{1, 2, 3, 4} // объявить и инициализировать срез
c := []int{7, 8, 9, 10} // [7 8 9 10]
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
chars := []string{"a", "b", "c"} // срез из букв [a b c]
fmt.Println(chars)
chars = []string{0: "a", 2: "b", 1: "c"} // задать порядок индексов при инициализии среза [a c b]
fmt.Println(chars)
fmt.Println(b[1:4]) // срез c индекса 1 по 3 (до 4)
fmt.Println(b[:3]) // срез c индекса 0 по 2
fmt.Println(b[1:]) // срез с индекса 1 по 3 (длинна среза/массива - len(a))
b = append(b, 5, 6) // добавление элементов к срезу a с помощью функции append
fmt.Println(b) // [1 2 3 4 5 6]
c = append(b, c...) // объединение срезов b и c
fmt.Println(c) // [1 2 3 4 5 6 7 8 9 10]
a = append(c[:1], c[9:10]...) // удаление элементов из среза
fmt.Println(a) // [1 10]
e := make([]byte, 5, 10) // первый аргумент длина (будет содержить 5 элементов с значением 0, по умолчанию), второй емкость
fmt.Println(len(e)) // 5
fmt.Println(cap(e)) // 10
e = make([]byte, 5) // Если не указывать емкость, то она будет равна длинне среза
fmt.Println(cap(e)) // 5
x := []int{1, 2, 3} // Исходный срез (откуда копировать)
y := make([]int, 3) // Новый срез с заданной длинной
n := copy(y, x) // Возвращяет число скопированных объектов
fmt.Printf("a = %v\n", x) // a = [1 2 3]
fmt.Printf("b = %v\n", y) // b = [1 2 3]
fmt.Printf("Скопировано %d элемента\n", n) // Скопировано 3 элемента
}Указатель — ссылается на первый элемент массива, доступный через срез (может не совпадать с началом самого массива).
Длина (length) — количество элементов в срезе.
Емкость (capacity) — общее количество элементов от начала среза до конца базового массива, на котором основан срез.
Если изменить значение в базовом массиве, то значение в дочернем срезе также изменится (или наоборот), т.к. элементы слайса и массива находятся в одном участке памяти. При создание среза на основе массива достаточной длины, возможно избежать операций выделения памяти при создании нового массива и копирования элементов из одного массива в другой.
package main
import (
"fmt"
)
func check(baseArray [10]int, baseSlice []int) {
fmt.Printf("Базовый массив: %v\n", baseArray)
fmt.Printf(
"Элементы среза: %v\nДлинна среза: %d \nEмкость среза: %d\n",
baseSlice,
len(baseSlice),
cap(baseSlice),
)
}
func main() {
baseArray := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Создаем срез из массива
baseSlice := baseArray[5:8]
check(baseArray, baseSlice)
// Элементы среза: [5 6 7]
// Длинна среза: 3 (6, 7 и 8 индексы базового массива)
// Eмкость среза: 5 (с 6-го индекса по 10-й)
// Фиксируем адрес элемента массива, на который ссылается срез
pointer := fmt.Sprintf("%p", baseSlice)
// Добавляем новы элемент в срез (присваиваем значение 10 для следующего порядкового индекса 4 в срезе)
baseSlice = append(baseSlice, 10)
check(baseArray, baseSlice)
// Базовый массив: [0 1 2 3 4 5 6 7 10 9] (изменился 9-й индекс в базовом массиве)
// Элементы среза: [5 6 7 10]
// Длинна среза: 4
// Eмкость среза: 5
// Проверяем, что адрес среза не измелися
fmt.Println(pointer == fmt.Sprintf("%p", baseSlice)) // true
// Добавляем элементы свыше емкости среза
baseSlice = append(baseSlice, 11, 12)
check(baseArray, baseSlice)
// Базовый массив не изменился, вместо этого создался новы срез основанный на массиве большего объема
// Элементы среза: [5 6 7 10 11 12]
// Длинна среза: 6
// Eмкость среза: 10
fmt.Println(pointer == fmt.Sprintf("%p", baseSlice)) // false
}Диапазон используется для перебора индексов и элементов массива в цикле.
// Цикл по массиву/срезу
for i, e := range a {
// "i" — индекс, "e" — элемент
}
// Если нужен только элемент "e"
for _, e := range a {
// Индекс опускается
}
// Если нужен только индекс
for i := range a {
}package main
import "fmt"
type Custom struct {
s string
i int
}
func main() {
m := make(map[string]int) // объявить карту (словарь) где ключ имеет тип данных string а значение int
m["key"] = 42 // инициализировать карту одним "ключ-значение"
fmt.Println(m["key"]) // вывести содержимое значения (value) по его уникальному названию клчюча
delete(m, "key") // удалить элемент из карты
elem, ok := m["key"] // проверка, если ключ присутствует, то получить его значение
fmt.Println(ok, elem) // false 0
if ok {
fmt.Println("Value:", m["key"])
} else {
fmt.Println("Key not found")
}
// Создаем карту с значениями массива данных
var m2 = map[string][]int{
"Microsoft": {100, 200, 300},
"Google": {400, 500},
}
// Перебрать содержимое карты в цикле
for key, value := range m2 {
fmt.Print(key, ": ")
// Перебрать содержиме массива (среза)
for _, v := range value {
fmt.Print(v, " ")
}
fmt.Println()
}
// Используем предопределенную структуру для значений
var m3 = map[int]Custom{
1: {"line: ", 1},
2: {"line: ", 2},
}
for key, value := range m3 {
fmt.Print("Key: ", key, "; ")
fmt.Println("Value:", value)
}
// Используем динамическую структуру данных для значений
obj := make(map[string]interface{})
// Заполняем значения разными типами данных
obj["int"] = 1
obj["string"] = "line"
obj["float"] = 1.23
obj["bool"] = true
obj["slice"] = []int{1, 2, 3}
obj["map"] = map[string]string{"key": "value"}
// Выводим содержимое карты
for k, v := range obj {
fmt.Printf("%s: %v\n", k, v)
}
}Вместо классов (class) в Go используются структуры (struct), которые являются новым типом данных комбинированных значений, а также могут содержать методы. Поля структуры всегда инициализируются нулевыми значениями при ее объявлении.
package main
import (
"fmt"
)
// Определение структуры Vertex
type Vertex struct {
X, Y float64
}
// Создаем метод для переданной структуры, который может оперировать ее элементами
func (v Vertex) sum() float64 {
v.X = v.X + v.Y
return v.X
}
// Мутирующий метод, изменяющий поля переданной структуры
// Метод получает указатель на структуру (тип *Vertex), а не копию структуры (тип Vertex)
func (v *Vertex) add(n float64) {
v.X += n
v.Y += n
}
func main() {
// Создание и инициализация структуры Vertex без указания ключей
vertex := Vertex{1, 2}
// Создание среза структур Vertex с несколькими элементами
vSlice := []Vertex{{1, 2}, {5, 2}, {5, 5}}
// Инициализация структуры с указанием ключей полей
vertex2 := Vertex{X: 1, Y: 2}
// Доступ и изменение значения поля структуры
vertex2.X = 4
fmt.Println(vSlice)
// [{1 2} {5 2} {5 5}]
fmt.Println(vSlice[0])
// {1 2}
fmt.Println(vSlice[0].X)
// 1
fmt.Println(vSlice[0].Y)
// 2
fmt.Println(vertex2)
// {4 2}
fmt.Println(vertex)
// {1 2}
fmt.Println(vertex.sum())
// 3
fmt.Println(vertex.X)
// 1
vertex.add(3)
fmt.Println(vertex)
// {4 5}
fmt.Println(vertex.sum())
// 9
}Пример использования структуры и перебор элементов в цикле.
package main
import "fmt"
type test struct {
s string
i []int
m map[string]int
}
func main() {
var arr []test
arr = append(arr, test{"str1", []int{1, 2, 3}, map[string]int{"year": 2025, "mount": 6, "day": 1}})
arr = append(arr, test{"str2", []int{4, 5, 6}, map[string]int{"year": 2035}})
for _, e := range arr {
fmt.Println(e.s)
for _, v := range e.i {
fmt.Println(v)
}
for key, val := range e.m {
fmt.Printf("%s: %d\n", key, val)
}
}
}В отличии от map[string]interface{}, анонимные структуры имеют строгую типизацию, что уменьшает ошибки и повышает производительность, но его нельзя использовать в разных местах без дублирования объявления.
package main
import "fmt"
func main() {
point := struct {
X, Y int
S string
}{1, 2, "test"}
fmt.Println(point)
}Аргументы в функциях и методах всегда копируются, указатели позволяют работать напрямую с содержимым переданных переменных и структурами данных, без копирования их содержимого (изменяя оригинальную переменную).
Оператор & используется для взятия адреса из памяти, а не значения самой переменной.
Функция new() выделяет память для указанного типа и возвращает переменной указатель на него, в отличие от оператора &, который возвращает указатель на существующую переменную.
package main
import (
"fmt"
)
func test(param *int) {
*param = 1
}
func main() {
a := 100 // объявляем и инициализируем переменную
var b *int = &a // используем "b" как указатель (или просто b := &a) на значение переменной "a"
fmt.Println(b) // 0xc000104040
*b++ // изменяем значение переменной "a", ссылаясь по указателю "*"
fmt.Println(a) // 101
c := &b // создать указатель на указатель
**c++ // изменить значение
fmt.Println(a) // 102
d := new(int) // выделяем память для переменной типа int
test(d) // передаем указатель в функцию
fmt.Println(*d) // 1
}p := Vertex{1, 2} // "p" - это структура Vertex
q := &p // "q" указывает на структуру Vertex
r := &Vertex{1, 2} // "r" также указывает на структуру Vertex
// Объявление переменной с указателем на структуру *Vertex
var s *Vertex = new(Vertex) // функция "new" создает указатель на новый экземпляр структуры с пустыми значениями &{0 0}Интерфейс - это набор методов (требований), которые должен иметь тип, чтобы соответствовать этому интерфейсу.
// Создаем пустой интерфейс (принимает произвольное количество значений любого типа)
s := []interface{}{"a", 2, "c", 4, "e"}
s = []any{"a", 2, "c", 4, "e"} // []any{} это alias для []interface{}{}
fmt.Println(s) // [a 2 c 4 e]
fmt.Println(s...) // распаковываем: a 2 c 4 e
// Объявление интерфейса с одинм методом Awesomize(), который возвращает строку
type Awesomizer interface {
Awesomize() string
}
// Обычная структура, которая может реализовывать методы
type Foo struct {}
// Добавление (реализация) метода Awesomize() в структуре Foo
// Тип автоматически соответствует интерфейсу, если он реализует все его методы
func (foo Foo) Awesomize() string {
return "Awesome!"
}В Go нет подклассов, вместо этого используется встраивание интерфейса и структуры, которое добавляет методы встроенной структуры к внешней.
// В структуру Server встраиваются все методы, которые есть у метода Logger из структуры log
type Server struct {
Host string
Port int
*log.Logger
}
// Структура Server инициализируется с помощью указателя на log.Logger
server := &Server{"localhost", 80, log.New(...)}
// Когда вызывается server.Log(...), Go автоматически перенаправляет вызов к server.Logger.Log(...).
server.Log(...)
// Поле встроенного типа доступно через его имя, по этому переменной можно присвоить ссылку на server.Logger
var logger *log.Logger = server.LoggerОбработка исключений отсутствует. Вместо этого функции, которые могут выдать ошибку, просто объявляют дополнительное возвращаемое значение типа error (чаще всего вторым возвращаемым параметром).
Встроенный тип интерфейса error — это общепринятый интерфейс для представления состояния ошибки, при этом нулевое значение не представляет ошибки.
type error interface {
Error() string
}Пример:
package main
import (
"errors"
"fmt"
"math"
)
// Определение функции sqrt должно быть вне main
func sqrt(x float64) (float64, error) {
if x < 0 {
// Создаем объект типа error с текстовым описанием ошибки
return 0, errors.New("ошибка: отрицательное значение")
}
return math.Sqrt(x), nil
}
func main() {
val, err := sqrt(-1)
if err != nil {
// Обработка ошибки
fmt.Println(err) // отрицательное значение
return
}
// Если все хорошо (переданное значение не отрицательное), вывести содержимое "val"
fmt.Println(val)
}Горутины — это легковесные потоки (управляемые Go, а не потоками ОС).
go f(a, b) запускает новую горутину, которая запускает f (при условии, что f — это функция).
// Просто функция (которая позже может быть запущена в горутине)
func doStuff(s string) {
fmt.Println(s)
}
func main() {
// Запуск существующий функции в горутине по ее имени
go doStuff("foobar")
// Использование анонимной внутренней функции в горутине
go func (x int) {
fmt.Println(x)
} (42) // Параметр анонимной функции
}Пакет sync используется для ожидания завершения всех запущенных горутин.
package main
import (
"fmt"
"sync"
)
func doStuff(s string, wg *sync.WaitGroup) {
fmt.Println(s)
defer wg.Done()
}
func main() {
// Объект для отслеживания завершение групп горутин
var wg sync.WaitGroup
// Задаем счетчик для запуска 2-х горутин
wg.Add(2)
// Запуск функции в горутине
go doStuff("foobar", &wg)
// Запуск анонимной функции в горутине
go func(x int, wg *sync.WaitGroup) {
fmt.Println(x)
// Уменьшить счетчик WaitGroup, когда горутина завершится
defer wg.Done()
}(42, &wg)
// Ожидание завершения всех горутин
wg.Wait()
}Таймеры из пакета time используются для задержки (паузы) на указанное время:
package main
import (
"fmt"
"time"
)
func goRun() {
// Симуляция работы
time.Sleep(2 * time.Second)
fmt.Println("Выполнение горутины завершено")
}
func main() {
// Запуск горутины
go goRun()
// Основная функция продолжает работать параллельно
fmt.Println("Запуск выполнения основной функции и ожидание завершения горутины")
// Ждем завершения выполнения горутины
time.Sleep(3 * time.Second)
}Небуферизованный канал блокирует операцию записи, пока не будет выполнено чтение, и наоборот.
// Создаем небуферизованный канал типа "int"
ch := make(chan int)
// Отправляем значение 42 в канал "ch"
// Операция блокирует текущую горутину, пока другая горутина не прочитает его значение
ch <- 42
// Получаем значение из канала "ch"
// Это также блокирует выполнение, пока не будет доступно значение для чтения в канале
v := <-chБуферизованный канал позволяет отправлять и получать данные без блокировки, пока размер буфера не будет превышен, как только буфер заполняется, запись блокируется, пока другие горутины не начнут извлекать значения из канала.
Закрытие канала — это сигнал получателю, что больше значений не будет отправляться в канал, при этом отправленные в него данные не удаляются. Это необходимо для того, чтобы получатели знали, что можно завершить чтение. Закрытие канала происходило только в той горутине, которая отправляет данные.
package main
import "fmt"
func main() {
// Создаем буферизованный канал с размером буфера 100
ch := make(chan int, 100)
// Отправляем некоторое количество значений в канал
for i := 0; i < 10; i++ {
ch <- i
}
// Закрываем канал, чтобы цикл мог завершиться
close(ch)
// Читать из канала, пока он не будет закрыт
for i := range ch {
fmt.Println(i)
}
// Прочитать данные из канала и проверить, закрыт ли он
v, ok := <-ch
if !ok {
fmt.Println("Канал закрыт, данные не доступны")
} else {
fmt.Println("Прочитано из канала:", v)
}
}Вывод: 0 1 2 3 4 5 6 7 8 9 Канал закрыт, данные не доступны
Оператор select работает как многоканальный оператор switch. Выбор блоков в операциях с несколькими каналами, если один из них разблокируется, выполняется соответствующие условие. Он блокируется до тех пор, пока одно из выражений case не будет готов к выполнению, при этом остальные игнорируются.
package main
import (
"fmt"
"time"
)
func doStuff(channelOut, channelIn chan int) {
select {
case channelOut <- 42:
fmt.Println("Отправить значение 42 в channelOut")
case x := <-channelIn:
fmt.Println("Прочитать из channelIn:", x)
case <-time.After(time.Second * 1):
fmt.Println("Задержка в одну секунду")
}
}
func main() {
// Создание двух каналов (один для записи, другой для чтения)
channelOut := make(chan int)
channelIn := make(chan int)
// Запуск горутины для записи в канал "channelOut"
go func() {
time.Sleep(500 * time.Millisecond) // Пауза перед отправкой
channelOut <- 42 // Отправить значение 42
fmt.Println("Значение 42 отправлено в channelOut")
}()
// Запуск горутины для чтения из канала "channelIn"
go func() {
time.Sleep(200 * time.Millisecond) // Пауза перед отправкой
channelIn <- 99 // Отправить значение 99
fmt.Println("Значение 99 отправлено в channelIn")
}()
// Запуск функции doStuff с двумя каналами
doStuff(channelOut, channelIn)
}Отправка в пустой канал блокируется навсегда и вызывает фатальную ошибку:
var c chan string
c <- "Hello, World!"Чтение из нулевого канала блокируется навсегда:
var c chan string
fmt.Println(<-c)Отправка в закрытый канал вызывает панику:
var c = make(chan string, 1)
c <- "Hello, World!"
close(c)
c <- "Hello, Panic!"Прием из закрытого канала немедленно возвращает нулевое значение:
var c = make(chan int, 2)
c <- 1
c <- 2
close(c)
for i := 0; i < 3; i++ {
fmt.Printf("%d ", <-c)
}
// 1 2 0package main
import (
"fmt"
"time"
"sync"
)
func goRun(ch chan string) {
time.Sleep(2 * time.Second)
// Возвращяем сообщене о выполнение в канал
ch <- "Первая горутина завершена за 2 секунды"
}
func goRunThree(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "Вторая горутина завершена за 3 секунды"
}
func printMessage(msg string, wg *sync.WaitGroup) {
// Уменьшает счетчик в WaitGroup, когда горутина завершена
defer wg.Done()
fmt.Println(msg)
}
func main() {
// Создаем канал
ch := make(chan string)
// Запускаем горутину
go goRun(ch)
fmt.Println("Ожидаем завершения горутины в канале")
// Блокируем main, пока не получим сообщение от горутины
result := <-ch
// После получения вывода, программа продолжает выполнение
fmt.Println(result)
// Создаем два канала и запускаем две горутины
ch1 := make(chan string)
ch2 := make(chan string)
go goRun(ch1)
go goRunThree(ch2)
fmt.Println("Ожидаем завершения первой выполненной горутины")
// Используем select для ожидания данных с двух каналов и выбора первого завершенного канала
select {
case msg1 := <-ch1:
fmt.Println("Ответ:", msg1)
case msg2 := <-ch2:
fmt.Println("Ответ:", msg2)
}
// Создаем группу ожидания для синхронизации выполнения нескольких горутин
var wg sync.WaitGroup
fmt.Println("Ожидаем выполнения всех запущенных горутин")
// Указать количество горутин, за которыми нужно следить
wg.Add(2)
go printMessage("Результат первой горутины", &wg)
go printMessage("Результат второй горутины", &wg)
// Ожидаем завершения всех горутин
wg.Wait()
fmt.Println("Все горутины завершили свою работу")
}| Формат | Описание |
|---|---|
%T |
вывод типа данных переменной |
%p |
вывод значения указателя (адрес в памяти, в шестнадцатеричном виде) |
%v |
универсальный спецификатор, для типа boolean (аналогичен %t), целочисленных типов (%d), чисел с плавающей точкой - %g, строк - %s |
%#v |
вывод значения (структуру, срез, карту или другой тип) в виде Go-литерала (как его можно было бы записать в исходном коде Go) для отладки |
%t |
вывод значений типа boolean (true или false) |
%s |
вывод строки (string) |
%f |
вывод чисел с плавающей точкой (float32 или float64) |
%5f |
ширина значения (если значение меньше ширины, то остаток заполняется пробелами) |
%.2f |
точность остатока (выводит 2 цифры в дробной части после точки) |
%d |
вывод целых чисел в десятичной системе |
%o |
вывод целых чисел в восьмеричной системе |
%b |
вывод целых чисел в двоичной системе |
%c |
вывод символов, представленных числовым кодом (тип данных rune или byte) в формате их числовых/буквенных значений |
%q |
вывод символов в одинарных кавычках (тип данных rune или byte в формате кодового значения Unicode) |
%x |
вывод целых чисел в шестнадцатеричной системе, буквенные символы числа имеют нижний регистр a-f |
%X |
вывод целых чисел в шестнадцатеричной системе, буквенные символы числа имеют верхний регистр A-F |
%U |
вывод символов в формате кодов Unicode, например, U+1234 |
%e |
вывод чисел с плавающей точкой в экспоненциальном представлении, например, -1.234456e+78 |
%E |
тоже самое что %e но в верхнем регистре, например, -1.234456E+78 |
package main
import (
"fmt"
)
func main() {
var a byte = 'A'
var b rune = '1'
var c rune = 49
var d string = "123"
var e string = "123"
var f int = 123
var g bool = true
var h float32 = 1.23
fmt.Printf(
"%q %c %q %q \n %s %d %t %.1f \n",
a, b, c, d, e, f, g, h,
)
var z float64 = 100.123456789
// Получаем результат форматирования с помощью метода Sprintf()
result := fmt.Sprintf("%.2f", z)
fmt.Printf("%q", result)
}
// 'A' 1 '1' "123"
// 123 123 true 1.2
// "100.12"package main
import "fmt"
func main() {
// Строка состоит из 10 символов, но её длина в байтах будет 19,
// так как кириллические символы занимают 2 байта (UTF-8) в отличии от латинских символов, а пробел - 1 байт.
var s string = "Это строка"
fmt.Printf("Длина строки: %d байт\n", len(s)) // Длина строки: 19 байт
// Получим подстроку строки по индексу и выводим его значение в исходном (%v) или строковом виде (%s)
fmt.Printf("Напечатаем только второе слово в кавычках: \"%s\"\n", s[7:]) // Напечатаем только второе слово в кавычках: "строка"
// При изменение строки возникнет ошибка компиляции, так как строки неизменяемы
// s[3] = 12
// Изменим строку, создав новую строку из частей
word := "новая "
newS := fmt.Sprintf("%v%v%v", s[:7], word, s[7:])
fmt.Printf("%v\n", newS) // Это новая строка
// Прогнать строку в цикле
for _, b := range s {
// Пропускаем пробел по символу кодировки Unicode
if b == 32 {
continue
}
// Выводим тип данных "rune" (содержащий символ Unicode) в человекочитаемом виде с помощью спецификатора %c
fmt.Printf("%c ", b)
}
// Э т о с т р о к а
}Функции для работы со строками из пакета strings:
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(
// Возвращает строку c нижним регистром
strings.ToLower("TEST"),
// test
// Возвращает строку c верхним регистром
strings.ToUpper("test"),
// TEST
// Проверить, содержится ли подстрока в строке
strings.Contains("test", "es"),
// true
// Вывести количество подстрок в строке
strings.Count("test", "t"),
// 2
// Проверить, что строка начинается с префикса
strings.HasPrefix("test", "te"),
// true
// Проверить, что строка заканчивается суффиксом
strings.HasSuffix("test", "st"),
// true
// Вывести первый найденный порядковый индекс подстроки в строке
strings.Index("test", "t"),
// 0
// При отсутствии вхождения
strings.Index("test", "r"),
// -1
// Повторяет строку n раз подряд
strings.Repeat("a", 5),
// aaaaa
// Разбивает строку на массив из строк (тип данных []string) по разделителю
strings.Split("hello-world", "-"),
// [hello world]
// Объединяет массив из строк с использованием разделителя
strings.Join([]string{"hello", "world"}, "-"),
// hello-world
// Замена, где последний аргумент позволяет указать количество замен ("-1" - заменить все)
strings.Replace("true true true", "tru", "fals", 2),
// false false true
)
}Основные элементы синтаксиса регулярных выражений:
| Символ | Описание |
|---|---|
. |
любой символ, кроме символа новой строки |
* |
0 или более повторений |
+ |
1 или более повторений |
{n} |
точно n повторений (например, a{3}, соответствует: "aaa") |
{n,} |
минимум n повторений (например, a{2,}, соответствует: "aa", "aaa" и т.д.) |
{n,m} |
от n до m повторений (например, a{2,4}, соответствует: "aa", "aaa", "aaaa") |
? |
0 или 1 повторений |
^ |
начало строки |
$ |
конец строки |
[] |
группа символов (например, [a-z]) |
\s |
любой пробельный символ (пробел, табуляция, новая строка и другие пробельные символы) |
\d |
цифра (эквивалентно [0-9]) |
\D |
любой символ, не являющийся цифрой (эквивалентно [^0-9]) |
\w |
буквенно-цифровой символ (буквы, цифры и подчеркивание, эквивалентно [a-zA-Z0-9_]) |
\W |
не буквенно-цифровой символ (эквивалентно [^a-zA-Z0-9_]) |
\b |
граница слова (например, \bword\b соответствует "word", и не подхоит "wordy") |
(?i) |
делает выражение нечувствительным к регистру |
\ |
экранирование специальных символов |
() |
группа захвата |
| |
логическое ИЛИ (например, `a |
Основные функции пакета regexp:
regexp.MatchString— проверяет, соответствует ли строка регулярному выражению.
package main
import (
"fmt"
"regexp"
)
func main() {
pattern := `^[a-z]+$`
str := "string"
matched, err := regexp.MatchString(pattern, str)
if err != nil {
fmt.Println("Ошибка в регулярном выражении:", err)
return
}
fmt.Printf("Строка '%s' соответствует регулярному выражению '%s' (результат: %v)", str, pattern, matched)
}regexp.Compile— компилирует регулярное выражение и возвращает объект типа*regexp.Regexp, если выражение корректное, или возвращается ошибка.
package main
import (
"fmt"
"regexp"
)
func main() {
// Компилируем регулярное выражение
r, err := regexp.Compile(`\d+`)
if err != nil {
fmt.Println("Ошибка компиляции регулярного выражения:", err)
return
}
// Применяем регулярное выражение к строке
fmt.Println(r.FindString("123 abc 456")) // 123
}regexp.FindAllString— находит все подстроки в строке, которые соответствуют регулярному выражению, и возвращает их в виде среза строк.
package main
import (
"fmt"
"regexp"
)
func main() {
r, err := regexp.Compile(`\d+`)
if err != nil {
fmt.Println("Ошибка компиляции регулярного выражения:", err)
return
}
matches := r.FindAllString("123abc456", -1)
fmt.Println(matches) // [123 456]
}regexp.ReplaceAllString— заменяет все соответствующие части строки.
package main
import (
"fmt"
"regexp"
)
func main() {
pattern := `\d+`
str := "Диапазон от 1 до 10"
// Заменяем все цифры на "X"
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println("Ошибка компиляции регулярного выражения:", err)
return
}
result := r.ReplaceAllString(str, "X")
fmt.Println(result)
}- Группы захвата
package main
import (
"fmt"
"regexp"
)
func main() {
// Регулярное выражение с группой захвата для даты в формате "dd.mm.yyyy"
pattern := `(\d{2}).(\d{2}).(\d{4})`
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println("Ошибка компиляции регулярного выражения:", err)
return
}
// Поиск и извлечение данных
result := r.FindStringSubmatch("01.12.2024")
if len(result) > 0 {
fmt.Println("День:", result[1])
fmt.Println("Месяц:", result[2])
fmt.Println("Год:", result[3])
}
}- Извлечение логина и домена из почтовых адресов
package main
import (
"fmt"
"regexp"
)
func main() {
pattern := `([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})`
str := "contact@example.com, support@example.net"
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println("Ошибка компиляции регулярного выражения:", err)
return
}
matches := r.FindAllStringSubmatch(str, -1)
for _, match := range matches {
fmt.Printf("Логин: %s, Домен: %s\n", match[1], match[2])
}
}package main
import (
"fmt"
"math"
)
func customCeil(numerator int, denominator int) int {
result := numerator / denominator
if numerator%denominator != 0 {
result++
}
return result
}
func main() {
fmt.Println("Возвращает наименьшее значение из двух чисел 9 и 10:", math.Min(9, 10)) // 9
fmt.Println("Возвращает наибольшее значение из двух чисел 9 и 10:", math.Max(9, 10)) // 10
fmt.Println("Округляет число в меньшую сторону 10 / 3:", math.Floor(10/3)) // 3
fmt.Println("Округляет число в большую сторону 10 / 3:", math.Ceil(10.0/3))
fmt.Println("Округляет число в большую сторону 10 / 3:", customCeil(10, 3)) // 4
fmt.Println("Отбрасывает дробную часть числа (не округляет) 4,9:", math.Trunc(4.9)) // 4
fmt.Println("Округляет число до ближайшего целого в большую сторону от 4,5:", math.Round(4.5)) // 5
fmt.Println("Округляет число до ближайшего целого в меньшую сторону от 4,5:", math.Round(4.45)) // 4
fmt.Println("Возвращает абсолютное значение числа -7:", math.Abs(-7)) // 7
fmt.Println("Возводит число 2 в степень 3:", math.Pow(2, 3)) // 8
fmt.Println("Вычисляет квадратный корень числа 16:", math.Sqrt(16)) // 4
}- Декларация пакета (объявление через
import) производится в начале каждого исходного файла. - Исполняемые файлы находятся в пакете
main. - Имя пакета соответствует последнему имени в пути импорта (например,
math/rand- пакетrand). - Идентификатор функции в верхнем регистре является экспортируемый (доступны из других пакетов).
- Идентификатор функции в нижнем регистре является частный (недоступны из других пакетов).
Программы Go могут встраивать статические файлы с помощью пакета embed и директиву go:embed path/filename:
package main
import (
"embed"
"fmt"
"io"
"log"
"net/http"
)
//go:embed static/*
var content embed.FS
func main() {
http.Handle("/", http.FileServer(http.FS(content)))
go func() {
log.Fatal(http.ListenAndServe(":8080", nil))
}()
// Чтение содержимого файлов из файловой системы
entries, err := content.ReadDir("static")
if err != nil {
log.Fatal(err)
}
for _, e := range entries {
resp, err := http.Get("http://localhost:8080/static/" + e.Name())
if err != nil {
log.Fatal(err)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if err := resp.Body.Close(); err != nil {
log.Fatal(err)
}
fmt.Printf("%q: %s", e.Name(), body)
}
// Блокировка программы, чтобы сервер продолжал работать для доступа к статическим файлам через Web-интерфейс
select {}
}
// Имитация реальных файлов с их содержимым для запуска в Playground
-- static/a.txt --
hello a
-- static/b.txt --
hello bРеализация простого API сервера на базе встроенной библиотеки net/http:
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// Обработчик API
func apiHandler(w http.ResponseWriter, r *http.Request) {
// Устанавливаем заголовок для ответа (Content-Type: application/json)
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
// Получение параметра "name" из URL
name := r.URL.Query().Get("name")
if name == "" {
name = "Guest" // Значение по умолчанию, если параметр отсутствует
}
// Формируем JSON-ответ
json.NewEncoder(w).Encode(map[string]string{
"message": fmt.Sprintf("Hi %s", name),
})
case "POST":
// Парсим JSON из тела запроса
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
// Формируем JSON-ответ
json.NewEncoder(w).Encode(map[string]interface{}{
"received": data,
"status": "OK",
})
default:
// Обработка неподдерживаемых методов
http.Error(w, "Метод не поддерживается", http.StatusMethodNotAllowed)
}
}
func main() {
// Регистрируем обработчик для пути /api
http.HandleFunc("/api", apiHandler)
// Запуск сервера
fmt.Println("Сервер запущен на http://localhost:8080")
http.ListenAndServe(":8080", nil)
}Делаем запрос к API через curl:
curl -s "http://localhost:8080/api" | jq .message # "Hi Guest"
curl -s "http://localhost:8080/api?name=Alex" | jq .message # "Hi Alex"
curl -s -X POST -d '{"key":"value"}' -H "Content-Type: application/json" http://localhost:8080/api | jq .received.key # "value"
curl -s -X POST "http://localhost:8080/api" # invalid JSONДелаем запрос к API в Go:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
// URL для отправки POST-запроса
url := "http://localhost:8080/api"
// Тело запроса в формате JSON
requestBody := map[string]string{"key": "value"}
jsonData, err := json.Marshal(requestBody)
if err != nil {
fmt.Println("Ошибка при создании тела запроса в формате JSON:", err)
return
}
// Создаем запрос
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Ошибка при отправке запроса:", err)
return
}
defer resp.Body.Close()
// Читаем тело ответа
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Ошибка при чтении ответа:", err)
return
}
// Разбираем ответ в формате JSON
var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil {
fmt.Println("Ошибка при парсинге JSON:", err)
return
}
// Выводим значение "key" из ответа
if received, ok := response["received"].(map[string]interface{}); ok {
if value, exists := received["key"]; exists {
fmt.Println(value) // "value"
} else {
fmt.Println("Ключ 'key' не найден в ответе")
}
} else {
fmt.Println("Ответ не содержит ожидаемую структуру received")
}
}HTTP запрос к API для получения последней версии релиза указаного репозитория в GitHub:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// Формируем структуру ответа от API
type GitHubRelease struct {
TagName string `json:"tag_name"`
}
func main() {
// Формируем URL для получения информации
repos := "Lifailon/lazyjournal"
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repos)
// Выполнение GET-запроса
resp, err := http.Get(url)
if err != nil {
log.Fatal("Ошибка при выполнении запроса:", err)
}
defer resp.Body.Close()
// Проверка на успешный ответ
if resp.StatusCode != http.StatusOK {
log.Fatalf("Ошибка HTTP: %s", resp.Status)
}
// Декодирование JSON-ответа в заданную структуру
var release GitHubRelease
err = json.NewDecoder(resp.Body).Decode(&release)
if err != nil {
log.Fatal("Ошибка при декодировании JSON:", err)
}
// Вывод последней версии
fmt.Println("Latest version:", release.TagName)
}go run main.go
Проверка доступности всех хостов в указанной подсети (асинхронный ICMP опрос):
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"sync"
)
func pingHost(ip string, wg *sync.WaitGroup) {
defer wg.Done()
// Запускаем команду ping
cmd := exec.Command("ping", "-n", "1", ip)
output, err := cmd.CombinedOutput()
if err != nil {
return
}
// Обрабатываем вывод команды
if strings.Contains(string(output), "TTL=") {
fmt.Printf("%s - доступен\n", ip)
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Использование: go run main.go <подсеть>")
return
}
// Извлекаем аргумент
subnet := os.Args[1]
// Убираем последний октет
ipBase := subnet[:len(subnet)-1]
var wg sync.WaitGroup
for i := 1; i <= 254; i++ {
ip := fmt.Sprintf("%s%d", ipBase, i)
wg.Add(1)
// Запускаем асинхронный пинг
go pingHost(ip, &wg)
}
// Ждем завершения всех горутин
wg.Wait()
}go run main.go 192.168.3.0
