[Перевод] Начало работы с языком Go

Go

оригинальныйМира запущена, U диск. ком. Даже если /2019/07/06/…

«Go — это язык программирования с открытым исходным кодом, который упрощает создание простого, надежного и эффективного программного обеспечения». —Голанг

Во многих языках существует множество способов решения данной проблемы. Программисты могут потратить много времени на размышления о наилучшем способе решения проблемы. Golang, с другой стороны, настаивает на урезанной функциональности — есть только один правильный способ решить проблему.

Это экономит время разработчиков и упрощает поддержку больших кодовых баз. В Golang нет «выразительных» функций вроде карт и фильтров.

«Если у вас есть функции, повышающие выразительность, это обычно увеличивает стоимость» - Роб Пайк

Недавно выпущен новый логотип для golang: https://blog.golang.org/go-brand

начиная

Golang состоит из пакетов. Пакет main сообщает компилятору Golang, что программа скомпилирована как исполняемый файл, а не как разделяемая библиотека. Это точка входа приложения. Основной пакет определяется как:

package main

Давайте напишем простой пример hello world, создав файл main.go в рабочей области Go lang.

Рабочее пространство

Рабочие области в Go определяются переменной среды GOPATH.

Любой код, который вы пишете, будет написан в рабочей области. Go будет искать все пакеты в каталоге GOPATH или в каталоге GOROOT, установленном по умолчанию при установке Go. GOROOT — это путь, по которому установлен go.

Установите GOPATH в нужный каталог. Теперь давайте установим ~/workspace в GOPATH.

# export env
export GOPATH=~/workspace
# go inside the workspace directory
cd ~/workspace

Создайте файл main.go со следующим кодом в только что созданной папке рабочей области.

HELLO WORLD!

 package main

 import (
     "fmt"
 )

 func main() {
     fmt.Println("Hello World!")
 }

В приведенном выше примереfmt— это встроенный в Go пакет, который реализует функции для форматированного ввода-вывода.

Мы используемimportключевое слово для импорта пакетов в Go.func mainЯвляется основной точкой входа для выполнения кода.Printlnэто функция внутри пакета,fmtОн печатает для нас «hello world».

Давайте посмотрим на запуск этого файла. Мы можем запускать команды Go двумя способами. Как мы знаем, Go — это компилируемый язык, поэтому нам сначала нужно его скомпилировать, прежде чем выполнять.

> go build main.go

Это создаст бинарный исполняемый файл main , который мы теперь можем запустить.

> ./main 
# Hello World!

Примечание. Чтобы попробовать запустить код, упомянутый в этой статье, вы можете использоватьplay.golang.org

Переменная

Переменные в Go объявляются явно. Go — статически типизированный язык. Это означает проверку типа переменной при ее объявлении. Переменные могут быть объявлены как:

var a int

В этом случае значение будет равно 0. Используйте следующий синтаксис для объявления и инициализации переменных с разными значениями:

var a = 1

Эта переменная автоматически назначается как int. Мы можем использовать сокращенное определение объявления переменной:

message := "hello world"

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

var bc int = 2,3

тип данных

Как и любой другой язык программирования, Golang поддерживает множество различных структур данных. Давайте исследуем:

NUMBER, STRING, AND BOOLEAN

Некоторые типы INT: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr...

Строковый тип хранит последовательность байтов. он использует ключевые словаstringпредставления и заявления.

использовать ключевые словаboolСохраняет логический тип.

Golang также поддерживает сложные числовые типы данных, которые можно использовать сcomplex64иcomplex128.

var a bool = true
var b int = 1
var c string = "hello world"
var d float32 = 1.222
var x complex128 = cmplx.Sqrt(-5 + 12i)

ARRAYS, SLICES, AND MAPS

Массивы — это элементарные последовательности одного и того же типа данных. Массив имеет фиксированную длину, определенную в объявлении, поэтому его нельзя расширить. Объявление массива:

var a [5] int

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

var multiD [2] [3] int

Массивы ограничивают эту ситуацию, когда значение массива изменяется во время выполнения. Массивы также не предоставляют возможности получения подмассивов. Для этой цели в Golang есть тип данных, называемый срезами.

