Samouczek Golang: Naucz się języka programowania Go dla początkujących
Co to jest Go?
Go (znany również jako Golang) jest językiem programowania open source opracowanym przez Google. Jest to statycznie typowany kompilowany język. Go obsługuje programowanie współbieżne, tzn. pozwala na jednoczesne uruchamianie wielu procesów. Jest to możliwe dzięki kanałom, goroutines itp. Język Go ma zbieranie śmieci, które samo zarządza pamięcią i pozwala na opóźnione wykonywanie funkcji.
W tym samouczku Learn Go Language nauczymy się wszystkich podstaw języka Golang.
Jak pobrać i zainstalować GO
Krok 1) Iść do https://golang.org/dl/. Pobierz plik binarny dla swojego systemu operacyjnego.
Krok 2) Double kliknij instalator i kliknij Uruchom.
Krok 3) Kliknij Dalej
Krok 4) Wybierz folder instalacyjny i kliknij Dalej.
Krok 5) Kliknij Zakończ po zakończeniu instalacji.
Krok 6) Po zakończeniu instalacji możesz to sprawdzić, otwierając terminal i wpisując
go version
Spowoduje to wyświetlenie zainstalowanej wersji go
Twój program First Go – Go Hello World!
Utwórz folder o nazwie studyGo. W tym samouczku języka Go utworzymy nasze programy w tym folderze. Pliki Go są tworzone z rozszerzeniem .wybrać się. Możesz uruchamiać programy Go, używając składni
go run <filename>
Utwórz plik o nazwie First.go, dodaj do niego poniższy kod i zapisz
package main import ("fmt") func main() { fmt.Println("Hello World! This is my first Go program\n") }
Przejdź do tego folderu w terminalu. Uruchom program za pomocą polecenia
idź najpierw pobiegnij. idź
Możesz zobaczyć wydruk wyjściowy
Hello World! This is my first Go program
Omówmy teraz powyższy program.
package main – Każdy program Go Language powinien zaczynać się od nazwy pakietu. Go pozwala nam używać pakietów w innych programach go, a tym samym wspiera ponowne wykorzystanie kodu. Wykonanie programu Go rozpoczyna się od kodu znajdującego się w pakiecie o nazwie main.
import fmt – importuje pakiet fmt. Pakiet ten implementuje funkcje we/wy.
func main() – Jest to funkcja, od której rozpoczyna się wykonywanie programu. Funkcja główna powinna być zawsze umieszczona w pakiecie głównym. Pod funkcją main() możesz wpisać kod wewnątrz { }.
fmt.Println – Spowoduje wydrukowanie tekstu na ekranie za pomocą funkcji Println fmt.
Uwaga: w poniższych sekcjach tego samouczka Go, gdy wspominasz o wykonaniu/uruchomieniu kodu, oznacza to zapisanie kodu w pliku z rozszerzeniem .go i uruchomienie go przy użyciu składni
go run <filename>
Typy danych
Typy (typy danych) reprezentują typ wartości przechowywanej w zmiennej, typ wartości zwracanej przez funkcję itp.
Istnieją trzy podstawowe typy w języku Go
Typy liczbowe – Reprezentują wartości liczbowe, w tym liczby całkowite, zmiennoprzecinkowe i wartości zespolone. Różne typy liczbowe to:
int8 – 8-bitowe liczby całkowite ze znakiem.
int16 – 16-bitowe liczby całkowite ze znakiem.
int32 – 32-bitowe liczby całkowite ze znakiem.
int64 – 64-bitowe liczby całkowite ze znakiem.
uint8 – 8-bitowe liczby całkowite bez znaku.
uint16 – 16-bitowe liczby całkowite bez znaku.
uint32 – 32-bitowe liczby całkowite bez znaku.
uint64 – 64-bitowe liczby całkowite bez znaku.
float32 – 32-bitowe liczby zmiennoprzecinkowe.
float64 – 64-bitowe liczby zmiennoprzecinkowe.
complex64 – ma float32 części rzeczywistej i urojonej.
complex128 – ma float32 części rzeczywistej i urojonej.
Rodzaje ciągów – Reprezentuje sekwencję bajtów (znaków). Możesz wykonywać różne operacje na ciągach, takie jak łączenie ciągów, wyodrębnianie podciągów itp.
Typy logiczne – Reprezentuje 2 wartości, prawdę lub fałsz.
Interfejs Golanga
Interfejs Golanga to zbiór sygnatur metod używanych przez Type do implementowania zachowania obiektów. Głównym celem interfejsu Golang jest dostarczanie sygnatur metod z nazwami, argumentami i typami zwracanych wartości. Zadeklarowanie i zaimplementowanie metody zależy od typu. Interfejs w języku Golang można zadeklarować za pomocą słowa kluczowego „interfejs”.
Zmienne
Zmienne wskazują lokalizację pamięci, w której przechowywana jest pewna wartość. Parametr typu (w poniższej składni) reprezentuje typ wartości, która może być przechowywana w lokalizacji pamięci.
Zmienną można zadeklarować za pomocą składni
var <variable_name> <type>
Po zadeklarowaniu zmiennej typu możesz przypisać zmienną do dowolnej wartości tego typu.
Możesz także nadać zmiennej wartość początkową podczas samej deklaracji, używając
var <variable_name> <type> = <value>
Jeśli zadeklarujesz zmienną z wartością początkową, wywnioskowaj typ zmiennej z typu przypisanej wartości. Możesz więc pominąć typ podczas deklaracji, używając składni
var <variable_name> = <value>
Możesz także zadeklarować wiele zmiennych za pomocą składni
var <variable_name1>, <variable_name2> = <value1>, <value2>
Poniższy program w tym samouczku Go zawiera kilka przykładów deklaracji zmiennych w języku 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) }
Dane wyjściowe będą
x: 3 y: 20 z: 50 i and j: 100 hello
Język Go zapewnia także łatwy sposób deklarowania wartości zmiennych poprzez pominięcie słowa kluczowego var using
<variable_name> := <value>
Zauważ, że użyłeś := zamiast =. Nie możesz użyć := tylko do przypisania wartości do zmiennej, która jest już zadeklarowana. := służy do deklarowania i przypisywania wartości.
Utwórz plik o nazwie assign.go z następującym kodem
package main import ("fmt") func main() { a := 20 fmt.Println(a) //gives error since a is already declared a := 30 fmt.Println(a) }
Wykonaj polecenie go run sign.go, aby zobaczyć wynik jako
./assign.go:7:4: no new variables on left side of :=
Zmienne zadeklarowane bez wartości początkowej będą miały wartość 0 dla typów numerycznych, wartość false dla typów logicznych i pusty ciąg znaków dla ciągów
Stałe
Zmienne stałe to te zmienne, których wartości nie można zmienić po przypisaniu. Stała w języku programowania Go jest deklarowana za pomocą słowa kluczowego „const”
Utwórz plik o nazwie constant.go i użyj następującego kodu
package main import ("fmt") func main() { const b =10 fmt.Println(b) b = 30 fmt.Println(b) }
Wykonaj idź, uruchom stałą.go, aby zobaczyć wynik jako
.constant.go:7:4: cannot assign to b
Przykłady pętli
Pętle służą do wielokrotnego wykonywania bloku instrukcji w oparciu o warunek. Większość języków programowania udostępnia 3 rodzaje pętli – for, while, do while. Ale język programowania Go obsługuje tylko pętlę for.
Składnia pętli for Golang jest następująca
for initialisation_expression; evaluation_expression; iteration_expression{ // one or more statement }
Wyrażenie_inicjalizacji jest wykonywane jako pierwsze (i tylko raz) w pętli for Golang.
Następnie oceniane jest wyrażenie_oceny i jeśli jest prawdziwe, wykonywany jest kod znajdujący się w bloku.
Wykonywany jest identyfikator iteracji_wyrażenia, a ocena_wyrażenia jest oceniana ponownie. Jeśli to prawda, blok instrukcji zostanie wykonany ponownie. Będzie to trwało, dopóki wyrażenie_oceny nie stanie się fałszywe.
Skopiuj poniższy program do pliku i uruchom go, aby zobaczyć pętlę for języka Go drukującą liczby od 1 do 5
package main import "fmt" func main() { var i int for i = 1; i <= 5; i++ { fmt.Println(i) } }
Wyjście to
1 2 3 4 5
Jeśli inaczej
Jeśli else jest instrukcją warunkową. Synaks jest
if condition{ // statements_1 }else{ // statements_2 }
Tutaj oceniany jest warunek i jeśli jest prawdziwy, wykonane zostaną instrukcje_1, w przeciwnym razie wykonane zostaną instrukcje_2.
Możesz użyć instrukcji if również bez else. Można także połączyć instrukcje if else. Poniższe programy wyjaśnią więcej na temat if else.
Wykonaj poniższy program. Sprawdza, czy liczba x jest mniejsza niż 10. Jeśli tak, wyświetli „x jest mniejsze niż 10”
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x < 10 fmt.Println("x is less than 10") } }
Tutaj, ponieważ wartość x jest większa niż 10, instrukcja if w warunku bloku nie zostanie wykonana.
Teraz spójrz na poniższy program. W tym samouczku dotyczącym języka programowania Go mamy blok else, który zostanie wykonany w przypadku niepowodzenia oceny 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") } }
Ten program wyświetli dane wyjściowe
x is greater than or equals 10
W tym samouczku Go zobaczymy program z wieloma blokami if else (połączonymi łańcuchowo if else). Wykonaj poniższy przykład Go. Sprawdza, czy liczba jest mniejsza niż 10, mieści się w przedziale 10-90 lub jest większa niż 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") } }
Tutaj najpierw warunek if sprawdza, czy x jest mniejsze niż 10 i tak nie jest. Sprawdza zatem, czy następny warunek (else if) mieści się w przedziale od 10 do 90, co również jest fałszywe. Następnie wykonuje blok w sekcji else, który daje wynik
x is greater than 90
Przełącznik
Switch to kolejna instrukcja warunkowa. Instrukcje Switch oceniają wyrażenie, a wynik jest porównywany z zestawem dostępnych wartości (przypadków). Po znalezieniu dopasowania wykonywane są instrukcje powiązane z tym dopasowaniem (przypadkiem). Jeśli nie zostanie znalezione żadne dopasowanie, nic nie zostanie wykonane. Możesz także dodać domyślny przypadek do przełączenia, który zostanie wykonany, jeśli nie zostaną znalezione żadne inne dopasowania. Składnia przełącznika jest następująca
switch expression { case value_1: statements_1 case value_2: statements_2 case value_n: statements_n default: statements_default }
Tutaj wartość wyrażenia jest porównywana z wartościami w każdym przypadku. Po znalezieniu dopasowania wykonywane są instrukcje powiązane z tym przypadkiem. Jeśli nie zostanie znalezione żadne dopasowanie, wykonywane są instrukcje z sekcji domyślnej.
Wykonaj poniższy program
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") } }
Otrzymasz dane wyjściowe jako
Sum is 3
Zmień wartość a i b na 3, a wynik będzie taki
Printing default
W przypadku można również umieścić wiele wartości, oddzielając je przecinkiem.
Tablice
Array reprezentuje stały rozmiar, nazwaną sekwencję elementów tego samego typu. Nie można mieć tablicy zawierającej zarówno liczby całkowite, jak i znaki. Po zdefiniowaniu rozmiaru nie można zmienić rozmiaru tablicy.
Składnia deklarowania tablicy jest następująca
var arrayname [size] type
Każdemu elementowi tablicy można przypisać wartość za pomocą składni
arrayname [index] = value
Indeks tablicy zaczyna się od 0 do rozmiaru-1.
Możesz przypisywać wartości do elementów tablicy podczas deklaracji, używając składni
arrayname := [size] type {value_0,value_1,…,value_size-1}
Możesz także zignorować parametr size podczas deklarowania tablicy z wartościami, zastępując size przez ... a kompilator znajdzie długość na podstawie liczby wartości. Składnia jest
arrayname := […] type {value_0,value_1,…,value_size-1}
Długość tablicy można znaleźć, korzystając ze składni
len(arrayname)
Wykonaj poniższy przykład Go, aby zrozumieć tablicę
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]) }
Wydajność
Two 3 [One Two Three] [1 2 3 4 5] 5
Funkcja krojenia i dołączania Golanga
Plasterek to część lub segment tablicy. Lub jest to widok lub częściowy widok podstawowej tablicy, na którą wskazuje. Dostęp do elementów plasterka można uzyskać, używając nazwy plasterka i numeru indeksu, tak samo jak w przypadku tablicy. Nie możesz zmienić długości tablicy, ale możesz zmienić rozmiar plasterka.
Zawartość plasterka jest w rzeczywistości wskaźnikami do elementów tablicy. To znaczy jeśli zmienisz dowolny element w plasterku, będzie to miało wpływ również na zawartość tablicy.
Składnia tworzenia plasterka jest następująca
var slice_name [] type = array_name[start:end]
Spowoduje to utworzenie plasterka o nazwie nazwa_plasterka z tablicy o nazwie nazwa_tablicy z elementami od początku do końca indeksu -1.
Teraz w tym samouczku Golang wykonamy poniższy program. Program utworzy wycinek z tablicy i wydrukuje go. Możesz także zobaczyć, że modyfikacja zawartości wycinka spowoduje modyfikację rzeczywistej tablicy.
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) }
Spowoduje to wydrukowanie wyniku jako
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]
Istnieją pewne funkcje, takie jak Golang len, Golang append, które można zastosować na plasterkach
len(nazwa_kawałka) – zwraca długość plasterka
append(nazwa_kawałka, wartość_1, wartość_2) – Dołączenie Golanga służy do dołączenia wartości_1 i wartości_2 do istniejącego plasterka.
append(kawałek_nale1,nazwa_kawałka2…) – dołącza nazwa_plasterka2 do nazwa_plasterku1
Uruchom następujący program.
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) }
Dane wyjściowe będą
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]
Program najpierw tworzy 2 plasterki i drukuje ich długość. Następnie dołączył jeden plasterek do drugiego, a następnie dołączył ciąg do powstałego plasterka.
Funkcje
Funkcja reprezentuje blok instrukcji, który wykonuje określone zadanie. Deklaracja funkcji podaje nam nazwę funkcji, typ zwracanej wartości i parametry wejściowe. Definicja funkcji reprezentuje kod zawarty w funkcji. Składnia deklarowania funkcji jest następująca
func function_name(parameter_1 type, parameter_n type) return_type { //statements }
Parametry i typy zwracane są opcjonalne. Możesz także zwrócić wiele wartości z funkcji.
Teraz w tym samouczku Golanga, uruchommy następujący przykład Golanga. Tutaj funkcja o nazwie calc zaakceptuje 2 liczby i wykona dodawanie i odejmowanie i zwróci obie wartości.
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) }
Dane wyjściowe będą
Sum 25 Diff 5
Pakiety
Pakiety służą do organizowania kodu. W dużym projekcie nie da się napisać kodu w jednym pliku. Język programowania Go pozwala nam organizować kod w różnych pakietach. Zwiększa to czytelność kodu i możliwość jego ponownego użycia. Wykonywalny program Go powinien zawierać pakiet o nazwie main, a wykonywanie programu rozpoczyna się od funkcji o nazwie main. Możesz zaimportować inne pakiety w naszym programie za pomocą składni
import package_name
W tym samouczku języka Go zobaczymy i omówimy, jak tworzyć i używać pakietów w poniższym przykładzie języka Go.
Krok 1) Utwórz plik o nazwie package_example.go i dodaj poniższy kod
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) }
W powyższym programie fmt jest pakietem, który język programowania Go udostępnia nam głównie do celów we/wy. Możesz także zobaczyć pakiet o nazwie obliczenie. Wewnątrz funkcji main() możesz zobaczyć sumę kroków := obliczenia.Do_add(x,y). Oznacza to, że wywołujesz funkcję Do_add z obliczenia pakietu.
Krok 2) Najpierw powinieneś utworzyć kalkulację pakietu w folderze o tej samej nazwie w folderze src. Zainstalowaną ścieżkę go można znaleźć w zmiennej PATH.
W przypadku komputerów Mac znajdź ścieżkę, wykonując echo $PATH
Zatem ścieżka to /usr/local/go
W przypadku systemu Windows znajdź ścieżkę, wykonując polecenie echo %GOROOT%
Tutaj ścieżka to C:\Go\
Krok 3) Przejdź do folderu src (/usr/local/go/src dla komputerów Mac i C:\Go\src dla systemów Windows). Teraz w kodzie nazwa pakietu to accounting. Go wymaga, aby pakiet został umieszczony w katalogu o tej samej nazwie w katalogu src. Utwórz katalog o nazwie accounting w folderze src.
Krok 4) Utwórz plik o nazwie calc.go (możesz podać dowolną nazwę, ale nazwa pakietu w kodzie ma znaczenie. Tutaj powinny być obliczenia) w katalogu obliczeń i dodaj poniższy kod
package calculation func Do_add(num1 int, num2 int)(int) { sum := num1 + num2 return sum }
Krok 5) Uruchom polecenie go install z katalogu obliczeń, który skompiluje plik calc.go.
Krok 6) Teraz wróć do package_example.go i uruchom go run package_example.go. Wynikiem będzie Suma 25.
Należy pamiętać, że nazwa funkcji Do_add zaczyna się od dużej litery. Dzieje się tak dlatego, że w Go, jeśli nazwa funkcji zaczyna się z dużej litery, oznacza to, że inne programy mogą ją zobaczyć (uzyskać do niej dostęp), w przeciwnym razie inne programy nie będą miały do niej dostępu. Gdyby nazwa funkcji brzmiała do_add , wystąpiłby błąd
nie może odwoływać się do niewyeksportowanej nazwy obliczeń.calc..
Odroczenie i układanie w stosy
Instrukcje Defer służą do odroczenia wykonania wywołania funkcji do czasu zakończenia wykonywania funkcji zawierającej instrukcję Defer.
Nauczmy się tego na przykładzie:
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()") }
Dane wyjściowe będą
Inside the main() Inside the sample()
W tym przypadku wykonanie próbki() jest odroczone do czasu zakończenia wykonywania otaczającej ją funkcji (main()).
Odroczenie stosowe wykorzystuje wiele instrukcji odroczenia. Załóżmy, że wewnątrz funkcji znajduje się wiele instrukcji odroczenia. Go umieszcza wszystkie odroczone wywołania funkcji na stosie, a gdy funkcja otaczająca powróci, funkcje ułożone w stos zostaną wykonane w Zamówienie „ostatnie weszło, pierwsze wyszło” (LIFO). Można to zobaczyć na poniższym przykładzie.
Wykonaj poniższy kod
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) }
Dane wyjściowe będą
4 3 2 1
Tutaj najpierw wykonywany jest kod wewnątrz funkcji main(), a następnie odroczone wywołania funkcji są wykonywane w odwrotnej kolejności, tj. 4, 3,2,1.
wskaźniki
Zanim wyjaśnimy wskaźniki, omówmy najpierw operator '&'. Operator '&' jest używany do uzyskania adresu zmiennej. Oznacza to, że '&a' wydrukuje adres pamięci zmiennej a.
W tym samouczku Golang uruchomimy poniższy program, aby wyświetlić wartość zmiennej i adres tej zmiennej
package main import "fmt" func main() { a := 20 fmt.Println("Address:",&a) fmt.Println("Value:",a) }
Wynik będzie
Address: 0xc000078008 Value: 20
Zmienna wskaźnikowa przechowuje adres pamięci innej zmiennej. Wskaźnik można zdefiniować za pomocą składni
var variable_name *type
Gwiazdka (*) oznacza, że zmienna jest wskaźnikiem. Więcej zrozumiesz wykonując poniższy program
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)}
Dane wyjściowe będą
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
Struktury
Struktura to typ danych zdefiniowany przez użytkownika, który sam zawiera jeszcze jeden element tego samego lub innego typu.
Korzystanie ze struktury jest procesem dwuetapowym.
Najpierw utwórz (zadeklaruj) typ struktury
Po drugie, utwórz zmienne tego typu do przechowywania wartości.
Struktury są używane głównie wtedy, gdy chcesz razem przechowywać powiązane dane.
Weź pod uwagę informację o pracowniku, która zawiera imię i nazwisko, wiek i adres. Można sobie z tym poradzić na 2 sposoby
Utwórz 3 tablice – jedna tablica przechowuje nazwiska pracowników, jedna wiek, a trzecia wiek.
Zadeklaruj typ struktury za pomocą 3 pól – nazwy, adresu i wieku. Utwórz tablicę tego typu struktury, w której każdy element jest obiektem struktury mającym nazwę, adres i wiek.
Pierwsze podejście nie jest skuteczne. W tego rodzaju scenariuszach struktury są wygodniejsze.
Składnia deklarowania struktury jest następująca
type structname struct { variable_1 variable_1_type variable_2 variable_2_type variable_n variable_n_type }
Przykładem deklaracji struktury jest
type emp struct { name string address string age int }
Tutaj tworzony jest nowy typ zdefiniowany przez użytkownika o nazwie emp. Teraz możesz tworzyć zmienne typu emp, używając składni
var variable_name struct_name
Przykładem jest
var empdata1 emp
Możesz ustawić wartości dla empdata1 jako
empdata1.name = "John" empdata1.address = "Street-1, Bangalore" empdata1.age = 30
Można także utworzyć zmienną struktury i przypisać wartości według
empdata2 := emp{"Raj", "Building-1, Delhi", 25}
Tutaj trzeba zachować kolejność elementów. Raj zostanie przypisany do imienia, następnego elementu do adresowania i ostatniego do wieku.
Wykonaj poniższy kod
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) }
Wydajność
John Raj
Metody (nie funkcje)
Metoda to funkcja posiadająca argument odbiorcy. Archiz technicznego punktu widzenia znajduje się pomiędzy słowem kluczowym func a nazwą metody. Składnia metody jest taka
func (variable variabletype) methodName(parameter1 paramether1type) { }
Przekonwertujmy powyższy przykładowy program tak, aby zamiast funkcji używał metod.
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 nie jest językiem obiektowym i nie ma w nim koncepcji klasy. Metody dają wyobrażenie o tym, co robisz w programach obiektowych, w których funkcje klasy są wywoływane przy użyciu składni nazwaobiektu.nazwafunkcji()
Konkurencja
Go obsługuje współbieżne wykonywanie zadań. Oznacza to, że Go może wykonywać wiele zadań jednocześnie. Różni się to od koncepcji paralelizmu. W paralelizmie zadanie jest dzielone na małe podzadania i wykonywane równolegle. Natomiast w przypadku współbieżności wiele zadań jest wykonywanych jednocześnie. Współbieżność jest osiągana w Go za pomocą Goroutines i Channels.
Gorutyny
Goroutine to funkcja, która może działać jednocześnie z innymi funkcjami. Zwykle po wywołaniu funkcji sterowanie zostaje przeniesione do wywoływanej funkcji, a po zakończeniu jej wykonywania kontrola powraca do funkcji wywołującej. Następnie funkcja wywołująca kontynuuje wykonywanie. Funkcja wywołująca czeka, aż wywołana funkcja zakończy wykonywanie, zanim przejdzie do pozostałych instrukcji.
Jednak w przypadku goroutine funkcja wywołująca nie będzie czekać na zakończenie wykonywania wywołanej funkcji. Będzie kontynuować wykonywanie z następnymi instrukcjami. W programie możesz mieć wiele goroutines.
Ponadto główny program zakończy działanie po zakończeniu wykonywania swoich instrukcji i nie będzie czekać na zakończenie wywołanych procedur gor.
Goroutine jest wywoływany za pomocą słowa kluczowego go, po którym następuje wywołanie funkcji.
Przykład
go add(x,y)
Gorutyny zrozumiesz dzięki poniższym przykładom Golang. Wykonaj poniższy program
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") } }
Dane wyjściowe będą
In main In main In main In main In main
Tutaj główny program zakończył wykonywanie jeszcze przed rozpoczęciem goroutine. Display() to goroutine wywoływana przy użyciu składni
go function_name(parameter list)
W powyższym kodzie funkcja main() nie czeka na zakończenie funkcji display(), a funkcja main() zakończyła wykonywanie, zanim funkcja display() wykonała swój kod. Zatem instrukcja print wewnątrz display() nie została wydrukowana.
Teraz modyfikujemy program, aby drukował także instrukcje z display(). Dodajemy opóźnienie czasowe wynoszące 2 sekundy w pętli for funkcji main() i 1 sekundę opóźnienia w pętli for funkcji 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") } }
Dane wyjściowe będą nieco podobne do
In display In main In display In display In main In display In display In main In main In main
Tutaj możesz zobaczyć, że obie pętle są wykonywane w sposób nakładający się ze względu na współbieżne wykonanie.
Kanały
Kanały umożliwiają funkcjom komunikację między sobą. Można go traktować jako medium, w którym jedna procedura umieszcza dane i uzyskuje do niej dostęp inna procedura na serwerze Golang.
Kanał można zadeklarować za pomocą składni
channel_variable := make(chan datatype)
Przykład:
ch := make(chan int)
Dane do kanału można wysyłać za pomocą składni
channel_variable <- variable_name
Przykład
ch <- x
Dane z kanału można odbierać za pomocą składni
variable_name := <- channel_variable
Przykład
y := <- ch
W powyższych przykładach goroutine w języku Go widziałeś, że główny program nie czeka na goroutine. Ale tak nie jest, gdy w grę wchodzą kanały. Załóżmy, że jeśli goroutine przesyła dane do kanału, funkcja main() będzie czekać na instrukcję odbierającą dane kanału, aż otrzyma dane.
Zobaczysz to w poniższych przykładach języków Go. Najpierw napisz normalną goroutine i zobacz zachowanie. Następnie zmodyfikuj program, aby korzystał z kanałów i zobacz zachowanie.
Wykonaj poniższy program
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()") }
Dane wyjściowe będą
Inside main()
Funkcja main() zakończyła wykonywanie i zakończyła działanie przed wykonaniem goroutine. Zatem wydruk wewnątrz display() nie został wykonany.
Teraz zmodyfikuj powyższy program, aby korzystać z kanałów i zobaczyć zachowanie.
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) }
Dane wyjściowe będą
Inside display() Inside main() Printing x in main() after taking from channel: 1234
Tutaj dzieje się funkcja main() po osiągnięciu x := <-ch będzie czekać na dane na kanale ch. Funkcja display() czeka 5 sekund, a następnie przesyła dane do kanału ch. Funkcja main() po odebraniu danych z kanału zostaje odblokowana i kontynuuje wykonywanie.
Nadawca przesyłający dane do kanału może poinformować odbiorców, że zamykając kanał, nie będą już dodawane żadne dane do kanału. Jest to używane głównie wtedy, gdy używasz pętli do przesyłania danych do kanału. Kanał można zamknąć za pomocą
close(channel_name)
Natomiast po stronie odbiornika można sprawdzić czy kanał jest zamknięty za pomocą dodatkowej zmiennej podczas pobierania danych z kanału za pomocą
variable_name, status := <- channel_variable
Jeśli status ma wartość True oznacza to, że otrzymałeś dane z kanału. Jeśli fałszywe, oznacza to, że próbujesz czytać z zamkniętego kanału
Możesz także używać kanałów do komunikacji pomiędzy goroutinami. Należy użyć 2 goroutines – jedna wypycha dane do kanału, a druga odbiera dane z kanału. Zobacz poniższy program
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()") }
Tutaj są 2 podprogramy, jeden wpycha dane do kanału, a drugi drukuje dane do kanału. Funkcja add_to_channel dodaje liczby od 0 do 9 i zamyka kanał. Jednocześnie funkcja fetch_from_channel czeka na
x, flag := <- ch i gdy dane staną się dostępne, drukuje je. Wychodzi, gdy flaga jest fałszywa, co oznacza, że kanał jest zamknięty.
Oczekiwanie w funkcji main() ma na celu uniemożliwienie wyjścia z funkcji main() do czasu, aż goroutines zakończą wykonywanie.
Wykonaj kod i zobacz wynik jako
Read data Send data 0 1 2 3 4 5 6 7 8 9 Empty channel Inside main()
Wybierz
Select można postrzegać jako instrukcję switch, która działa na kanałach. Tutaj instrukcje case będą operacją kanału. Zazwyczaj każda instrukcja case będzie próbą odczytu z kanału. Gdy którykolwiek z przypadków jest gotowy (kanał jest odczytywany), wykonywana jest instrukcja powiązana z tym przypadkiem. Jeśli wiele przypadków jest gotowych, zostanie wybrany losowy przypadek. Możesz mieć domyślny przypadek, który jest wykonywany, jeśli żaden z przypadków nie jest gotowy.
Zobaczmy poniższy kod
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) } }
Wykonanie powyższego programu da wynik:
from data2()
Tutaj instrukcja select czeka, aż dane będą dostępne w którymkolwiek z kanałów. Funkcja data2() dodaje dane do kanału po 2 sekundowym uśpieniu, co spowoduje wykonanie drugiego przypadku.
Dodaj domyślny przypadek do zaznaczenia w tym samym programie i zobacz wynik. Tutaj, po osiągnięciu bloku wyboru, jeśli żaden przypadek nie ma gotowych danych na kanale, wykona domyślny blok bez czekania, aż dane będą dostępne na którymkolwiek kanale.
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") } }
Ten program da wynik:
Default case executed
Dzieje się tak dlatego, że po osiągnięciu bloku wyboru żaden kanał nie miał danych do odczytu. Zatem wykonywany jest przypadek domyślny.
Mutex
Mutex to skrót od mutual exclusion (wzajemnego wykluczania). Mutex jest używany, gdy nie chcesz, aby zasób był dostępny dla wielu podprogramów jednocześnie. Mutex ma 2 metody – Lock i Unlock. Mutex jest zawarty w pakiecie sync. Musisz więc zaimportować pakiet sync. Instrukcje, które muszą być wykonywane wzajemnie wykluczająco, mogą być umieszczone wewnątrz mutex.Lock() i mutex.Unlock().
Nauczmy się muteksu na przykładzie liczenia liczby wykonań pętli. W tym programie oczekujemy, że procedura wykona pętlę 10 razy, a licznik zostanie zapisany w postaci sumy. Wywołujesz tę procedurę 3 razy, więc łączna liczba powinna wynosić 30. Liczba jest przechowywana w zmiennej globalnej count.
Najpierw uruchamiasz program bez mutexu
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) }
Zobacz wynik
Count after i=1 Count: 11 Count after i=3 Count: 12 Count after i=2 Count: 13 Final Count: 13
Wynik może być inny, gdy go wykonasz, ale wynik końcowy nie będzie wynosił 30.
Tutaj dzieje się tak, że 3 goroutines próbują zwiększyć liczbę pętli przechowywaną w zmiennej count. Załóżmy, że w danej chwili liczba wynosi 5, a goroutine1 zwiększy liczbę do 6. Główne kroki obejmują
Skopiuj licznik do temp
Zwiększ temp
Temp. przechowywania z powrotem do zliczenia
Załóżmy, że wkrótce po wykonaniu kroku 3 przez goroutine1; inna goroutine może mieć starą wartość, powiedzmy 3 wykonuje powyższe kroki i zapisuje 4 z powrotem, co jest błędne. Można temu zapobiec, używając muteksu, który powoduje, że inne procedury czekają, gdy jedna z nich już używa zmiennej.
Teraz uruchomisz program z mutexem. Tutaj wyżej wymienione 3 kroki są wykonywane w muteksie.
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) }
Teraz będzie wynik
Count after i=3 Count: 21 Count after i=2 Count: 28 Count after i=1 Count: 30 Final Count: 30
Tutaj otrzymujemy oczekiwany wynik jako wynik końcowy. Ponieważ instrukcje odczytywania, zwiększania i zapisywania liczby licznika są wykonywane w muteksie.
Obsługa błędów
Błędy to nietypowe warunki, takie jak zamknięcie nieotwartego pliku, otwarcie pliku, który nie istnieje itp. Funkcje zwykle zwracają błędy jako ostatnią zwracaną wartość.
Poniższy przykład wyjaśnia więcej na temat błędu.
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") }
Wyjście będzie:
open /invalid.txt: no such file or directory
Tutaj próbowaliśmy otworzyć nieistniejący plik, ale zwrócił on błąd do zmiennej er. Jeśli plik jest prawidłowy, błąd będzie miał wartość null
Niestandardowe błędy
Korzystając z tej funkcji, możesz tworzyć niestandardowe błędy. Odbywa się to za pomocą New() pakietu błędów. Przepiszemy powyższy program, aby wykorzystać niestandardowe błędy.
Uruchom poniższy program
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) } }
Wyjście będzie:
Custom error message:File name is wrong
Tutaj area() zwraca pole kwadratu. Jeśli wartość wejściowa jest mniejsza niż 1, funkcja area() zwraca komunikat o błędzie.
Czytanie plików
Pliki służą do przechowywania danych. Go pozwala nam na odczytanie danych z plików
Najpierw utwórz plik data.txt w swoim obecnym katalogu z poniższą zawartością.
Line one Line two Line three
Teraz uruchom poniższy program, aby zobaczyć, że drukuje zawartość całego pliku jako dane wyjściowe
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)) }
Tutaj dane, err := ioutil.ReadFile(“data.txt”) odczytuje dane i zwraca sekwencję bajtów. Podczas drukowania jest konwertowany do formatu ciągu znaków.
Zapisywanie plików
Zobaczysz to za pomocą programu
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 } }
Tutaj tworzony jest plik test.txt. Jeśli plik już istnieje, zawartość pliku zostanie obcięta. Funkcja Writeline() służy do zapisywania zawartości pliku. Następnie zamknąłeś plik za pomocą Close().
Ściągawka
W tym samouczku Go omówiliśmy:
Temat | Opis | Składnia |
---|---|---|
Typy podstawowe | Numeryczne, łańcuchowe, bool | |
Zmienne | Deklaruj i przypisuj wartości do zmiennych | var nazwa_zmiennej typ var nazwa_zmiennej typ = wartość var nazwa_zmiennej1, nazwa_zmiennej2 = wartość1, wartość2 nazwa_zmiennej := wartość |
Stałe | Zmienne, których wartości nie można zmienić po przypisaniu | zmienna stała = wartość |
Dla pętli | Wykonuj instrukcje w pętli. | dla wyrażenia_inicjalizacyjnego; wyrażenie_oceny; iteracja_wyrażenie{ // jedno lub więcej instrukcji } |
Jeśli inaczej | Jest to stwierdzenie warunkowe | jeśli warunek{ // oświadczenia_1 Else {} // oświadczenia_2 } |
wyłącznik | Instrukcja warunkowa z wieloma przypadkami | wyrażenie przełączające { wartość przypadku_1: oświadczenia_1 wartość przypadku_2: oświadczenia_2 wartość przypadku_n: oświadczenia_n domyślna: oświadczenia_domyślne } |
Szyk | Stały rozmiar nazwanej sekwencji elementów tego samego typu | nazwa tablicy := [rozmiar] typ {wartość_0, wartość_1,…, wartość_rozmiar-1} |
Plaster | Część lub segment tablicy | var nazwa_plasterku [] type = nazwa_tablicy[początek:koniec] |
Funkcje | Blok instrukcji realizujący określone zadanie | func nazwa_funkcji(typ parametru_1, typ parametru_n) typ_zwrotu { //sprawozdania } |
Pakiety | Służą do organizowania kodu. Zwiększa czytelność kodu i możliwość jego ponownego użycia | importuj nazwę_pakietu |
Odraczać | Opóźnia wykonanie funkcji do momentu zakończenia wykonywania funkcji zawierającej | odroczenie nazwa_funkcji(lista_parametrów) |
wskaźniki | Przechowuje adres pamięci innej zmiennej. | var nazwa_zmiennej *typ |
Structure | Typ danych zdefiniowany przez użytkownika, który sam zawiera jeszcze jeden element tego samego lub innego typu | wpisz nazwa struktury struktura { zmienna_1 zmienna_1_typ zmienna_2 zmienna_2_typ zmienna_n zmienna_n_typ } |
Metody | Metoda to funkcja posiadająca argument odbiorcy | func (typ zmiennej) nazwa_metody(lista_parametrów) { } |
Gorutyna | Funkcja, która może działać jednocześnie z innymi funkcjami. | przejdź nazwa_funkcji (lista_parametrów) |
Kanał | Sposób komunikowania się funkcji między sobą. Medium, w którym jedna procedura umieszcza dane i do którego uzyskuje dostęp inna procedura. | Ogłosić: ch := make(chan int) Wyślij dane do kanału: zmienna_kanału <- nazwa_zmiennej Odbierz z kanału: nazwa_zmiennej := <- zmienna_kanału |
Wybierz | Instrukcja Switch, która działa na kanałach. Instrukcje case będą operacją kanału. Gdy którykolwiek kanał jest gotowy z danymi, wykonywana jest instrukcja powiązana z tym case | wybierać { przypadek x := <-chan1: fmt.Drukuj(x) przypadek y := <-chan2: fmt.Println(y) } |
Mutex | Mutex jest używany, gdy nie chcesz, aby wiele podprogramów miało dostęp do zasobu jednocześnie. Mutex ma 2 metody – Zablokuj i Odblokuj | mutex.Lock() //sprawozdania mutex.Odblokuj(). |
Czytaj pliki | Odczytuje dane i zwraca sekwencję bajtów. | Dane, błąd:= ioutil.ReadFile(nazwa pliku) |
Zapisz plik | Zapisuje dane do pliku | l, błąd := f.WriteString(text_to_write) |