Учебники

Учебник Go

Что такое го?

Go (также известный как Golang) – это язык программирования с открытым исходным кодом, разработанный Google. Это статически скомпилированный язык. Go поддерживает параллельное программирование, то есть позволяет запускать несколько процессов одновременно. Это достигается с помощью каналов, подпрограмм и т. Д. Go имеет сборку мусора, которая сама выполняет управление памятью и обеспечивает отложенное выполнение функций.

Как скачать и установить GO

Шаг 1) Перейдите на https://golang.org/dl/ . Загрузите бинарный файл для вашей ОС.

Шаг 2) Дважды щелкните установщик и нажмите «Выполнить».

Шаг 3) Нажмите Далее

Шаг 4) Выберите папку для установки и нажмите Далее.

Шаг 5) Нажмите Finish после завершения установки.

Шаг 6) После завершения установки вы можете проверить это, открыв терминал и набрав

go version

Это покажет версию Go установлен

Ваша программа First Go

Создайте папку с именем 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

Теперь давайте обсудим вышеуказанную программу.

основной пакет – каждая программа go должна начинаться с имени пакета. Go позволяет нам использовать пакеты в других программах go и, следовательно, поддерживает повторное использование кода. Выполнение программы go начинается с кода внутри пакета с именем main.

import fmt – импортирует пакет fmt. Этот пакет реализует функции ввода / вывода.

func main () – это функция, с которой начинается выполнение программы. Основная функция всегда должна быть помещена в основной пакет. Под main (), вы можете написать код внутри {}.

fmt.Println – это напечатает текст на экране с помощью функции Println fmt.

Примечание. В следующих разделах, когда вы упоминаете выполнить / запустить код, это означает сохранить код в файл с расширением .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 реальных и мнимых частей.

String types – Представляет последовательность байтов (символов). Вы можете выполнять различные операции со строками, такие как конкатенация строк, извлечение подстроки и т. Д.

Булевы типы. Представляет 2 значения: true или false.

переменные

Переменные указывают на область памяти, в которой хранится какое-то значение. Параметр type (в приведенном ниже синтаксисе) представляет тип значения, которое можно сохранить в ячейке памяти.

Переменная может быть объявлена ​​с использованием синтаксиса

    var <variable_name> <type>

После того, как вы объявите переменную типа, вы можете присвоить переменную любому значению этого типа.

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

    var <variable_name> <type> = <value>

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

    var <variable_name> = <value>

Кроме того, вы можете объявить несколько переменных с синтаксисом

    var <variable_name1>, <variable_name2>  = <value1>, <value2>

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

 
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 также предоставляет простой способ объявления переменных со значением, опуская ключевое слово 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 run assign.go, чтобы увидеть результат как

./assign.go:7:4: no new variables on left side of :=		

Переменные, объявленные без начального значения, будут иметь 0 для числовых типов, false для Boolean и пустую строку для строк

Константы

Постоянные переменные – это те переменные, значение которых нельзя изменить после присвоения. Константа в 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

Loops

Циклы используются для многократного выполнения блока операторов в зависимости от условия. Большинство языков программирования предоставляют 3 типа циклов – для while, а для while. Но Go поддерживает только цикл.

Синтаксис цикла for

for initialisation_expression; evaluation_expression; iteration_expression{
   // one or more statement
}

Выражение initialisation_expression выполняется первым (и только один раз).

Затем оценивается выражение_производства, и если оно истинно, выполняется код внутри блока.

Идентификатор iteration_expression выполняется, и выражение_класса вычисляется снова. Если это правда, блок операторов выполняется снова. Это будет продолжаться до тех пор, пока expression_expression не станет ложным.

Скопируйте приведенную ниже программу в файл и выполните ее, чтобы увидеть числа циклической печати от 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 condition{
// statements_1
}else{
// statements_2
}

Здесь условие оценивается, и если оно истинно, будут выполнены операторы_1, иначе будут выполнены операторы_2.

Вы можете использовать оператор if без других также. Вы также можете приковать если еще заявления. Приведенные ниже программы объяснят больше, если еще.

Выполните следующую программу. Он проверяет, меньше ли число х, чем 10. Если это так, он напечатает «х меньше, чем 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, инструкция внутри условия блока не будет выполнена.

Теперь смотрите программу ниже. У нас есть блок 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

Теперь мы увидим программу с несколькими блоками if else (с цепочкой if else). Выполните приведенный ниже пример. Он проверяет, является ли число меньше 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)

Выполните приведенный ниже пример, чтобы понять массив

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]) 
}

Output

Two
3
[One Two Three]
[1 2 3 4 5]
5

Slice

A slice is a portion or segment of an array. Or it is a view or partial view of an underlying array to which it points. You can access the elements of a slice using the slice name and index number just as you do in an array. You cannot change the length of an array, but you can change the size of a slice.

Contents of a slice are actually the pointers to the elements of an array. It means if you change any element in a slice, the underlying array contents also will be affected.

The syntax for creating a slice is

var slice_name [] type = array_name[start:end]

This will create a slice named slice_name from an array named array_name with the elements at the index start to end-1.