Срез хранит последовательность элементов, которую можно расширить в любое время. Объявление слайса похоже на объявление массива — емкость не определена:

var b [] int

Это создаст срез нулевой емкости и нулевой длины. Срезы также могут определять емкость и длину. Мы можем использовать следующий синтаксис:

numbers:= make([] int5,10

Здесь начальная длина среза равна 5, а емкость равна 10.

Срез — это абстракция массива. Срезы используют массивы в качестве базовой структуры. Срез содержит три компонента: емкость, длину и указатель на базовый массив, как показано на следующей диаграмме:

Емкость слайса можно увеличить, используя функции добавления или копирования. Функция append может добавлять значения в конец массива и при необходимости увеличивать емкость.

numbers = append(numbers, 1, 2, 3, 4)

Еще один способ увеличить емкость слайса — использовать функцию копирования. Просто создайте еще один слайс большей емкости и скопируйте исходный слайс во вновь созданный слайс:

// create a new slice
number2 := make([]int, 15)
// copy the original slice to new slice
copy(number2, number)

Мы можем создавать подсрезы слайсов. Это можно сделать с помощью следующей команды:

 package main

 import (
     "fmt"
 )

 func main() {
     // initialize a slice with 4 len and values
     number2 := []int{1, 2, 3, 4}
     fmt.Println(number2) // -> [1 2 3 4]
     // create sub slices
     slice1 := number2[2:]
     fmt.Println(slice1) // -> [3 4]
     slice2 := number2[:3]
     fmt.Println(slice2) // -> [1 2 3]
     slice3 := number2[1:4]
     fmt.Println(slice3) // -> [2 3 4]
 }

Map— это тип данных в Go, который сопоставляет ключи со значениями. Мы можем определить отображение с помощью:

var m map[string]int

Здесь m — новая переменная карты, ключи которойstring, значениеintegers. Мы можем легко добавить ключи и значения на карту:

package main

 import (
     "fmt"
 )

 func main() {
     m := make(map[string]int)
     // adding key/value
     m["clearity"] = 2
     m["simplicity"] = 3
     // printing the values
     fmt.Println(m["clearity"])   // -> 2
     fmt.Println(m["simplicity"]) // -> 3
 }

преобразование типов

Вы можете использовать преобразование типов для преобразования одного типа данных в другой. Давайте посмотрим на простое преобразование типов:

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i++
 }

 func main() {
     a := 1.1
     b := int(a)
     fmt.Println(b)
     //-> 1
 }

Не все типы типов данных могут быть преобразованы в другие типы. Убедитесь, что тип данных совместим с преобразованием.

Условные операторы

IF ELSE

Для условных операторов мы можем использовать оператор if-else, как показано в следующем примере. Убедитесь, что фигурные скобки находятся на той же строке, что и условие.

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i++
 }

 func main() {
     if num := 9; num < 0 {
         fmt.Println(num, "is negative")
     } else if num < 10 {
         fmt.Println(num, "has 1 digit")
     } else {
         fmt.Println(num, "has multiple digits")
     }
 }

SWITCH CASE

Switch casesПолезно для организации нескольких условных операторов.В следующем примере показан простой оператор switch case:

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i++
 }

 func main() {
     i := 2
     switch i {
     case 1:
         fmt.Println("one")
     case 2:
         fmt.Println("two")
     default:
         fmt.Println("none")
     }
 }

Looping

В Голанге есть ключевое слово цикла. Единая команда for loop помогает реализовать различные типы циклов:

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i++
 }

 func main() {
     i := 0
     sum := 0
     for i < 10 {
         sum += 1
         i++
     }
     fmt.Println(sum)
 }

Приведенный выше пример похож на цикл while в C. Для цикла for можно использовать тот же оператор for:

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i++
 }

 func main() {
     sum := 0
     for i := 0; i < 10; i++ {
         sum += i
     }
     fmt.Println(sum)
 }

Бесконечный цикл в Go:

for {
}

указатель

