План
- Синтаксис и структура: ознакомьтесь с базовыми элементами языка Go, такими как переменные, типы данных, операторы, условные выражения и циклы.
- Функции: изучите создание и использование функций в Go, а также передачу параметров по значению и по ссылке.
- Срезы (slices) и массивы: узнайте, как создавать, использовать и манипулировать срезами и массивами в Go.
- Структуры и методы: ознакомьтесь с определением структур и созданием методов, которые работают с ними.
- Указатели: изучите работу с указателями в Go и их применение для изменения значений переменных и передачи данных.
- Интерфейсы: узнайте, как создавать интерфейсы и реализовывать их в Go для достижения полиморфизма.
- Горутины и каналы: изучите концепцию параллельного и конкурентного программирования с помощью горутин и каналов в Go.
- Обработка ошибок: ознакомьтесь с практиками обработки ошибок в Go, используя механизмы возврата ошибок и паники.
- Пакеты и импорты: изучите организацию кода в пакеты и импортирование пакетов в Go.
- Тестирование: узнайте, как создавать модульные тесты для своего кода с помощью пакета
testing
в Go. - Работа с файлами и сетью: ознакомьтесь с базовыми операциями работы с файлами и сетью в Go.
- Развертывание приложений: изучите процесс сборки и развертывания приложений на языке Go, включая управление зависимостями и компиляцию.
Синтаксис и структура
Настраиваем Goland
Указываем пути до папки с исходными кодами приложений
И до папки с компилятором Go
Первое приложение. main функция
Первым делом в корневом файле всегда обязательно должен присутствовать пакет main
и функция main
, которые будут хранить основные пакеты приложения и функцию запуска приложения на Go
В myVar
записана переменная, а уже через библиотеку форматирования fmt
и её метод Print
можно вывести значение в консоли
package main
import "fmt"
func main() {
myVar := "Hello world"
fmt.Print(myVar)
}
- Первой командой устанавливается корневой путь до SDK Go (компилятор)
- Второй - GOPATH - это переменная среды, используемая в Go для указания корневого каталога рабочего пространства, где хранятся исходные коды и бинарные файлы проектов на Go.
- Третья команда вызывает компилятор Go через
build
и ключом-o
задаётся путь выходного файла. Путь будет иметьtmp
директорию. Последний аргумент - это целевой файл, который нужно скомпилировать.
Так же мы можем запустить нужный файл через данную команду:
go run main.go
Так же мы можем сбилдить нужный нам файл в бинарник нашей системы
go build -o main_app main.go
Переменные и типы данных
В Go, как и в других языках, присутствуют самые распространённые типы данных:
bool
: логический тип данных, который может бытьtrue
илиfalse
.- числовые типы данных:
int
(int8
,int16
,int32
,int64
): целое число со знаком.uint
(uint8
,uint16
,uint32
,uint64
): целое число без знака.float32
,float64
: числа с плавающей точкой.complex64
/complex128
byte
=uint8
rune
=int32
string
: строки текста.- массивы и срезы: коллекции элементов определенного типа.
struct
: пользовательские типы данных, состоящие из полей разных типов.- указатели: хранят адрес памяти другой переменной.
interface
: набор методов, объединенных под общим интерфейсом.func
: фрагменты кода, которые можно вызывать и использовать.maps
: коллекции пар ключ-значение.channels
: используются для обмена данными между горутинами.
Мы можем указать переменную через три конструкции:
var имя тип_данных = значение
- сами указываем переменную и тип данныхvar имя = значение
- сами указываем переменную без типа данныхимя := значение
- автоматическое указание типа данных
Golang почти строготипизированный язык, поэтому если мы не укажем тип, но укажем определённую переменную, то поменять строку на чичсло - не получится
Так же у нас есть явный перевод данных и с помощью того же int
можно перевести число одного типа в другое
func main() {
myVar := "Hello world"
fmt.Print(myVar)
var a int = 10
b := 40
var c uint32 = 50
d := a + int(c)
}
Так же можно указать сразу несколько переменных подобным синтаксисом:
func main() {
var (
name = "Oleg"
age = 30
)
fmt.Print(name)
fmt.Print(age)
}
Или подобным:
func main() {
var name, age = "Oleg", 30
fmt.Print(name)
fmt.Print(age)
}
Работа со строками
Чтобы объединять строки, мы можем использовать просто оператор +
func main() {
str1 := "Hello"
str2 := "World"
result := str1 + " " + str2
fmt.Println(result) // Выводит: Hello World
}
Так же мы можем конструировать строки через использование метода Sprintf
, куда первым параметром передаём строку и указатели (%s
- строка, %d
- число), а дальше переменные с нужными типами данных
func main() {
var name, age = "Oleg", 30
c := fmt.Sprintf("Hello! I'm %s. And I'm %d years old", name, age)
fmt.Print(c)
}
Через функцию Join
мы можем объединить массив в строку
func main() {
strs := []string{"Hello", "World"}
result := strings.Join(strs, " ")
fmt.Println(result) // Выводит: Hello World
}
Функция Split
позволит разбить строку по определённому разделителю в массив
func main() {
str := "apple,banana,orange"
arr := strings.Split(str, ",")
fmt.Println(arr) // Выводит: [apple banana orange]
}
strings.Contains(s, substr)
:- Проверяет, содержит ли строка
s
подстрокуsubstr
. - Возвращает булево значение
true
, еслиs
содержитsubstr
, иначеfalse
.
- Проверяет, содержит ли строка
strings.HasPrefix(s, prefix)
:- Проверяет, начинается ли строка
s
с префиксаprefix
. - Возвращает булево значение
true
, еслиs
начинается сprefix
, иначеfalse
.
- Проверяет, начинается ли строка
strings.HasSuffix(s, suffix)
:- Проверяет, заканчивается ли строка
s
суффиксомsuffix
. - Возвращает булево значение
true
, еслиs
заканчивается наsuffix
, иначеfalse
.
- Проверяет, заканчивается ли строка
strings.Replace(s, old, new, n)
:- Заменяет все вхождения подстроки
old
в строкеs
на подстрокуnew
. - Можно указать необязательный аргумент
n
, чтобы ограничить количество замен. - Возвращает новую строку, полученную после замены.
- Заменяет все вхождения подстроки
strings.ToLower(s)
:- Преобразует все символы строки
s
в нижний регистр. - Возвращает новую строку с преобразованными символами.
- Преобразует все символы строки
strings.ToUpper(s)
:- Преобразует все символы строки
s
в верхний регистр. - Возвращает новую строку с преобразованными символами.
- Преобразует все символы строки
strings.TrimSpace(s)
:- Удаляет все начальные и конечные пробелы из строки
s
. - Возвращает новую строку без пробелов в начале и конце.
- Удаляет все начальные и конечные пробелы из строки
Циклы
Цикл for
Основной цикл, который существует в Go представлен следующими вариациями:
// Форма 1: С наибольшим контролем
for init; condition; post {
// Тело цикла
}
// Форма 2: Как классический цикл while
for condition {
// Тело цикла
}
// Форма 3: Бесконечный цикл
for {
// Тело цикла
}
Цикл range
Цикл range
используется для итерации по элементам массива, среза (slice), строки, карты (map) или канала (channel). Он возвращает индекс или ключ текущего элемента и его значение
for index, value := range collection {
// Тело цикла
}
while
Цикл while моделируется обычным оставлением одного кондишенела в цикле перебора
for condition {
// Тело цикла
}
do-while
В Go также нет прямой поддержки цикла do-while
, но можно смоделировать его, используя бесконечный цикл и прерывание с помощью оператора break
for {
// Тело цикла
if !condition {
break
}
}
Ветвления
Представление самого стандартного ветвления:
func main() {
x := 10
if x > 5 {
fmt.Println("x больше 5")
}
}
Представление работы else-if:
func main() {
age := 20
if age < 18 {
fmt.Println("Вы несовершеннолетний")
} else if age >= 18 && age < 60 {
fmt.Println("Вы взрослый")
} else {
fmt.Println("Вы пожилой человек")
}
}
В Go также есть возможность использовать условные операторы с коротким обозначением переменной (:=
). Это позволяет нам объявить и присвоить значение переменной внутри условия if
func main() {
if x := 5; x > 0 {
fmt.Println("x положительное число")
} else if x < 0 {
fmt.Println("x отрицательное число")
} else {
fmt.Println("x равно нулю")
}
}
Функции
Создание функций
Самая стандартная функция начинается с конструкции func
. Далее следует имя функции, её сигнатура, в которой указываются переменные и их тип. После сигнатуры указывается тип возвращаемого значения (если есть).
В теле функции выполняется повторяемый код. Если мы хотим что-то вернуть, то указываем return
func sayHello() {
fmt.Println("Привет!")
}
func add(a, b int) int {
return a + b
}
Так же функция может вернуть сразу несколько параметров
func values() (int, int) {
x := 3
y := 4
return x, y
}
horizontal, vertical := valuews()
Вызов функций
Для вызова функции достаточно написать её имя, после которого следуют круглые скобки с аргументами
sayHello() // Выведет "Привет!"
result := add(3, 5) // Присваивает переменной result значение 8
Передача параметров по значению
По умолчанию в Go параметры функции передаются по значению, то есть создается копия значения аргумента и эта копия используется внутри функции
func increment(x int) {
x = x + 1
fmt.Println("Внутри функции:", x)
}
num := 5
increment(num)
fmt.Println("После вызова функции:", num)
// Внутри функции: 6 - значение изменено
// После вызова функции: 5 - значение не изменено (не мутировано внутри функции)
Передача параметров по ссылке
Если требуется изменить значение переменной внутри функции и эти изменения должны отразиться на оригинальной переменной, то можно передать параметр по ссылке (указателю)
func incrementByReference(x *int) {
*x = *x + 1
fmt.Println("Внутри функции:", *x)
}
num := 5
incrementByReference(&num)
fmt.Println("После вызова функции:", num)
// Внутри функции: 6
// После вызова функции: 6
Отложенное выполнение
Ключевое слово defer
используется для отложенного выполнения функции или метода. Когда функция содержит инструкцию defer
, соответствующий вызов функции не выполняется немедленно, а откладывается до окончания выполнения текущей функции или блока.
Вот некоторые особенности defer
:
- Порядок выполнения: Если в функции используются несколько инструкций
defer
, они будут выполнены в порядке обратном их объявлению. - Захват аргументов: Аргументы функции, передаваемые в инструкцию
defer
, вычисляются немедленно при объявленииdefer
. - Аргументы функции считываются заранее: Если аргументы функции, передаваемые в
defer
, подвержены изменениям до момента выполнения отложенной функции, то отложенная функция все равно получит их исходные значения, а не значения на момент вызова. - Работа с файлами и ресурсами:
defer
часто используется для освобождения ресурсов, таких как закрытие файла или соединения с базой данных, чтобы гарантировать, что эти ресурсы будут корректно освобождены независимо от пути выполнения функции.
func readFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
// Чтение файла и выполнение других операций
return nil
}
В этом примере отложенный вызов file.Close()
гарантирует, что файл будет закрыт независимо от того, как завершится функция readFile()
. Это помогает избежать утечек ресурсов и обеспечивает более безопасное и удобное управление ресурсами.
Срезы, массивы и map
Массивы
Объявление и использование массивов. Массивы хранят в себе только тот тип данных, который мы указали
func main() {
var arr [5]int // инициализация массива
arr[1] = 5 // задание значения
arr2 := [5]int{}
arrWithValues := [5]int{1, 2, 3, 4}
}
Слайсы
Слайс - это динамический массив, который не имеет определённого количества элементов
package main
import "fmt"
func main() {
week := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
weekend := week[6:8]
fmt.Println(len(week)) // len покажет длину массива - 8
fmt.Println(weekend) // [6 7]
fmt.Println(week[6:]) // [6 7 8]
fmt.Println(cap(weekend)) // cap покажет, сколько памяти может занимать слайс
animals := []string{} // это пример создания слайса - не указываем размер массива
animals = append(animals, "lion") // тут мы добавляем значение в слайс
cars := make([]string, 5, 10) // так же мы имеем такую возможность создать значение любого типа данных
}
Слайс имеет определённые особенности при его использовании
package main
import "fmt"
func main() {
a := []int{1}
b := a[0:1] // тут мы связали слайсы, так как они сейчас начали смотреть на один массив
b[0] = 0 // задали нолик в стоковом массиве
fmt.Println(a) // [0] - вышел нолик
a[0] = 1 // поменяли стоковый массив на 1
fmt.Println(b) // [1] - вышла единичка
a = append(a, 2) // данная функция вместе с добавлением значения разделит слайсы
fmt.Println(a, b) // [1 2] [1] - тут уже слайсы стали смотреть на разные массивы
}
Map
Карта - это ассоциативный массив, который представляет собой хеш-таблицу
Ключи и значения имеют одинаковый тип данных
package main
import "fmt"
func main() {
workdays := map[string]int{
"monday": 1,
"tuesday": 2,
"wednesday": 3,
"thursday": 4,
"friday": 5,
}
workdays["saturday"] = 6
workdays["sunday"] = 7
day, ok := workdays["saturday"] // вторым аргументом присваивается значение существования ключа - true
if ok { // если ключ существует
fmt.Println(day)
}
delete(workdays, "sunday") // удаление ключа
fmt.Println(workdays["tuesday"]) // 2
}
Структуры и методы
структуры
Структура - это набор различных типов данных, которые описывают единый объект
Имя поля с заглавной - экспортируется, со строчной - не экспортируется и не может быть отображено в другом пакете вне данного
type Human struct {
Name string
age int
Width int
Height int
}
Далее в любую другую переменную мы можем записать экземпляр структуры Human
package main
import "fmt"
type Human struct {
Name string
age int
Width int
Height int
}
func main() {
person := Human{
age: 20,
Name: "Valery",
Height: 178,
Width: 109,
}
fmt.Println(person)
}
Методы
Базовая структура
type Person struct {
Name string
Age int
}
Метод - это функция, которая привязана к определённой структуре. То есть для вызова метода, нужно создать соответствующую структуру
В приведенном примере мы создаем два метода на структуре Person
. Нужно обратить внимание, что имя структуры указывается перед именем метода в скобках - это ресивер (приёмник) - он обозначает привязанность метода к структуре.
Первый метод называется GetName()
и возвращает значение поля Name
структуры Person
. Второй метод называется SetAge(age int)
и устанавливает поле Age
структуры Person
в указанное значение age
.
Во втором случае мы использовали указатель, так как нам нужно получить доступ (ссылку) к объекту структуры и изменить его значение. В первом случае мы не использовали указатель, так как нам нужно только отобразить имя
func (p Person) GetName() string {
return p.Name
}
func (p *Person) SetAge(age int) {
p.Age = age
}
Чтобы использовать структуры и их методы, мы можем создать экземпляр структуры и вызвать методы на этом экземпляре
person := Person{Name: "Alice", Age: 25}
name := person.GetName() // Вызываем метод GetName()
fmt.Println(name) // Выводит: Alice
person.SetAge(30) // Вызываем метод SetAge()
fmt.Println(person.Age) // Выводит: 30
Конструктор
Иногда, когда нам нужно будет создавать объект по структуре, мы можем сократить запись и реализовать конструктор, который может задавать определённые поля по умолчанию
type User struct {
name, email string
isConfirmed bool
}
func NewUser(name, email string) User {
return User{
email: email,
name: name,
isConfirmed: false,
}
}
func main() {
user := NewUser("Oleg", "oleg@yandex.ru")
fmt.Println(user) // {Oleg oleg@yandex.ru false}
}
Встраивание структуры
Структуры так же можно вкладывать друг друга, разбивая их данные
type Person struct {
name string
Parameters Parameters
}
type Parameters struct {
height int
age uint8
}
Так же есть и второй способ указания встроенной структуры, при котором поля уже будут принадлежать не отдельно структуре Parameters
, а именно Person
type Person struct {
name string
Parameters
}
type Parameters struct {
height int
age uint8
}
Занесение структуры
param := Parameters{height: 178, age: 20}
person := Person{name: "Oleg", Parameters: param}
Указатели
Определение указателя
В Go указатель - это переменная, которая содержит адрес в памяти другой переменной. Через оператор *
мы указываем на ту переменную, которая будет являться указателем. Оператор &
используется для получения адреса переменной.
var x int = 10
var ptr *int = &x
Разыменование указателя
В Go оператор *
используется для разыменования указателя и доступа к значению, на которое он указывает.
fmt.Println(*ptr) // Выводит значение переменной, на которую указывает ptr (10)
Изменение значения переменной через указатель
Поскольку указатель содержит адрес переменной, мы можем использовать его для изменения значения переменной напрямую
*ptr = 20 // Изменяет значение переменной x через указатель ptr
fmt.Println(x) // Выводит измененное значение (20)
Передача указателя в функции
Указатели часто используются для передачи данных по ссылке в функции. При передаче указателя в функцию, изменения, сделанные внутри функции, будут отразиться на исходных переменных
func changeValue(ptr *int) {
*ptr = 30 // Изменяет значение переменной через указатель
}
var y int = 10
changeValue(&y) // Передача указателя на переменную y в функцию
fmt.Println(y) // Выводит измененное значение (30)
Передача указателя в функции
Указатели часто используются для работы со значительными данными, такими как структуры. Создание указателей на структуры позволяет избежать копирования больших объемов данных при их передаче в функции
type Person struct {
Name string
Age int
}
func changeName(p *Person, newName string) {
p.Name = newName
}
var person1 = Person{"John", 25}
changeName(&person1, "Mike")
fmt.Println(person1.Name) // Выводит измененное имя ("Mike")
Интерфейсы
Создание интерфейса
Для создания интерфейса в Go необходимо объявить новый тип с набором методов, которые требуется реализовать. Например, предположим, что мы хотим создать интерфейс “Shape” для различных геометрических фигур:
type Shape interface {
Area() float64
Perimeter() float64
}
Использование интерфейса
Затем можно создать структуры, которые реализуют этот интерфейс. Например, вот реализация интерфейса “Shape” для круга или “Square” для квадрата:
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.radius
}
type Square struct {
sideLength float64
}
func (s Square) Area() float64 {
return s.sideLength * s.sideLength
}
func (s Square) Perimeter() float64 {
return 4 * s.sideLength
}
Обращение к интерфейсу
И теперь мы можем сделать обобщённую функцию, которая будет принимать в себя значения по интерфейсу
func PrintShapeDetails(s Shape) {
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
func main() {
circle := Circle{radius: 5}
square := Square{sideLength: 10}
PrintShapeDetails(circle)
PrintShapeDetails(square)
}
Итог
Таким образом, интерфейс - это структура, используемая для определения набора методов, которые тип должен реализовывать.
Чтобы воспользоваться интерфейсом, мы должны для структуры указать её методы, которые должны соответствовать интерфейсу по имени и типу возвращаемых данных
Интерфейс позволяет нам реализовать полиморфизм, так как мы можем указывать принимаемые аргументы в виде интерфейса.
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
}
type Square struct {
sideLength float64
}
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.radius
}
func (s Square) Area() float64 {
return s.sideLength * s.sideLength
}
func (s Square) Perimeter() float64 {
return 4 * s.sideLength
}
func printShapeDetails(s Shape) {
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
func main() {
circle := Circle{radius: 10}
square := Square{sideLength: 10}
printShapeDetails(circle)
printShapeDetails(square)
}
Горутины и каналы
Горутины
Горутины (goroutines) в Go - это легковесные потоки выполнения, которые работают независимо друг от друга и могут быть запущены параллельно. Они позволяют выполнять асинхронные операции без явного создания отдельных потоков или процессов. Горутины разделяют общее пространство адресов и управляются сборщиком мусора, что делает их эффективными и недорогими в использовании.
Горутина - это функция, которая может быть запущена в отдельном потоке выполнения
Для создания горутины нужно просто передать функцию с ключевым словом go
func main() {
go myFunc() // Создание горутины
// ...
}
func myFunc() {
// Код функции
}
Канал
Каналы (channels) служат для обмена данными между горутинами и обеспечивают синхронизацию и координацию выполнения параллельных процессов. Каналы можно представить как “трубы”, через которые горутины передают информацию. Они обеспечивают безопасный доступ к данным при параллельном выполнении, предотвращая состояние гонки (race condition) и другие проблемы синхронизации.
Каналы используются для обмена данными между горутинами
Каналы могут быть созданы с помощью функции make
ch := make(chan int) // Создание канала типа int
Так же можно создать канал через стандартную запись
var intChannel chan int // если мы хотим принимать любые типы данных, то здесь нужно указать interface
intChannel <- 1
number := <- intChannel
Затем вы можете отправлять и получать значения через канал, используя операторы <-
:
ch <- value // Отправка значения в канал
result := <-ch // Получение значения из канала
Каналы по умолчанию блокируют выполнение горутины при чтении или записи, пока другая горутина не будет готова для обмена. Это предотвращает состояние гонки (race condition) и делает программу безопасной для параллельного выполнения.
Кроме того, Go предлагает различные средства синхронизации, такие как мьютексы (mutexes) и условные переменные (condition variables), чтобы обеспечить правильную синхронизацию доступа к общим ресурсам и согласование работы горутин.
Буферизованный и небуферизованный канал
Небуферизованный канал может в себя принять ровно одно значение до того момента, пока его не прочитает горутина. Если горутина в другом месте кода не прочтёт значение из небуферизованного канала, то поток блокируется до того момента, пока его не начнут читать
ch := make(chan int)
Буферизованный канал в себе имеет уже выделенное нами количество ячеек под данные. Он так же блокируется, когда место под значения кончается. Если значение из него прочитали, то место под новые данные освобождается.
ch := make(chan int, 5)
Использование горутин и каналов
В данном примере мы создаём канал resultChan
, который принимает в себя все просуммированные квадраты. Затем из этого канала мы достаём полученные значения функции.
func main() {
numbers := []int{1, 2, 3, 4, 5}
resultChan := make(chan int)
for _, num := range numbers {
go square(num, resultChan)
}
total := 0
for i := 0; i < len(numbers); i++ {
total += <-resultChan
}
fmt.Println("Сумма квадратов чисел:", total)
}
func square(num int, resultChan chan<- int) {
square := num * num
resultChan <- square
}
Обработка ошибок
Ошибки
Errors: В Go ошибки представляются интерфейсом error
, который определен в стандартной библиотеке. Он имеет единственный метод Error() string
, который возвращает описание ошибки. Обычная практика возвращать ошибку в качестве значения последнего аргумента функции.
Проверка ошибок (Error Checking): Для проверки ошибок используется условное выражение, обычно с помощью if
или switch
. Пример:
result, err := SomeFunction()
if err != nil {
// Обработка ошибки
} else {
// Обработка успешного результата
}
Возврат ошибок (Returning Errors): Функции в Go могут возвращать ошибку вместе с другими значениями. Это позволяет вызывающему коду обрабатывать ошибку непосредственно. Пример:
func SomeFunction() (int, error) {
// ...
if someCondition {
return 0, errors.New("some error")
}
// ...
return result, nil
}
Ошибки как значения (Errors as Values): Важно помнить, что в Go ошибки являются значениями, а не исключениями. Они должны быть явно обработаны кодом.
Паники
Паника (Panic): Паника - это серьезная ошибка, которая приводит к немедленной остановке выполнения программы. Когда возникает паника, выполнение обычно останавливается, и выводится стек вызовов (stack trace). Паника может возникать, например, при доступе к nil-указателю или при делении на ноль.
Восстановление (Recover): Чтобы предотвратить полную остановку программы при возникновении паники, можно использовать механизм восстановления (recover). Recover может быть использован только в отложенных функциях (deferred functions). Он возвращает значение типа interface{}
и используется для перехвата паники и возврата управления программе.
func main() {
defer func() {
if r := recover(); r != nil {
// Обработка паники
}
}()
// ...
panic("something went wrong")
// ...
}
Упрощенное восстановление (Simplified Recovery): В Go 1.13 и более поздних версиях было добавлено упрощенное восстановление, которое позволяет перехватывать и обрабатывать панику в одной инструкции.
func main() {
// ...
if r := recover(); r != nil {
// Обработка паники
}
// ...
}
Пакеты и импорты
Тестирование
Работа с файлами и сетью
Развертывание приложений
Прочее
Веб-сервер на Golang
Самый простой веб-сервер:
package main
import (
"log"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!\n"))
}
func main() {
http.HandleFunc("/", Handler)
log.Println("Start server on PORT:8081")
log.Fatal(http.ListenAndServe(":8081", nil))
}
Сборка и запуск веб-сервера под Windows и Linux
Чтобы собрать приложение под другую ОС, нужно указать в переменных окружения GOOS
и GOARCH