Execute the below program. The program will create a slice from the array and print it. Also, you can see that modifying the contents in the slice will modify the actual array.

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)
}

This will print result as

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]

There are certain functions which you can apply on slices

len(slice_name) – returns the length of the slice

append(slice_name, value_1, value_2) – It is used to append value_1 and value_2 to an existing slice.

append(slice_nale1,slice_name2…) – appends slice_name2 to slice_name1

Выполните следующую программу.

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
}

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

Давайте запустим следующий пример. Здесь функция с именем 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

пакеты

Пакеты используются для организации кода. В большом проекте невозможно написать код в одном файле. Перейти позволяют нам организовать код под разные пакеты. Это повышает читаемость кода и возможность его повторного использования. Исполняемая программа Go должна содержать пакет с именем main, и выполнение программы начинается с функции с именем main. Вы можете импортировать другие пакеты в нашей программе, используя синтаксис

import package_name

Мы увидим и обсудим, как создавать и использовать пакеты в следующем примере.

Шаг 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 () вы можете увидеть сумму шагов: = calculation.Do_add (x, y). Это означает, что вы вызываете функцию Do_add из расчета пакета.

Шаг 2) Сначала вы должны создать расчет пакета внутри папки с тем же именем в папке src the go. Установленный путь go можно найти в переменной PATH.

Для Mac найдите путь, выполнив echo $ PATH

Таким образом, путь / usr / local / go

Для окон найдите путь, выполнив 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, запустите package_example.go. Выход будет Сумма 25.

Обратите внимание, что имя функции Do_add начинается с заглавной буквы. Это происходит потому, что в Go, если имя функции начинается с заглавной буквы, это означает, что другие программы могут видеть (получать к нему доступ), иначе другие программы не могут получить к нему доступ. Если бы имя функции было do_add, то вы бы получили ошибку

не может ссылаться на неэкспортированное имя

Откладывать и укладывать

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

Давайте узнаем это на примере:

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 ()).

Отсрочка стека использует несколько операторов отсрочки. Предположим, у вас есть несколько операторов отсрочки внутри функции. 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.

Выполните приведенную ниже программу, чтобы отобразить значение переменной и адрес этой переменной

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 массива: в одном массиве хранятся имена сотрудников, в одном – возраст, а в третьем – возраст.

Объявите тип структуры с 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

Методы (не функции)

Метод – это функция с аргументом получателя. Архитектурно, это между ключевым словом 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 не является объектно-ориентированным языком и не имеет понятия класса. Методы дают представление о том, что вы делаете в объектно-ориентированных программах, где функции класса вызываются с использованием синтаксиса objectname.functionname ()

совпадение

Go поддерживает одновременное выполнение задач. Это означает, что Go может выполнять несколько задач одновременно. Это отличается от концепции параллелизма. Параллельно задача разбивается на маленькие подзадачи и выполняется параллельно. Но одновременно, несколько задач выполняются одновременно. Параллелизм достигается в Go с использованием Goroutines и Channels.

Goroutines

Goroutine – это функция, которая может работать одновременно с другими функциями. Обычно, когда функция вызывается, элемент управления передается в вызываемую функцию, и после завершения ее выполнения управление возвращается к вызывающей функции. Вызывающая функция затем продолжает свое выполнение. Вызывающая функция ожидает, пока вызванная функция завершит выполнение, прежде чем продолжить работу с остальными операторами.

Но в случае goroutine, вызывающая функция не будет ждать завершения вызванной функции. Он будет продолжать выполняться со следующими инструкциями. Вы можете иметь несколько программ в программе.

Кроме того, основная программа завершит работу, как только завершит выполнение своих операторов, и не будет ждать завершения вызванных процедур.

Goroutine вызывается с помощью ключевого слова go, за которым следует вызов функции.

пример

go add(x,y)

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

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 () выполнит свой код. Таким образом, оператор print внутри display () не был напечатан.

Теперь мы модифицируем программу, чтобы также печатать операторы из display (). Мы добавляем задержку в 2 секунды в цикл for функции main () и задержку в 1 секунду в цикле for дисплея ().

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

Здесь вы можете видеть, что оба цикла выполняются перекрывающимся образом из-за одновременного выполнения.

каналы

Каналы – это способ взаимодействия функций друг с другом. Его можно рассматривать как средство, в котором одна процедура размещает данные и к которой обращается другая процедура.

Канал может быть объявлен с синтаксисом

channel_variable := make(chan datatype)

Пример:

	ch := make(chan int)

Вы можете отправить данные на канал, используя синтаксис

channel_variable <- variable_name

пример

    ch <- x

Вы можете получать данные из канала, используя синтаксис

    variable_name := <- channel_variable

пример

   y := <- ch

В приведенных выше примерах goroutine вы видели, что основная программа не ждет goroutine. Но это не тот случай, когда каналы вовлечены. Предположим, что если программа отправляет данные в канал, функция main () будет ожидать оператора, получающего данные канала, пока не получит данные.

Вы увидите это в примере ниже. Сначала напишите нормальную программу и посмотрите на поведение. Затем измените программу, чтобы использовать каналы и посмотреть поведение.