Golang предоставляет указатели. Указатель — это место, где хранится адрес значения. Указатели определяются *. Указатели определяются в соответствии с типом данных. пример:

var ap * int

Этотapявляется указателем на целочисленный тип. Должен&Действия могут использоваться для получения адреса переменной.

a := 12
ap =&a

можно использовать*Оператор обращается к значению, на которое указывает указатель:

fmt.Println(* AP
// => 12

Указатели обычно предпочтительнее при передаче структур в качестве параметров или объявлении методов для определенных типов.

  • При передаче значения фактически скопированное значение означает больше памяти
  • После передачи указателя значение, измененное функцией, будет отражено в вызывающем методе/функции.

пример:

package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i++
 }

 func main() {
     i := 10
     increment(&i)
     fmt.Println(i)
 }

 //=> 11

Примечание. При запуске кода примера в тексте не забудьте включить его в основной пакет и при необходимости импортировать fmt или другие пакеты, как показано в первом примере main.go выше.

функция

Основная функция, определенная в основном пакете, является точкой входа для выполнения программы golang. Можно определить и использовать больше функций. Давайте рассмотрим простой пример:

 package main

 import (
     "fmt"
 )

 func add(a int, b int) int {
     c := a + b
     return c
 }

 func main() {
     fmt.Println(add(2, 1))
 }
 //=> 3

Как мы видели в приведенном выше примере, используйтеfuncКлючевое слово, за которым следует имя функции, определяет функцию Golang. Параметры, требуемые функцией, должны быть определены с точки зрения их типов данных и, наконец, типа возвращаемых данных.

Возвращаемое значение функции также может быть предопределено в самой функции:

package main

 import (
     "fmt"
 )

 func add(a int, b int) (c int) {
     c = a + b
     return
 }

 func main() {
     fmt.Println(add(2, 1))
 }
 //=> 3

Здесь c определяется как возвращаемая переменная. Следовательно, определенная переменная c будет возвращена автоматически без необходимости ее определения в операторе return в конце.

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

package main

 import (
     "fmt"
 )

 func add(a int, b int) (int, string) {
     c := a + b
     return c, "successfully added"
 }

 func main() {
     sum, message := add(2, 1)
     fmt.Println(message)
     fmt.Println(sum)
 }

 //=> successfully added
 //=> 3

Методы, структуры и интерфейсы

Golang не является полностью объектно-ориентированным языком, но имеет структуры, интерфейсы и методы для поддержки объектно-ориентированной разработки.

структура

Структура — это набор типов с разными полями. Структуры используются для группировки данных. Например, если бы мы хотели сгруппировать данные типа «Человек», мы бы определили атрибуты человека, которые могут включать имя, возраст, пол. Структуры могут быть определены с использованием следующего синтаксиса:

type person struct {
     名字串
     年龄int
     性别串
 }

Определив структуру типа человека, давайте теперь создадим человека:

//way 1: specifying attribute and value
p = person{name: "Bob", age: 42, gender: "Male"}
//way 2: specifying only value
person{"Bob", 42, "Male"}

Мы можем легко получить доступ к этим данным с помощью точки (.)

p.name
//=> Bob
p.age
//=> 42
p.gender
//=> Male

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

pp = &person{name: "Bob", age: 42, gender: "Male"}
pp.name
//=> Bob

метод

Методы — это специальные типы функций с приемниками. Получатель может быть либо значением, либо указателем. Давайте создадим метод под названием description, который мы использовали в нашем примере выше.personМетод структуры:

package main

 import "fmt"

 // struct defination
 type person struct {
     name   string
     age    int
     gender string
 }

 // method defination
 func (p *person) describe() {
     fmt.Printf("%v is %v years old.", p.name, p.age)
 }

 func (p *person) setAge(age int) {
     p.age = age
 }

 func (p person) setName(name string) {
     p.name = name
 }

 func main() {
     pp := &person{name: "Bob", age: 42, gender: "Male"}
     pp.describe()
     // => Bob is 42 years old
     pp.setAge(45)
     fmt.Println(pp.age)
     //=> 45
     pp.setName("Hari")
     fmt.Println(pp.name)
     //=> Bob
 }

