Учебное пособие по Golang: изучение языка программирования Go для начинающих
Что такое Го?
Go (также известный как Golang) — язык программирования с открытым исходным кодом, разработанный Google. Это статически типизированный компилируемый язык. Go поддерживает параллельное программирование, то есть позволяет одновременно запускать несколько процессов. Это достигается с помощью каналов, горутин и т. д. В Go Language есть сборщик мусора, который сам управляет памятью и позволяет отложенное выполнение функций.
В этом руководстве по изучению языка Go мы изучим все основы Golang.
Как скачать и установить GO
Шаг 1) Перейдите на https://golang.org/dl/. Загрузите бинарный файл для вашей ОС.
Шаг 2) Double нажмите на установщик и нажмите «Выполнить».
Шаг 3) Нажмите Далее
Шаг 4) Выберите папку установки и нажмите «Далее».
Шаг 5) Нажмите «Готово» после завершения установки.
Шаг 6) После завершения установки вы можете проверить это, открыв терминал и набрав
go version
Это отобразит установленную версию go.
Ваша первая программа Go – Go Hello World!
Создайте папку с названием StudyGo. В этом уроке по языку Go мы будем создавать программы Go внутри этой папки. Файлы Go создаются с расширением .идти. Вы можете запускать программы Go, используя синтаксис
go run <filename>
Создайте файл с именем first.go, добавьте в него приведенный ниже код и сохраните.
package main import ("fmt") func main() { fmt.Println("Hello World! This is my first Go program\n") }
Перейдите в эту папку в своем терминале. Запустите программу с помощью команды
иди и беги первым.
Вы можете увидеть результат печати
Hello World! This is my first Go program
Теперь давайте обсудим вышеуказанную программу.
package main — каждая программа Go Language должна начинаться с имени пакета. Go позволяет нам использовать пакеты в других программах Go и, следовательно, поддерживает возможность повторного использования кода. Выполнение программы Go начинается с кода внутри пакета с именем main.
import fmt – импортирует пакет fmt. Этот пакет реализует функции ввода-вывода.
func main() — это функция, с которой начинается выполнение программы. Основная функция всегда должна размещаться в основном пакете. Под функцией main() вы можете написать код внутри { }.
fmt.Println – это позволит распечатать текст на экране с помощью функции Println fmt.
Примечание. В следующих разделах этого руководства по Go, когда вы упоминаете выполнение/запуск кода, это означает сохранение кода в файле с расширением .go и запуск его с использованием синтаксиса.
go run <filename>
Типы данных
Типы (типы данных) представляют тип значения, хранящегося в переменной, тип значения, возвращаемого функцией, и т. д.
В языке Go существует три основных типа.
Числовые типы – Представляют числовые значения, которые включают целые, с плавающей точкой и комплексные значения. Различные числовые типы:
int8 – 8-битные целые числа со знаком.
int16 – 16-битные целые числа со знаком.
int32 – 32-битные целые числа со знаком.
int64 – 64-битные целые числа со знаком.
uint8 – 8-битные целые числа без знака.
uint16 – 16-битные целые числа без знака.
uint32 – 32-битные целые числа без знака.
uint64 – 64-битные целые числа без знака.
float32 – 32-битные числа с плавающей запятой.
float64 – 64-битные числа с плавающей запятой.
complex64 – имеет float32 действительных и мнимых частей.
complex128 – имеет float32 действительных и мнимых частей.
Типы строк – Представляет собой последовательность байтов (символов). Вы можете выполнять различные операции со строками, такие как объединение строк, извлечение подстроки и т. д.
Логические типы – Представляет два значения: true или false.
Голанговый интерфейс
Голанговый интерфейс представляет собой набор сигнатур методов, используемых типом для реализации поведения объектов. Основная цель интерфейса Golang — предоставить сигнатуры методов с именами, аргументами и типами возвращаемых значений. Объявление и реализация метода зависит от типа. Интерфейс в Golang можно объявить с помощью ключевого слова «интерфейс».
Переменные
Переменные указывают на ячейку памяти, в которой хранится какое-то значение. Параметр типа (в приведенном ниже синтаксисе) представляет тип значения, которое может быть сохранено в данной ячейке памяти.
Переменная может быть объявлена с использованием синтаксиса
var <variable_name> <type>
Объявив переменную определенного типа, вы можете присвоить этой переменной любое значение этого типа.
Вы также можете указать начальное значение переменной во время самого объявления, используя
var <variable_name> <type> = <value>
Если вы объявляете переменную с начальным значением, перейдите к выводу типа переменной из типа присвоенного значения. Таким образом, вы можете опустить тип во время объявления, используя синтаксис
var <variable_name> = <value>
Кроме того, вы можете объявить несколько переменных с помощью синтаксиса
var <variable_name1>, <variable_name2> = <value1>, <value2>
В приведенной ниже программе в этом руководстве по Go есть несколько примеров объявлений переменных на Golang.
package main import "fmt" func main() { //declaring a integer variable x var x int x=3 //assigning x the value 3 fmt.Println("x:", x) //prints 3 //declaring a integer variable y with value 20 in a single statement and prints it var y int=20 fmt.Println("y:", y) //declaring a variable z with value 50 and prints it //Here type int is not explicitly mentioned var z=50 fmt.Println("z:", z) //Multiple variables are assigned in single line- i with an integer and j with a string var i, j = 100,"hello" fmt.Println("i and j:", i,j) }
Выход будет
x: 3 y: 20 z: 50 i and j: 100 hello
Go Language также предоставляет простой способ объявления переменных со значением, опуская ключевое слово var, используя
<variable_name> := <value>
Обратите внимание, что вы использовали := вместо =. Вы не можете использовать := только для присвоения значения уже объявленной переменной. := используется для объявления и присвоения значения.
Создайте файл с именем Assign.go со следующим кодом.
package main import ("fmt") func main() { a := 20 fmt.Println(a) //gives error since a is already declared a := 30 fmt.Println(a) }
Выполните go, запустите Assign.go, чтобы увидеть результат:
./assign.go:7:4: no new variables on left side of :=
Переменные, объявленные без начального значения, будут иметь значение 0 для числовых типов, false для логических типов и пустую строку для строк.
Константы
Константные переменные — это переменные, значение которых не может быть изменено после присвоения. Константа в языке программирования Go объявляется с помощью ключевого слова const.
Создайте файл с именем constant.go и следующим кодом
package main import ("fmt") func main() { const b =10 fmt.Println(b) b = 30 fmt.Println(b) }
Выполните go run Constant.go, чтобы увидеть результат:
.constant.go:7:4: cannot assign to b
Примеры циклов
Циклы используются для многократного выполнения блока операторов на основе условия. Большинство языков программирования предоставляют три типа циклов: for, while, do while. Но язык программирования Go поддерживает только циклы.
Синтаксис цикла for в Голанге:
for initialisation_expression; evaluation_expression; iteration_expression{ // one or more statement }
Выражение инициализации выполняется первым (и только один раз) в цикле for Golang.
Затем оценивается выражение_оценки, и если оно истинно, выполняется код внутри блока.
Идентификатор iteration_expression выполняется, а Assessment_expression вычисляется снова. Если это правда, блок операторов выполняется снова. Это будет продолжаться до тех пор, пока выражение Assessment_expression не станет ложным.
Скопируйте приведенную ниже программу в файл и запустите ее, чтобы увидеть номера печати цикла Golang от 1 до 5.
package main import "fmt" func main() { var i int for i = 1; i <= 5; i++ { fmt.Println(i) } }
Выход
1 2 3 4 5
Если еще
If else — это условный оператор. Синакс - это
if condition{ // statements_1 }else{ // statements_2 }
Здесь условие оценивается, и если оно истинно, будут выполнены операторы_1, иначе будут выполнены операторы_2.
Вы также можете использовать оператор if без else. Вы также можете использовать операторы if else. Приведенные ниже программы объяснят больше об if else.
Выполните приведенную ниже программу. Он проверяет, меньше ли число x 10. Если да, он выводит «x меньше 10».
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x < 10 fmt.Println("x is less than 10") } }
Здесь, поскольку значение x больше 10, оператор внутри условия блока не будет выполнен.
Теперь посмотрите программу ниже. В этом руководстве по языку программирования Go у нас есть блок else, который будет выполнен в случае неудачной оценки if.
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else { //Executes if x >= 10 fmt.Println("x is greater than or equals 10") } }
Эта программа выдаст вам результат
x is greater than or equals 10
Теперь в этом уроке по Go мы увидим программу с несколькими блоками if else (связанными в цепочку if else). Выполните приведенный ниже пример Go. Он проверяет, меньше ли число 10, находится в диапазоне 10-90 или больше 90.
package main import "fmt" func main() { var x = 100 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else if x >= 10 && x <= 90 { //Executes if x >= 10 and x<=90 fmt.Println("x is between 10 and 90") } else { //Executes if both above cases fail i.e x>90 fmt.Println("x is greater than 90") } }
Здесь сначала условие if проверяет, меньше ли x 10, а это нет. Таким образом, он проверяет следующее условие (иначе если), находится ли оно между 10 и 90, что также является ложным. Затем он выполняет блок в разделе else, который дает результат.
x is greater than 90
Коммутатор
Switch — еще одно условное утверждение. Операторы переключения оценивают выражение, и результат сравнивается с набором доступных значений (случаев). Как только совпадение найдено, выполняются инструкции, связанные с этим совпадением (случайом). Если совпадение не найдено, ничего не будет выполнено. Вы также можете добавить вариант по умолчанию для переключения, который будет выполняться, если другие совпадения не найдены. Синтаксис переключателя
switch expression { case value_1: statements_1 case value_2: statements_2 case value_n: statements_n default: statements_default }
Здесь значение выражения сравнивается со значениями в каждом случае. Как только совпадение найдено, выполняются инструкции, связанные с этим случаем. Если совпадение не найдено, выполняются инструкции в разделе по умолчанию.
Выполните приведенную ниже программу
package main import "fmt" func main() { a,b := 2,1 switch a+b { case 1: fmt.Println("Sum is 1") case 2: fmt.Println("Sum is 2") case 3: fmt.Println("Sum is 3") default: fmt.Println("Printing default") } }
Вы получите результат как
Sum is 3
Измените значение a и b на 3, и результат будет таким:
Printing default
Вы также можете указать несколько значений в регистре, разделив их запятой.
Массивы
Массив представляет собой фиксированный размер, именованную последовательность элементов одного типа. У вас не может быть массива, содержащего как целые числа, так и символы. Вы не можете изменить размер массива после его определения.
Синтаксис объявления массива следующий:
var arrayname [size] type
Каждому элементу массива можно присвоить значение, используя синтаксис
arrayname [index] = value
Индекс массива начинается с от 0 до размера-1.
Вы можете присваивать значения элементам массива во время объявления, используя синтаксис
arrayname := [size] type {value_0,value_1,…,value_size-1}
Вы также можете игнорировать параметр размера при объявлении массива со значениями, заменив размер на ... и компилятор найдет длину по количеству значений. Синтаксис
arrayname := […] type {value_0,value_1,…,value_size-1}
Вы можете найти длину массива, используя синтаксис
len(arrayname)
Выполните приведенный ниже пример Go, чтобы понять массив.
package main import "fmt" func main() { var numbers [3] string //Declaring a string array of size 3 and adding elements numbers[0] = "One" numbers[1] = "Two" numbers[2] = "Three" fmt.Println(numbers[1]) //prints Two fmt.Println(len(numbers)) //prints 3 fmt.Println(numbers) // prints [One Two Three] directions := [...] int {1,2,3,4,5} // creating an integer array and the size of the array is defined by the number of elements fmt.Println(directions) //prints [1 2 3 4 5] fmt.Println(len(directions)) //prints 5 //Executing the below commented statement prints invalid array index 5 (out of bounds for 5-element array) //fmt.Println(directions[5]) }
Результат
Two 3 [One Two Three] [1 2 3 4 5] 5
Функция среза и добавления Golang
Срез — это часть или сегмент массива. Или это представление или частичное представление базового массива, на который он указывает. Вы можете получить доступ к элементам среза, используя имя среза и номер индекса, так же, как и в массиве. Вы не можете изменить длину массива, но можете изменить размер среза.
Содержимое среза на самом деле является указателями на элементы массива. Это значит если вы измените какой-либо элемент в срезе, это также повлияет на содержимое базового массива.
Синтаксис создания среза:
var slice_name [] type = array_name[start:end]
Это создаст срез с именем срез_имя из массива с именем имя_массива с элементами в индексе от начала до конца-1.
Теперь в этом уроке по Golang мы выполним приведенную ниже программу. Программа создаст фрагмент массива и распечатает его. Кроме того, вы можете видеть, что изменение содержимого среза приведет к изменению фактического массива.
package main import "fmt" func main() { // declaring array a := [5] string {"one", "two", "three", "four", "five"} fmt.Println("Array after creation:",a) var b [] string = a[1:4] //created a slice named b fmt.Println("Slice after creation:",b) b[0]="changed" // changed the slice data fmt.Println("Slice after modifying:",b) fmt.Println("Array after slice modification:",a) }
Это напечатает результат как
Array after creation: [one two three four five] Slice after creation: [two three four] Slice after modifying: [changed three four] Array after slice modification: [one changed three four five]
Существуют определенные функции, такие как Golang len, Golang Append, которые вы можете применять к срезам.
лен (имя_фрагмента) – возвращает длину среза
добавить (имя_фрагмента, значение_1, значение_2) – Добавление Golang используется для добавления value_1 и value_2 к существующему срезу.
добавить(slice_nale1,slice_name2…) – добавляет имя_фрагмента2 к имя_фрагмента1
Выполните следующую программу.
package main import "fmt" func main() { a := [5] string {"1","2","3","4","5"} slice_a := a[1:3] b := [5] string {"one","two","three","four","five"} slice_b := b[1:3] fmt.Println("Slice_a:", slice_a) fmt.Println("Slice_b:", slice_b) fmt.Println("Length of slice_a:", len(slice_a)) fmt.Println("Length of slice_b:", len(slice_b)) slice_a = append(slice_a,slice_b...) // appending slice fmt.Println("New Slice_a after appending slice_b :", slice_a) slice_a = append(slice_a,"text1") // appending value fmt.Println("New Slice_a after appending text1 :", slice_a) }
Выход будет
Slice_a: [2 3] Slice_b: [two three] Length of slice_a: 2 Length of slice_b: 2 New Slice_a after appending slice_b : [2 3 two three] New Slice_a after appending text1 : [2 3 two three text1]
Программа сначала создает 2 фрагмента и печатает их длину. Затем он добавил один фрагмент к другому, а затем добавил строку к полученному фрагменту.
функции
Функция представляет собой блок операторов, выполняющий определенную задачу. Объявление функции сообщает нам имя функции, тип возвращаемого значения и входные параметры. Определение функции представляет собой код, содержащийся в функции. Синтаксис объявления функции следующий:
func function_name(parameter_1 type, parameter_n type) return_type { //statements }
Параметры и типы возвращаемых значений являются необязательными. Кроме того, вы можете вернуть несколько значений из функции.
Теперь в этом уроке Golang давайте запустим следующий пример Golang. Здесь функция с именем calc примет 2 числа и выполнит сложение и вычитание и вернет оба значения.
package main import "fmt" //calc is the function name which accepts two integers num1 and num2 //(int, int) says that the function returns two values, both of integer type. func calc(num1 int, num2 int)(int, int) { sum := num1 + num2 diff := num1 - num2 return sum, diff } func main() { x,y := 15,10 //calls the function calc with x and y an d gets sum, diff as output sum, diff := calc(x,y) fmt.Println("Sum",sum) fmt.Println("Diff",diff) }
Выход будет
Sum 25 Diff 5
Packages
Пакеты используются для организации кода. В большом проекте невозможно написать код в одном файле. Язык программирования Go позволяет нам организовывать код в разные пакеты. Это повышает читаемость кода и возможность повторного использования. Исполняемая программа Go должна содержать пакет с именем main, а выполнение программы начинается с функции с именем main. Вы можете импортировать другие пакеты в нашу программу, используя синтаксис
import package_name
В этом руководстве по Golang мы увидим и обсудим, как создавать и использовать пакеты в следующем примере Golang.
Шаг 1) Создайте файл package_example.go и добавьте приведенный ниже код.
package main import "fmt" //the package to be created import "calculation" func main() { x,y := 15,10 //the package will have function Do_add() sum := calculation.Do_add(x,y) fmt.Println("Sum",sum) }
В приведенной выше программе fmt — это пакет, который язык программирования Go предоставляет нам в основном для целей ввода-вывода. Также вы можете увидеть пакет с именем расчет. Внутри main() вы можете увидеть сумму шагов := вычисление.Do_add(x,y). Это означает, что вы вызываете функцию Do_add из расчета пакета.
Шаг 2) Во-первых, вам следует создать расчет пакета внутри папки с тем же именем в папке src. Установленный путь go можно найти в переменной PATH.
Для Mac найдите путь, выполнив echo $PATH
Итак, путь /usr/local/go.
Для Windows найдите путь, выполнив echo %GOROOT%
Здесь путь C:\Go\
Шаг 3) Перейдите в папку src (/usr/local/go/src для Mac и C:\Go\src для Windows). Теперь из кода имя пакета — вычисление. Go требует, чтобы пакет был помещен в одноименный каталог в каталоге src. Создайте каталог с именем «расчет» в папке src.
Шаг 4) Создайте файл с именем Calc.go (вы можете дать любое имя, но имя пакета в коде имеет значение. Здесь это должен быть расчет) внутри каталога вычислений и добавьте приведенный ниже код.
package calculation func Do_add(num1 int, num2 int)(int) { sum := num1 + num2 return sum }
Шаг 5) Запустите команду go install из каталога вычислений, которая скомпилирует файл Calc.go.
Шаг 6) Теперь вернитесь к package_example.go и запустите go run package_example.go. Результатом будет сумма 25.
Обратите внимание, что имя функции Do_add начинается с заглавной буквы. Это связано с тем, что в Go, если имя функции начинается с заглавной буквы, это означает, что другие программы могут видеть (получить доступ) к ней, в противном случае другие программы не могут получить к ней доступ. Если бы имя функции было do_add, вы бы получили ошибку.
не может ссылаться на неэкспортированное имя расчет.calc..
Отсрочка и накопление отсрочек
Операторы defer используются для отсрочки выполнения вызова функции до тех пор, пока функция, содержащая оператор defer, не завершит выполнение.
Давайте узнаем это на примере:
package main import "fmt" func sample() { fmt.Println("Inside the sample()") } func main() { //sample() will be invoked only after executing the statements of main() defer sample() fmt.Println("Inside the main()") }
Выход будет
Inside the main() Inside the sample()
Здесь выполнение sample() откладывается до завершения выполнения включающей функции (main()).
Объединение defer использует несколько операторов defer. Предположим, у вас есть несколько операторов defer внутри функции. Go помещает все вызовы отложенных функций в стек, и как только включающая функция возвращает значение, стековые функции выполняются в стеке. Порядок «последним пришел — первым ушел» (LIFO). Вы можете увидеть это на примере ниже.
Выполните приведенный ниже код
package main import "fmt" func display(a int) { fmt.Println(a) } func main() { defer display(1) defer display(2) defer display(3) fmt.Println(4) }
Выход будет
4 3 2 1
Здесь сначала выполняется код внутри main(), а затем вызовы отложенных функций выполняются в обратном порядке, т.е. 4, 3,2,1.
Указатели
Прежде чем объяснять указатели, давайте сначала обсудим оператор «&». Оператор «&» используется для получения адреса переменной. Это означает, что '&a' напечатает адрес переменной a в памяти.
В этом уроке по Golang мы выполним приведенную ниже программу, чтобы отобразить значение переменной и адрес этой переменной.
package main import "fmt" func main() { a := 20 fmt.Println("Address:",&a) fmt.Println("Value:",a) }
Результат будет
Address: 0xc000078008 Value: 20
Переменная-указатель хранит адрес памяти другой переменной. Вы можете определить указатель, используя синтаксис
var variable_name *type
Звездочка (*) означает, что переменная является указателем. Вы поймете больше, выполнив приведенную ниже программу.
package main import "fmt" func main() { //Create an integer variable a with value 20 a := 20 //Create a pointer variable b and assigned the address of a var b *int = &a //print address of a(&a) and value of a fmt.Println("Address of a:",&a) fmt.Println("Value of a:",a) //print b which contains the memory address of a i.e. &a fmt.Println("Address of pointer b:",b) //*b prints the value in memory address which b contains i.e. the value of a fmt.Println("Value of pointer b",*b) //increment the value of variable a using the variable b *b = *b+1 //prints the new value using a and *b fmt.Println("Value of pointer b",*b) fmt.Println("Value of a:",a)}
Выход будет
Address of a: 0x416020 Value of a: 20 Address of pointer b: 0x416020 Value of pointer b 20 Value of pointer b 21 Value of a: 21
Структуры
Структура — это определяемый пользователем тип данных, который сам содержит еще один элемент того же или другого типа.
Использование структуры — это двухэтапный процесс.
Сначала создайте (объявите) тип структуры.
Во-вторых, создайте переменные этого типа для хранения значений.
Структуры в основном используются, когда вы хотите хранить связанные данные вместе.
Рассмотрим часть информации о сотруднике, в которой указаны имя, возраст и адрес. Вы можете справиться с этим двумя способами
Создайте 3 массива: в одном массиве хранятся имена сотрудников, в другом — возраст, в третьем — возраст.
Объявите тип структуры с тремя полями: имя, адрес и возраст. Создайте массив этого типа структуры, где каждый элемент является объектом структуры, имеющим имя, адрес и возраст.
Первый подход неэффективен. В подобных сценариях структуры более удобны.
Синтаксис объявления структуры следующий:
type structname struct { variable_1 variable_1_type variable_2 variable_2_type variable_n variable_n_type }
Пример объявления структуры:
type emp struct { name string address string age int }
Здесь создается новый определяемый пользователем тип с именем emp. Теперь вы можете создавать переменные типа emp, используя синтаксис
var variable_name struct_name
В качестве примера можно
var empdata1 emp
Вы можете установить значения для empdata1 как
empdata1.name = "John" empdata1.address = "Street-1, Bangalore" empdata1.age = 30
Вы также можете создать структурную переменную и присвоить значения с помощью
empdata2 := emp{"Raj", "Building-1, Delhi", 25}
Здесь вам необходимо поддерживать порядок элементов. Радж будет сопоставлен с именем, следующий элемент — с адресом, а последний — с возрастом.
Выполните код ниже
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //function which accepts variable of emp type and prints name property func display(e emp) { fmt.Println(e.name) } func main() { // declares a variable, empdata1, of the type emp var empdata1 emp //assign values to members of empdata1 empdata1.name = "John" empdata1.address = "Street-1, London" empdata1.age = 30 //declares and assign values to variable empdata2 of type emp empdata2 := emp{"Raj", "Building-1, Paris", 25} //prints the member name of empdata1 and empdata2 using display function display(empdata1) display(empdata2) }
Результат
John Raj
Методы (не функции)
Метод — это функция с аргументом-получателем. Archiструктурно он находится между ключевым словом func и именем метода. Синтаксис метода
func (variable variabletype) methodName(parameter1 paramether1type) { }
Давайте преобразуем приведенный выше пример программы для использования методов вместо функций.
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //Declaring a function with receiver of the type emp func(e emp) display() { fmt.Println(e.name) } func main() { //declaring a variable of type emp var empdata1 emp //Assign values to members empdata1.name = "John" empdata1.address = "Street-1, Lodon" empdata1.age = 30 //declaring a variable of type emp and assign values to members empdata2 := emp { "Raj", "Building-1, Paris", 25} //Invoking the method using the receiver of the type emp // syntax is variable.methodname() empdata1.display() empdata2.display() }
Go не является объектно-ориентированным языком и в нем нет понятия класса. Методы дают представление о том, что вы делаете в объектно-ориентированных программах, где функции класса вызываются с использованием синтаксиса имя_объекта.имя_функции().
совпадение
Go поддерживает одновременное выполнение задач. Это означает, что Go может выполнять несколько задач одновременно. Это отличается от концепции параллелизма. При параллелизме задача разбивается на небольшие подзадачи и выполняется параллельно. Но в параллельном режиме несколько задач выполняются одновременно. Параллелизм достигается в Go с помощью Goroutines и Channels.
Горутины
Горутина — это функция, которая может выполняться одновременно с другими функциями. Обычно при вызове функции управление передается вызываемой функции, а после завершения ее выполнения управление возвращается вызывающей функции. Затем вызывающая функция продолжает свое выполнение. Вызывающая функция ожидает завершения выполнения вызванной функции, прежде чем продолжить выполнение остальных операторов.
Но в случае с горутиной вызывающая функция не будет ждать завершения выполнения вызванной функции. Он продолжит выполняться со следующими операторами. В программе может быть несколько горутин.
Кроме того, основная программа закроется после завершения выполнения своих операторов и не будет ждать завершения вызванных горутин.
Горутина вызывается с использованием ключевого слова go, за которым следует вызов функции.
Пример
go add(x,y)
Вы поймете горутины на приведенных ниже примерах Golang. Выполните приведенную ниже программу
package main import "fmt" func display() { for i:=0; i<5; i++ { fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() //The main() continues without waiting for display() for i:=0; i<5; i++ { fmt.Println("In main") } }
Выход будет
In main In main In main In main In main
Здесь основная программа завершила выполнение еще до запуска горутины. display() — это горутина, которая вызывается с использованием синтаксиса
go function_name(parameter list)
В приведенном выше коде функция main() не ждет завершения выполнения функции display(), а функция main() завершила свое выполнение до того, как функция display() выполнила свой код. Таким образом, оператор печати внутри display() не был напечатан.
Теперь мы модифицируем программу, чтобы она также выводила операторы из display(). Мы добавляем задержку в 2 секунды в цикл for функции main() и задержку в 1 секунду в цикл for display().
package main import "fmt" import "time" func display() { for i:=0; i<5; i++ { time.Sleep(1 * time.Second) fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() for i:=0; i<5; i++ { time.Sleep(2 * time.Second) fmt.Println("In main") } }
Результат будет чем-то похож на
In display In main In display In display In main In display In display In main In main In main
Здесь вы можете видеть, что оба цикла выполняются перекрывающимся образом из-за одновременного выполнения.
Каналы
Каналы — это способ взаимодействия функций друг с другом. Его можно рассматривать как среду, в которой одна подпрограмма размещает данные и к которой обращается другая подпрограмма на сервере Golang.
Канал может быть объявлен с помощью синтаксиса
channel_variable := make(chan datatype)
Пример:
ch := make(chan int)
Вы можете отправить данные в канал, используя синтаксис
channel_variable <- variable_name
Пример
ch <- x
Вы можете получать данные из канала, используя синтаксис
variable_name := <- channel_variable
Пример
y := <- ch
В приведенных выше примерах горутины на языке Go вы видели, что основная программа не ждет горутин. Но это не тот случай, когда задействованы каналы. Предположим, что горутина отправляет данные в канал, main() будет ждать оператора, получающего данные канала, пока не получит данные.
Вы увидите это в примерах языка Go ниже. Сначала напишите обычную горутину и посмотрите на ее поведение. Затем измените программу, чтобы она использовала каналы, и посмотрите на поведение.
Выполните приведенную ниже программу
package main import "fmt" import "time" func display() { time.Sleep(5 * time.Second) fmt.Println("Inside display()") } func main() { go display() fmt.Println("Inside main()") }
Выход будет
Inside main()
Функция main() завершила выполнение и завершила работу до выполнения горутины. Таким образом, печать внутри display() не была выполнена.
Теперь измените приведенную выше программу, чтобы она использовала каналы, и посмотрите на поведение.
package main import "fmt" import "time" func display(ch chan int) { time.Sleep(5 * time.Second) fmt.Println("Inside display()") ch <- 1234 } func main() { ch := make(chan int) go display(ch) x := <-ch fmt.Println("Inside main()") fmt.Println("Printing x in main() after taking from channel:",x) }
Выход будет
Inside display() Inside main() Printing x in main() after taking from channel: 1234
Вот что происходит: main() при достижении x := <-ch будет ждать данных на канале ch. Display() ожидает 5 секунд, а затем передает данные в канал ch. Функция main() при получении данных из канала разблокируется и продолжает свое выполнение.
Отправитель, отправляющий данные в канал, может сообщить получателям, что данные больше не будут добавляться в канал, закрыв канал. В основном это используется, когда вы используете цикл для передачи данных в канал. Канал можно закрыть с помощью
close(channel_name)
А на стороне получателя можно проверить, закрыт ли канал, используя дополнительную переменную при извлечении данных из канала с помощью
variable_name, status := <- channel_variable
Если статус True, это означает, что вы получили данные из канала. Если false, это означает, что вы пытаетесь прочитать из закрытого канала.
Вы также можете использовать каналы для связи между горутинами. Необходимо использовать 2 горутины — одна отправляет данные в канал, а другая получает данные из канала. Смотрите программу ниже
package main import "fmt" import "time" //This subroutine pushes numbers 0 to 9 to the channel and closes the channel func add_to_channel(ch chan int) { fmt.Println("Send data") for i:=0; i<10; i++ { ch <- i //pushing data to channel } close(ch) //closing the channel } //This subroutine fetches data from the channel and prints it. func fetch_from_channel(ch chan int) { fmt.Println("Read data") for { //fetch data from channel x, flag := <- ch //flag is true if data is received from the channel //flag is false when the channel is closed if flag == true { fmt.Println(x) }else{ fmt.Println("Empty channel") break } } } func main() { //creating a channel variable to transport integer values ch := make(chan int) //invoking the subroutines to add and fetch from the channel //These routines execute simultaneously go add_to_channel(ch) go fetch_from_channel(ch) //delay is to prevent the exiting of main() before goroutines finish time.Sleep(5 * time.Second) fmt.Println("Inside main()") }
Здесь есть две подпрограммы: одна передает данные в канал, а другая печатает данные в канал. Функция add_to_channel складывает числа от 2 до 0 и закрывает канал. Одновременно функция fetch_from_channel ожидает
x, flag := <- ch и как только данные станут доступны, он их распечатает. Он завершается, когда флаг становится ложным, что означает, что канал закрыт.
Ожидание в main() задано для предотвращения выхода из main() до тех пор, пока горутины не завершат выполнение.
Выполните код и посмотрите результат как
Read data Send data 0 1 2 3 4 5 6 7 8 9 Empty channel Inside main()
Выберите
Select можно рассматривать как оператор переключения, который работает с каналами. Здесь операторы случая будут операцией канала. Обычно каждый оператор случая будет попыткой чтения из канала. Когда какой-либо из случаев готов (канал прочитан), выполняется оператор, связанный с этим случаем. Если готовы несколько случаев, он выберет случайный. У вас может быть кейс по умолчанию, который выполняется, если ни один из кейсов не готов.
Давайте посмотрим код ниже
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements wait for data in the chan1 or chan2. //chan2 gets data first since the delay is only 2 sec in data2(). //So the second case will execute and exits the select block select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) } }
Выполнение вышеуказанной программы даст результат:
from data2()
Здесь оператор выбора ожидает доступности данных в любом из каналов. data2() добавляет данные в канал после 2-секундного ожидания, что приведет к выполнению второго случая.
Добавьте регистр по умолчанию к выбору в той же программе и посмотрите результат. Здесь, при достижении блока выбора, если ни в одном случае на канале нет готовых данных, он выполнит блок по умолчанию, не дожидаясь, пока данные станут доступны на каком-либо канале.
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements check for data in chan1 or chan2. //But data is not available (both routines have a delay of 2 and 4 sec) //So the default block will be executed without waiting for data in channels. select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) default: fmt.Println("Default case executed") } }
Эта программа выдаст результат:
Default case executed
Это связано с тем, что при достижении блока выбора ни один канал не имел данных для чтения. Итак, выполняется случай по умолчанию.
Mutex
Мьютекс — это сокращенная форма взаимного исключения. Мьютекс используется, когда вы не хотите, чтобы к ресурсу обращались несколько подпрограмм одновременно. У Mutex есть 2 метода — Lock и Unlock. Мьютекс содержится в пакете синхронизации. Итак, вам нужно импортировать пакет синхронизации. Операторы, которые должны выполняться взаимоисключающе, могут быть помещены внутри mutex.Lock() и mutex.Unlock().
Давайте изучим мьютекс на примере, который подсчитывает количество выполнений цикла. В этой программе мы ожидаем, что подпрограмма выполнит цикл 10 раз, и счетчик будет сохранен в сумме. Вы вызываете эту подпрограмму 3 раза, поэтому общее количество должно быть 30. Число сохраняется в глобальной переменной count.
Во-первых, вы запускаете программу без мьютекса.
package main import "fmt" import "time" import "strconv" import "math/rand" //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
Посмотреть результат
Count after i=1 Count: 11 Count after i=3 Count: 12 Count after i=2 Count: 13 Final Count: 13
Результат может быть другим при его выполнении, но окончательный результат не будет равен 30.
Здесь происходит следующее: 3 горутины пытаются увеличить количество циклов, хранящееся в переменной count. Предположим, что в данный момент count равен 5, а goroutine1 собирается увеличить счетчик до 6. Основные шаги включают в себя:
Скопировать счетчик во временную
Увеличение температуры
Температура хранения обратно для подсчета
Предположим, вскоре после выполнения шага 3 с помощью goroutine1; другая горутина может иметь старое значение, скажем, 3 выполняет вышеуказанные шаги и сохраняет 4 обратно, что неверно. Этого можно избежать, используя мьютекс, который заставляет другие процедуры ждать, когда одна из них уже использует переменную.
Теперь вы запустите программу с мьютексом. Здесь вышеупомянутые 3 шага выполняются во мьютексе.
package main import "fmt" import "time" import "sync" import "strconv" import "math/rand" //declare a mutex instance var mu sync.Mutex //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) //lock starts here mu.Lock() temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp //lock ends here mu.Unlock() } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
Теперь вывод будет
Count after i=3 Count: 21 Count after i=2 Count: 28 Count after i=1 Count: 30 Final Count: 30
Здесь мы получаем ожидаемый результат в качестве окончательного результата. Потому что операторы чтения, увеличения и обратной записи счетчика выполняются во мьютексе.
Обработка ошибок
Ошибки — это ненормальные состояния, такие как закрытие файла, который не открыт, открытие файла, который не существует и т. д. Функции обычно возвращают ошибки в качестве последнего возвращаемого значения.
В приведенном ниже примере объясняется больше об ошибке.
package main import "fmt" import "os" //function accepts a filename and tries to open it. func fileopen(name string) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { fmt.Println(er) return }else{ fmt.Println("file opened", f.Name()) } } func main() { fileopen("invalid.txt") }
Выходной сигнал будет:
open /invalid.txt: no such file or directory
Здесь мы попытались открыть несуществующий файл, и он вернул ошибку в переменную er. Если файл действителен, то ошибка будет равна нулю.
Пользовательские ошибки
Используя эту функцию, вы можете создавать собственные ошибки. Это делается с помощью New() пакета ошибок. Мы перепишем приведенную выше программу, чтобы использовать собственные ошибки.
Запустите приведенную ниже программу
package main import "fmt" import "os" import "errors" //function accepts a filename and tries to open it. func fileopen(name string) (string, error) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { //created a new error object and returns it return "", errors.New("Custom error message: File name is wrong") }else{ return f.Name(),nil } } func main() { //receives custom error or nil after trying to open the file filename, error := fileopen("invalid.txt") if error != nil { fmt.Println(error) }else{ fmt.Println("file opened", filename) } }
Выходной сигнал будет:
Custom error message:File name is wrong
Здесь area() возвращает площадь квадрата. Если входное значение меньше 1, то area() возвращает сообщение об ошибке.
Чтение файлов
Файлы используются для хранения данных. Go позволяет нам читать данные из файлов
Сначала создайте файл data.txt в вашем текущем каталоге с указанным ниже содержимым.
Line one Line two Line three
Теперь запустите приведенную ниже программу, чтобы увидеть, как она печатает содержимое всего файла в качестве вывода.
package main import "fmt" import "io/ioutil" func main() { data, err := ioutil.ReadFile("data.txt") if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
Здесь данные, err := ioutil.ReadFile("data.txt") считывает данные и возвращает последовательность байтов. При печати он преобразуется в строковый формат.
Запись файлов
Вы увидите это с помощью программы
package main import "fmt" import "os" func main() { f, err := os.Create("file1.txt") if err != nil { fmt.Println(err) return } l, err := f.WriteString("Write Line one") if err != nil { fmt.Println(err) f.Close() return } fmt.Println(l, "bytes written") err = f.Close() if err != nil { fmt.Println(err) return } }
Здесь создается файл test.txt. Если файл уже существует, его содержимое усекается. Writeline() используется для записи содержимого в файл. После этого вы закрыли файл с помощью Close().
Шпаргалка
В этом уроке Go мы рассмотрели,
Тема | Описание | Синтаксис |
---|---|---|
Основные типы | Числовое, строковое, логическое значение | |
Переменные | Объявляйте и присваивайте значения переменным | var имя_переменной тип var имя_переменной тип = значение var имя_переменной1, имя_переменной2 = значение1, значение2 имя_переменной := значение |
Константы | Переменные, значение которых нельзя изменить после присвоения | константная переменная = значение |
Для петли | Выполнение операторов в цикле. | для выражения_инициализации; выражение_оценки; итерация_выражение{ // один или несколько операторов } |
Если еще | Это условное утверждение | если условие { // операторы_1 Else {} // операторы_2 } |
переключатель | Условный оператор с несколькими случаями | переключить выражение { значение случая_1: заявления_1 значение случая_2: заявления_2 значение случая_n: заявления_n по умолчанию: заявления_по умолчанию } |
массив | Именованная последовательность элементов одного типа фиксированного размера. | имя массива := [размер] тип {значение_0,значение_1,…,размер_значения-1} |
Ломтик | Часть или сегмент массива | var имя_фрагмента [] тип = имя_массива[начало:конец] |
функции | Блок операторов, выполняющий конкретную задачу | func имя_функции (тип параметра_1, тип параметра_n) return_type { //заявления } |
Packages | Используются для организации кода. Повышает читаемость кода и возможность повторного использования. | импортировать имя_пакета |
Перенести | Откладывает выполнение функции до тех пор, пока содержащая функция не завершит выполнение. | отложить имя_функции (список_параметров) |
Указатели | Сохраняет адрес памяти другой переменной. | var имя_переменной *тип |
Структура | Пользовательский тип данных, который сам содержит еще один элемент того же или другого типа. | введите имя структуры struct { переменная_1 переменная_1_тип переменная_2 переменная_2_тип переменная_n переменная_n_type } |
методы | Метод — это функция с аргументом-получателем. | func (тип переменной) имя_метода(список_параметров) { } |
Горутина | Функция, которая может выполняться одновременно с другими функциями. | перейти имя_функции (список_параметров) |
Канал | Способ взаимодействия функций друг с другом. Среда, на которую одна подпрограмма помещает данные и к которой обращается другая подпрограмма. | Объявляет: ch := make(chan int) Отправить данные в канал: переменная_канала <- имя_переменной Получить с канала: имя_переменной := <- переменная_канала |
Выберите | Оператор Switch, который работает на каналах. Операторы случая будут операцией канала. Когда какой-либо канал готов к данным, выполняется оператор, связанный с этим случаем. | Выбрать { случай x := <-chan1: fmt.Println(x) случай y := <-chan2: fmt.Println(y) } |
Mutex | Мьютекс используется, когда вы не хотите, чтобы к ресурсу обращались несколько подпрограмм одновременно. У Mutex есть 2 метода — Lock и Unlock. | мьютекс.Lock() //заявления мьютекс.Разблокировать(). |
Читать файлы | Считывает данные и возвращает последовательность байтов. | Данные, ошибка: = ioutil.ReadFile(имя файла) |
Записать файл | Записывает данные в файл | л, ошибка:= f.WriteString(text_to_write) |