Выполните следующую программу

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 () завершила выполнение и завершила работу до выполнения программы. Таким образом, печать внутри дисплея () не была выполнена.

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

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. Дисплей () ожидает 5 секунд, а затем передает данные в канал ch. Функция main () при получении данных из канала разблокируется и продолжает выполнение.

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

close(channel_name)

И на стороне приемника можно проверить, закрыт ли канал, используя дополнительную переменную, при извлечении данных из канала, используя

variable_name, status := <- channel_variable

Если статус Истинный, это означает, что вы получили данные с канала. Если 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()")
}

Здесь есть 2 подпрограммы: одна отправляет данные в канал, а другая печатает данные в канал. Функция add_to_channel добавляет числа от 0 до 9 и закрывает канал. Одновременно функция 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()

Выбрать

Выбор можно рассматривать как оператор переключения, который работает на каналах. Здесь операторы case будут операцией канала. Обычно, каждый случай заявления будет считан попыткой чтения из канала. Когда любое из дел готово (канал читается), выполняется оператор, связанный с этим делом. Если несколько случаев готовы, он выберет случайный. Вы можете иметь дело по умолчанию, которое выполняется, если ни одно из дел не готово.

Давайте посмотрим на код ниже

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()

Здесь оператор select ожидает данных, которые будут доступны в любом из каналов. 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 используется, когда вы не хотите, чтобы к ресурсу обращались одновременно несколько подпрограмм. У Mutex есть 2 метода – блокировка и разблокировка. Мутекс содержится в пакете синхронизации. Итак, вы должны импортировать пакет синхронизации. Операторы, которые должны выполняться взаимно исключительно, могут быть помещены в 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. Предположим, что в данный момент счет равен 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)
    }  
}

The output will be:

Custom error message:File name is wrong

Here the area() returns the area of a square. If the input is less than 1 then area() returns an error message.

Reading files

Files are used to store data. Go allows us to read data from the files

First create a file, data.txt, in your present directory with the below content.

Line one
Line two
Line three

Now run the below program to see it prints the contents of the entire file as output

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))
}

Here the data, err := ioutil.ReadFile(“data.txt”) reads the data and returns a byte sequence. While printing it is converted to string format.

Writing files

You will see this with a program

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 мы рассмотрели

Тема Описание Синтаксис
Основные типы Числовой, строковый, bool
переменные Объявите и присвойте значения переменным var имя_переменной тип var имя_переменной тип = значение var имя_переменной1, имя_переменной2 = значение1, значение2 имя_переменной: = значение
Константы Переменные, значение которых нельзя изменить после присвоения константная переменная = значение
Для петли Выполнить операторы в цикле. для инициализации_экспрессия; evaluation_expression; iteration_expression {// один или несколько операторов}
Если еще Это условное утверждение условие if {// Statement_1} else {// Statement_2}
переключатель Условное утверждение с несколькими случаями Выражение переключателя {case value_1: Statement_1 Case Value_2: Statement_2 Case Value_n: Statement_n По умолчанию: Statement_default}
массив Фиксированный размер именованной последовательности элементов одного типа имя_прибора: = [размер] тип {значение_0, значение_1,…, значение_размер-1}
Кусочек Часть или сегмент массива var slice_name [] type = array_name [start: end]
функции Блок утверждений, который выполняет определенную задачу func имя_функции (тип параметра_1, тип параметра_n) return_type {// операторы}
пакеты Используются для организации кода. Увеличивает читаемость и возможность повторного использования кода import package_nam
Перенести Откладывает выполнение функции, пока содержащая функция не закончит выполнение defer имя_функции (список_параметров)
указатели Хранит адрес памяти другой переменной. var variable_name * type
Структура Определяемый пользователем тип данных, который сам содержит еще один элемент того же или другого типа тип structname struct {variable_1 variable_1_type variable_2 variable_2_type variable_n variable_n_type}
методы Метод – это функция с аргументом получателя. func (variable variabletype) methodName (parameter_list) {}
Goroutine Функция, которая может работать одновременно с другими функциями. перейти имя_функции (список_параметров)
канал Способ для функций общаться друг с другом. Среда, на которую одна подпрограмма помещает данные и доступ к которой осуществляется другой подпрограммой. Объявление: ch: = make (chan int) Отправка данных на канал: channel_variable <- имя переменной. Получение от канала: имя переменной: = <- переменная канала.
Выбрать Переключатель оператора, который работает на каналах. Операторы case будут операцией канала. Когда какой-либо канал готов с данными, выполняется оператор, связанный с этим случаем. выберите {case x: = <-chan1: fmt.Println (x) case y: = <-chan2: fmt.Println (y)}
Mutex Mutex используется, когда вы не хотите, чтобы к ресурсу обращались одновременно несколько подпрограмм. Mutex имеет 2 метода – блокировка и разблокировка mutex.Lock () // операторы mutex.Unlock ().
Читать файлы Читает данные и возвращает последовательность байтов. Данные, err: = ioutil.ReadFile (имя файла)
Написать файл Записывает данные в файл l, err: = f.WriteString (text_to_write)