Как видно из приведенного выше примера, метод теперь можно вызывать с помощью оператора точкиpp.describe. Обратите внимание, что приемники являются указателями. С указателями мы передаем ссылку на значение, поэтому, если мы внесем какие-либо изменения в метод, это будет отражено в получателе.ppсередина. Он также не создает новую копию объекта, экономя память.

Обратите внимание, что в приведенном выше примере значение age изменилось, а значение name не изменилось, потому что метод setName имеет тип приемника, а setAge — тип указателя.

интерфейс

Интерфейс Golang — это набор методов. Интерфейсы помогают группировать свойства типа вместе. Возьмем в качестве примера интерфейс с животными:

 type animal interface {
     description() string
 }

Животное здесь — тип интерфейса. Теперь давайте создадим два разных типа животных, которые реализуют интерфейс животных:

package main

 import (
     "fmt"
 )

 type animal interface {
     description() string
 }

 type cat struct {
     Type  string
     Sound string
 }

 type snake struct {
     Type      string
     Poisonous bool
 }

 func (s snake) description() string {
     return fmt.Sprintf("Poisonous: %v", s.Poisonous)
 }

 func (c cat) description() string {
     return fmt.Sprintf("Sound: %v", c.Sound)
 }

 func main() {
     var a animal
     a = snake{Poisonous: true}
     fmt.Println(a.description())
     a = cat{Sound: "Meow!!!"}
     fmt.Println(a.description())
 }

 //=> Poisonous: true
 //=> Sound: Meow!!!

В основной функции мы создаемaТипanimalПеременные. Мы присваиваем животным типы змей и кошек и используем Println для печати описания. Поскольку мы реализовали методы, описанные в двух типах (кошках и змеях), по-разному, то получили описания разных животных.

Сумка

Пишем весь код на Golang в одном пакете. Этот основной пакет является точкой входа для выполнения программы. В Go есть много встроенных пакетов. Наиболее часто мы используем пакет fmt.

«Пакеты Go являются основным механизмом программирования в больших размерах, которые предоставляет Go, и позволяют разделить большой проект на более мелкие части».

— Роберт Гриземер

установить пакет

go get 
// example
go get github.com/satori/go.uuid

Пакеты, которые мы устанавливаем, хранятся в GOPATH, нашем рабочем каталоге. Вы можете войти в пакет через папку pkg в нашем рабочем каталогеcd $GOPATH/pkg.

Создание пользовательских пакетов

Начнем с создания папки custom_package:

> mkdir custom_package
> cd custom_package

Чтобы создать пользовательский пакет, нам нужно сначала создать папку с именем пакета, который нам нужен. Предположим, мы строим пакетperson. Для этого мы создаемpersonпапка вcustom_packageВ папке:

> mkdir person
> cd person

Теперь создадим в этой папке файл person.go.

package person

 func Description(name string) string {
     return "The person name is: " + name
 }

 func secretName(name string) string {
     return "Do not share"
 }

Теперь нам нужно установить пакет, чтобы его можно было импортировать и использовать. Итак, давайте установим его:

> go install

Теперь вернемся в папку custom_package и создадим файл main.go.

 package main

 import (
     "custom_package/person"
     "fmt"
 )

 func main() {
     p := person.Description("Milap")
     fmt.Println(p)
 }

 // => The person name is: Milap

Теперь мы можем импортировать то, что мы создалиpersonpackage и используйте функцию Description. Обратите внимание, что функция secretName, которую мы создали в пакете, будет недоступна. В Go имена методов, начинающиеся со строчной буквы, будут приватными.

пакетная документация

Golang имеет встроенную поддержку документации пакетов. Выполните следующие команды для создания документации:

godoc person Description

Это создаст документацию для функции Description для наших разработчиков пакетов. Чтобы просмотреть документацию, запустите веб-сервер с помощью следующей команды:

godoc -http =“:8080

Теперь откройте URLhttp//localhost8080/pkg/И ознакомьтесь с документацией к пакету, который мы только что создали.

Некоторые встроенные пакеты в GO

FMT

Этот пакет реализует форматированные функции ввода-вывода. Мы использовали этот пакет для печати на стандартный вывод.

JSON

Еще один полезный пакет в Golang — это пакет json. Это помогает в кодировании/декодировании JSON. Давайте возьмем пример для кодирования/декодирования некоторого json:

encode

package main

 import (
     "encoding/json"
     "fmt"
 )

 func main() {
     mapA := map[string]int{"apple": 5, "lettuce": 7}
     mapB, _ := json.Marshal(mapA)
     fmt.Println(string(mapB))
 }

Decode

package main

 import (
     "encoding/json"
     "fmt"
 )

 type response struct {
     PageNumber int      json:"page"
     Fruits     []string json:"fruits"
 }

 func main() {
     str := {"page": 1, "fruits": ["apple", "peach"]}
     res := response{}
     json.Unmarshal([]byte(str), &res)
     fmt.Println(res.PageNumber)
 }

 //=> 1

При декодировании байтов json с использованием unmarshal первый параметр — это байты json, а второй параметр — адрес структуры типа ответа, на которую мы хотим сопоставить json. Обратите внимание, что ключ страницы карты json:"page" является ключом PageNumber в структуре.

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

Ошибка — это нежелательный и неожиданный результат работы программы. Предположим, мы делаем вызов API к внешней службе. Этот вызов API может быть успешным, а может и нет. Ошибки в программах Golang можно идентифицировать по типу ошибки. Давайте посмотрим на этот пример:

resp, err := http.Get("http://example.com/")

Здесь err означает, что вызов API может пройти или не пройти. Мы можем проверить, является ли ошибкаnilи обработайте ответ соответствующим образом:

 package main

 import (
     "fmt"
     "net/http"
 )

 func main() {
     resp, err := http.Get("http://example.com/")
     if err != nil {
         fmt.Println(err)
         return
     }
     fmt.Println(resp)
 }

функция возвращает пользовательскую ошибку

Когда мы пишем собственные функции, бывают случаи, когда мы сталкиваемся с ошибками. в состоянии пройтиerrorsчтобы вернуть эти ошибки:

  package main

 import (
     "errors"
     "fmt"
 )

 func Increment(n int) (int, error) {
     if n < 0 {
         // return error object
         return 0, errors.New("math: cannot process negative number")
     }
     return (n + 1), nil
 }

 func main() {
     num := 5
     if inc, err := Increment(num); err != nil {
         fmt.Printf("Failed Number: %v, error message: %v", num, err)
     } else {
         fmt.Printf("Incremented Number: %v", inc)
     }
 }

 // => The person name is: Milap

Большинство пакетов, встроенных в Go, или внешние пакеты, которые мы используем, имеют механизм обработки ошибок. Таким образом, любая функция, которую мы вызываем, может содержать ошибку. Эти ошибки никогда не следует игнорировать, и они всегда корректно обрабатываются при вызове функции, как мы это сделали в примере выше.

panic

Паника — это необработанная ошибка, внезапно возникающая во время выполнения программы. В Go паника — не идеальный способ обработки исключений в программе. Рекомендуется использовать объект ошибки. При возникновении паники выполнение программы останавливается. То, что выполняется после паники, — это отсрочка.

package main

import "fmt"

func main() {
 f()
 fmt.Println("Returned normally from f.")
}

func f() {
 defer func() {
     if r := recover(); r != nil {
         fmt.Println("Recovered in f", r)
     }
 }()
 fmt.Println("Calling g.")
 g(0)
 fmt.Println("Returned normally from g.")
}

func g(i int) {
 if i > 3 {
     fmt.Println("Panicking!")
     panic(fmt.Sprintf("%v", i))
 }
 defer fmt.Println("Defer in g", i)
 fmt.Println("Printing in g", i)
 g(i + 1)
}

defer

Defer всегда выполняется в конце функции.

В приведенном выше примере мы использовали panic() для паники при выполнении программы. Как вы заметили, существует оператор задержки, который заставит программу выполнить строку в конце выполнения программы. Defer также можно использовать, когда нам нужно что-то сделать в конце функции, например, закрыть файл.

параллелизм

Golang создан с учетом параллелизма. Параллелизм в Golang может быть достигнут с помощью облегченных многопоточных процедур Go.

Go routine

Горутина — это функция, которая может выполняться параллельно или одновременно с другой функцией. Создать горутину очень просто. Просто поставив перед функцией ключевое слово Go, мы можем заставить ее выполняться параллельно. Подпрограммы Go очень легкие, поэтому мы можем создавать тысячи подпрограмм. Давайте рассмотрим простой пример:

package main

 import (
     "fmt"
     "time"
 )

 func main() {
     go c()
     fmt.Println("I am main")
     time.Sleep(time.Second * 2)
 }

 func c() {
     time.Sleep(time.Second * 2)
     fmt.Println("I am concurrent")
 }

 //=> I am main
 //=> I am concurrent

Как видно из приведенного выше примера, функция c — это горутина, которая выполняется параллельно с основным потоком Go. Иногда нам нужно разделить ресурсы между несколькими потоками. Golang не любит делиться переменными одного потока с другим потоком, потому что это увеличивает вероятность взаимоблокировок и ожидания ресурсов. Есть еще один способ разделить ресурсы между горутинами: каналы.

ряд

Мы можем использовать каналы для передачи данных между двумя горутинами. При создании канала необходимо указать тип данных, которые получает канал. Давайте создадим простой канал типа string следующим образом:

c := make(chan string)

Используя этот канал, мы можем отправлять данные строкового типа. Мы можем как отправлять, так и получать данные в этом канале:

package main

 import "fmt"

 func main() {
     c := make(chan string)
     go func() { c <- "hello" }()
     msg := <-c
     fmt.Println(msg)
 }

 //=>"hello"接收方通道等待发送方向通道发送数据。

односторонний канал

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

 package main

 import (
     "fmt"
 )

 func main() {
     ch := make(chan string)
     go sc(ch)
     fmt.Println(<-ch)
 }

 func sc(ch chan<- string) {
     ch <- "hello"
 }

В приведенном выше примереscэто горутина, которая может только отправлять сообщения на канал, но не получать сообщения.

Использование select для обработки нескольких каналов для Goroutine

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

 package main

 import (
     "fmt"
     "time"
 )

 func main() {
     c1 := make(chan string)
     c2 := make(chan string)
     go speed1(c1)
     go speed2(c2)
     fmt.Println("The first to arrive is:")
     select {
     case s1 := <-c1:
         fmt.Println(s1)
     case s2 := <-c2:
         fmt.Println(s2)
     }
 }

 func speed1(ch chan string) {
     time.Sleep(2 * time.Second)
     ch <- "speed 1"
 }

 func speed2(ch chan string) {
     time.Sleep(1 * time.Second)
     ch <- "speed 2"
 }

В приведенном выше примере main ожидает два канала c1 и c2. Используйте оператор select case, чтобы напечатать основную функцию, сообщение отправляется из канала, в зависимости от того, какой из них был получен первым.

буферный канал

Вы можете создавать буферизованные каналы в Golang. С буферизованным каналом сообщения, отправленные в канал, будут заблокированы, если буфер заполнен. Давайте посмотрим на этот пример:

package main

 import "fmt"

 func main() {
     ch := make(chan string, 2)
     ch <- "hello"
     ch <- "world"
     ch <- "!" // extra message in buffer
     fmt.Println(<-ch)
 }

 // => fatal error: all goroutines are asleep - deadlock!

Почему GOLANG добился успеха?

Простота… — Роб-Пайк

Great!

Мы узнали о некоторых основных компонентах и ​​особенностях Golang.

  1. переменная, тип данных
  2. Срезы массива и карты
  3. функция
  4. Циклы и условные операторы
  5. указатель
  6. Сумка
  7. Методы, структуры и интерфейсы
  8. обработка ошибок
  9. Параллелизм — горутины и каналы

Поздравляем, теперь вы неплохо разбираетесь в Go.

One of my most productive days was throwing away 1,000 lines of code. --Ken Thompson

Не останавливайтесь здесь. продолжать. Рассмотрите небольшое приложение и начните его создавать.