This commit is contained in:
Slava Zgordan
2015-08-11 09:51:51 +02:00
parent 72f86c889d
commit 028b399b22
3 changed files with 978 additions and 2 deletions

460
ru/02.2.md Normal file
View File

@@ -0,0 +1,460 @@
# 2.2 Фундамент Go
В этом разделе мы научим Вас определять константы, переменные, относящиеся к элементарным типам данных и некоторым приемам программирования на Go.
## Определение переменных
В Go существует множество способов определить переменную.
Основной способ определить переменную в Go - с помощью ключевого слова 'var'. Заметьте, что в Go тип переменной ставится `после` ее имени.
// Определяем переменную “variableName” и тип "type"
var variableName type
Определение множества переменных.
// Определяем три переменных типа "type"
var vname1, vname2, vname3 type
Определение переменной с присваиванием ей значения.
// Определяем переменную “variableName” типа "type" и задаем ей значение "value"
var variableName type = value
Определение множества переменных с присваиванием им значений.
/*
Определям три переменные типа "type" и инициализируем их значения.
vname1 равно v1, vname2 равно v2, vname3 равно v3
*/
var vname1, vname2, vname3 type = v1, v2, v3
Вам не кажется слишком скучным определять переменные способом, указанным выше? Не волнуйтесь, команда разработчиков Go также посчитала это проблемой. Поэтому если Вы хотите определить переменные с начальными значениями, можно просто опустить указание типа переменных, и код будет выглядеть следующим образом:
/*
Определям три переменные типа "type" и инициализируем их значения.
vname1 равно v1, vname2 равно v2, vname3 равно v3
*/
var vname1, vname2, vname3 = v1, v2, v3
Да, я понимаю, что этого недостаточно. Исправить это мы можем так:
/*
Определяем три переменные, не используя ключевые слова "type" и "var" и задаем им начальные значения.
vname1 равно v1, vname2 равно v2, vname3 равно v3
*/
vname1, vname2, vname3 := v1, v2, v3
Так уже гораздо лучше. Используйте `:=` для замены `var` и `type`, это называется коротким объявлением. Но есть одно ограничение: такую форму определения можно использовать только внутри функций. Если Вы попытаетесь использовтаь ее вне тела функции, Вы получите ошибку компиляции. Поэтому можно использовать `var` для определения глобальных переменных и короткие объявления в `var()`.
`_` (blank) - это специальное имя переменной. Любое значение, присвоенное такой переменной, будет проигнорировано. Например, мы присваиваем `35` переменной `b` и пропускаем `34`.( ***Этот пример просто призван показать, как это работает. Здесь не видно, в чем его польза, но мы будем часто использовать эту возможность Go в работе со значениями, возвращаемыми функциями.*** )
_, b := 34, 35
Если Вы определили переменную и не использовали ее нигде в своей программе, компилятор покажем Вам ошибку компиляции. Попробуйте откомпилировать следующий код и посмотрите, что будет.
package main
func main() {
var i int
}
## Константы
Так называемые константы - это значения, которые были определены во время компиляции, и их нельзя изменить во время работы программы. В Go в качестве типов констант можно использовать число, булев тип и строку.
Константы определяются следующим образом:
const constantName = value
// Если нужно, можно задать тип константы
const Pi float32 = 3.1415926
Еще примеры:
const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"
## Элементарные типы
### Булев тип
в Go для определения переменной булева типа используется `bool`, значение ее может быть только `true` или `false`, и `false` - это значение по умолчанию. ( ***Нельзя конвертировать булев тип в числовой и наоборот!*** )
// пример кода
var isActive bool // глобальная переменная
var enabled, disabled = true, false // опускаем тип переменной
func test() {
var available bool // локальная переменная
valid := false // краткое объявление переменной
available = true // присваивание значения переменной
}
### Числовые типы
Целочисленные типы включают в себя как целочисленные типы со знаком, так и без знака. В Go есть `int` и `uint` одновременно, у них одинаковая длина, но в каждом конкретном случае он азависит от операционной системы. Используются 32-битные типы в 32-битных операционных системах и 64-битные в 64-битных ОС. В Go также есть типы, у которых особая длина. К ним относятся `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Заметьте, что `rune` - это алиас `int32`, а `byte` - это алиас `uint8`.
Есть одна важная вещь, которую надо знать - Вы не можете комюинировать разные типы в одном выражении, такая операция повлечет ошибку компиляции.
var a int8
var b int32
c := a + b
Хотя int32 длиннее int8 и является тем же типом, что и int, нельзя использовать их в одним выражении. ( ***c здесь будет определено как переменная типа `int`*** )
К типам с плавающей точкой относятся `float32` и `float64`, типа, назвывемого `float` в Go нет. `float64` используется по умолчанию при коротком объявлении.
That's all? No! Go supports complex numbers as well. `complex128` (with a 64-bit real and 64-bit imaginary part) is the default type, if you need a smaller type, there is one called `complex64` (with a 32-bit real and 32-bit imaginary part). Its form is `RE+IMi`, where `RE` is real part and `IM` is imaginary part, the last `i` is imaginary number. There is a example of complex number.
Это все? Нет! Go также поддерживает и комплексные числа. `complex128` (с 64-битной вещественной и 64-битной мнимыми частями) является комплексным числом по умолчанию, а если Вам нужны числа поменьше, есть `complex64` (с 32-битной вещественной и 32-битной нмимыми частями). Числа представлены в форме `RE+IMi`, где `RE` - вещественная часть, а `IM` - мнимая, последнее `i` - мнимая единица. Вот пример комплексной единицы:
var c complex64 = 5+5i
//output: (5+5i)
fmt.Printf("Значение: %v", c)
### Строки
Мы уже говорили о том, как Go использует кодировку UTF-8. Строки представлены двойными кавычками `""` обратными кавычками ``` `` ```.
// Пример кода
var frenchHello string // основная форма определения строки
var emptyString string = "" // определяем строковую переменную с пустым значением
func test() {
no, yes, maybe := "no", "yes", "maybe" // короткое объявление
japaneseHello := "Ohaiou"
frenchHello = "Bonjour" // основная форма присваивания переменной значения
}
Менять отдельные символы в строках по индексу невозможно. Например, при компиляции следующего кода вы получите ошибку:
var s string = "hello"
s[0] = 'c'
Что если нам действительно хочется изменить лишь один символ в строке? Попробуем следующее:
s := "hello"
c := []byte(s) // конвертируем строку в тип []byte
c[0] = 'c'
s2 := string(c) // конвертируем []byte обратно в строку
fmt.Printf("%s\n", s2)
Для того, чтобы объединить две строки, используйте оператор `+`:
s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)
Также:
s := "hello"
s = "c" + s[1:] // нельзя менять строку по индексу, но получать значения по индексу можно
fmt.Printf("%s\n", s)
What if I want to have a multiple-line string?
Что, если мы захотим определить строку, чтобы она визуально находилась на разных строках?
m := `hello
world`
`` ` все символы в строке воспринимает буквально, как часть значения переменной.
### Типы ошибок
В Go есть один тип `error`, предназначенный для работы с сообщениями об ошибках. Также есть пакет `errors` для обработки ошибок.
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
### Структура, лежащая в основе данных в Go
Следующий рисунок приведен из статьи про [структуру данных в Go](http://research.swtch.com/godata) в [блоге Russ Cox](http://research.swtch.com/). Для хранения данных Go использует блоки памяти.
![](images/2.2.basic.png?raw=true)
Рисунок 2.1 Структура, лежащая в основе данных в Go.
## Некоторые приемы
### Групповое определение
If you want to define multiple constants, variables or import packages, you can use the group form.
Если Вы хотите определить сразу несколько констант, переменных или импортировать несколько пакетов, Вы можете использовать групповое определение:
Основная форма:
import "fmt"
import "os"
const i = 100
const pi = 3.1415
const prefix = "Go_"
var i int
var pi float32
var prefix string
Групповое определение:
import(
"fmt"
"os"
)
const(
i = 100
pi = 3.1415
prefix = "Go_"
)
var(
i int
pi float32
prefix string
)
Если не указать, что значение константы равно `iota`, то значение первой константы в группе `const()` будет равно `0`. Если же константе, перед которой есть другая константа, явно не присвоено никакого значения, оно будут равно значению идущей перед ней константы. Если значение константы указано как `iota`, значения последующих после нее констант в группе, которым явно не присвоено никаких значений, также будут равны `iota` (И так до тех пор, пока не встретится константа с явно указанным значением, после этого значения всех идущих после нее констант с явно неуказанным значением будут равны ее значению - прим. переводчика на русский)
### Перечисление iota
В Go есть ключевое слово `iota`, оно служит для того, чтобы обеспечить последовательное перечисление (`enum`), оно начинается с `0` и с каждым шагом увеличивается на `1`.
const(
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // если значение константы не указано, ей присваивается значение идущей перед ней константы, следовательно здесь также получается w = iota. Поэтому w == 3, а в случаях y и x также можно было бы опустить "= iota".
)
const v = iota // так как iota встречает ключевое слово `const`, происходит сброс на `0`, поэтому v = 0.
const (
e, f, g = iota, iota, iota // e=0,f=0,g=0, значения iota одинаковы, так как находятся на одной строке.
)
### Некоторые правила
Причина краткости кода, написанного на Go - это то, что этому языку присущи некоторые моменты поведения по умолчанию:
- Все переменные, имя которых начинается с большой буквы, являются публичными; те, имя которых начинается с маленькой буквы - приватными.
- То же относится к функциям и константам, в Go нет ключевых слов `public` или `private`.
## array, slice, map
### array
`array` - это массив, он определяется так:
var arr [n]тип
В `[n]тип`, `n` - длина массива, `type` тип его элементов. Так же, как и в других языках, квадратные скобки `[]`используются для того, чтобы получить или присвоить значения элементам массива.
var arr [10]int // массив типа [10]int
arr[0] = 42 // первый элемент массива имеет индекс 0
arr[1] = 13 // присвуаивание значение элементу массива
fmt.Printf("Первый элемент - %d\n", arr[0]) // получаем значение элемента массива, оно равно 42
fmt.Printf("Последний элемент - %d\n", arr[9]) // возвращается значение по умолчанию 10-го элемента этого массива, в данном случае оно равно 0.
Поскольку длина массива является составной частью его типа, `[3]int` и `[4]int` - разные типы. Поэтому длину массива менять нельзя. Когда массивы используются в качестве аргументов, функции работают с их копиями, не ссылками! Если Вы хотите работать со ссылками, используйте `срезы`, о которых мы поговорим позже.
При определении массивов можно пользоваться коротким объявлением `:=`.
a := [3]int{1, 2, 3} // определяем целочисленный массив длиной 3 элемента.
b := [10]int{1, 2, 3} // определяем целочисленный массив длиной 10 элементов, из которых первым трем присваиваем значения. Остальным элементам присваивается значение по умолчанию 0.
c := [...]int{4, 5, 6} // используйте `…` вместо значения длины, и Go посчитает его за Вас.
Вам может захотеться использовать массивы в качестве элементов другого массива. Давайте посмотрим, как это делается:
// определяем двумерный массив, состоящий из 2 элементов, каждый из которых - массив, который содержит по 4 элемента.
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// То же самое более коротким способом:
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
Структура, лежащая в основе массива.
![](images/2.2.array.png?raw=true)
Рисунок 2.2 Отношения внутри многомерного массива
### slice
Часто тип array - не очень подходящий тип, например, когда при определении мы не знаем точно, какова будет длина массива. Поэтому нам нужен "динамический массив". В Go такой динамический массив называется `срезом (slice)`.
`slice` is not really a `dynamic array`. It's a reference type. `slice` points to an underlying `array` whose declaration is similar to `array`, but doesn't need length.
Вообще `срез` - это не `динамический массив`. Это ссылочный тип. `срез` указывает на лежащий в его основе `массив`, его объявление аналогично `массиву`, но длина не указывается.
// так же, как мы объявляем массив, но здесь мы не указываем длину
var fslice []int
Так мы определяем `срез` и задаем его начальное значение:
slice := []byte {'a', 'b', 'c', 'd'}
`Срез` может переопределять существующие массивы и срезы. `slice` использует `array[i:j]`, чтобы получить фрагмент массива, где `i` - начальный индекс, а `j` - конечный, но имейте в виду, что `array[j]` не войдет в срез, так как длина среза равна `j-i`.
// Определяем массив длиной 10 элементов, элементы являются значениями типа byte.
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// Определяем два среза типа []byte
var a, b []byte
// 'a' указывает на элементы с 3-го по 5-ый в массиве ar.
a = ar[2:5]
// теперь 'a' содержит ar[2],ar[3] и ar[4]
// 'b' - еще один срез массива ar
b = ar[3:5]
// теперь 'b' содержит ar[3] и ar[4]
Имейте в виду разницу между `срезом` и `массивом`, когда определяете их. Для вычисления длины массива используется `[…]`, но для определения среза - только `[]`.
Структура, лежащая в основе срезов.
![](images/2.2.slice.png?raw=true)
Рисунок 2.3 Связь между срезом и массивом
Со срезами можно производить некоторые удобные операции:
- Первый элемент среза имеет индекс 0, `ar[:n]` равен `ar[0:n]`
- Если второй индекс элемента не указан, он равен длине среза, `ar[n:]` равен `ar[n:len(ar)]`.
- Можно использовать `ar[:]`, чтобы срез был равен всему массиву, как следует из сказанного в двух предыдущих пунктах.
Еще примеры, относящиеся к `срезам`:
// определяем массив
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// определяем два среза
var aSlice, bSlice []byte
// некоторые удобные операции
aSlice = array[:3] // то же, что и aSlice = array[0:3] aSlice содержит a,b,c
aSlice = array[5:] // то же, что и aSlice = array[5:10] aSlice содержит f,g,h,i,j
aSlice = array[:] // то же, что и aSlice = array[0:10] aSlice содержит все элементы
// срез из среза
aSlice = array[3:7] // aSlice содержит d,e,f,glen=4 (длина)cap=7 (емкость)
bSlice = aSlice[1:3] // bSlice содержит aSlice[1], aSlice[2], или e,f
bSlice = aSlice[:3] // bSlice содержит aSlice[0], aSlice[1], aSlice[2], или d,e,f
bSlice = aSlice[0:5] // срез может быть расширен до значения емкости, теперь bSlice содержит d,e,f,g,h
bSlice = aSlice[:] // bSlice содержит все элементы aSlice или d,e,f,g
`slice` - ссылочный тип, поэтому при его изменении изменятся также значения всех остальных переменных, указывающих на тот же срез или массив. Например, возвращаясь к `aSlice` и `bSlice`, о которых шла речь выше, если изменить значение одного из элементов `aSlice`, `bSlice` тоже будет изменен.
`slice` похож на struct и состоит из 3 частей:
- Указатель на то, где начинается `срез`.
- Длина `среза`.
- Емкость - длина от начального до конечного индексов `среза`.
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
Slice_a := Array_a[2:5]
Структура, лежащая в основе это кода:
![](images/2.2.slice2.png?raw=true)
Рисунок 2.4 Информация о срезе на основе массива
Срез имеет несколько встроенных функций:
- `len` возвращает длину `среза`.
- `cap` возвращает максимальную длину `среза`
- `append` присоединяет к `срезу` один или несколько элементов и возвращает новый `срез` .
- `copy` копирует элементы из одного среза в другой и возвращает количество скопированных элементов.
Внимание: `append` изменяет массив, на который указывает `срез` и затрагивает все остальные срезы, указывающие на тот же массив. Также, если срезу не хватает длины массива (`(cap-len) == 0`), `append` возвращает новый массив, на который теперь будет указывать этот срез. В этом случае другие срезы, указывающие на старый массив, не изменятся.
### Карты
Поведение `карты (map)` похоже на то, как ведет себя словарь в Python. Чтобы определить карту, используйте `map[типКлюча]типЗначения`.
Давайте посмотрим на пример кода. Команды 'изменения' и 'получения' значений в `карте` похожи на соответствующие для `среза`, однако индекс для `среза` может быть только типа 'int', в то время как `карта` может использовать для этих целей гораздо больше: например `int`, `string` или вообще все что захотите. Также можно использовать `==` и `!=`, чтобы сравнивать значения между собой.
// используем `string` для задания типа ключа, `int` для задания типа значения и инициализируем карту с помощью `make`.
var numbers map[string] int
// еще один способ определить карту
numbers := make(map[string]int)
numbers["one"] = 1 // задаем значение элементу по его ключу
numbers["ten"] = 10
numbers["three"] = 3
fmt.Println("Третий элемент равен: ", numbers["three"]) // получаем значения
// Код выводит: Третий элемент равен: 3
Несколько замечаний при использовании карт:
- элементы в `карте` неупорядочены. Каждый раз, когда Вы печатаете `карту`, Вы получите различные результаты. Получить значения по `индексу` невозможно - следует использовать `ключи`.
- У `карты` нет фиксированной длины. Это ссылочный тип, как и `срез`.
- `len (длина)` работает также и с `картой`. Она возвращает количество `ключей` в карте.
- Изменить значение в `карте` очень просто. Чтобы изменить значение `ключа` one на `11`, нужно использовать выражение `numbers["one"]=11`.
You can use form `key:val` to initialize map's values, and `map` has built-in methods to check if the `key` exists.
Чтобы задать значения элементам карты, нужно исполоьзовать форму `ключ:значение`, также у `карты` есть встроенные методы для того, чтобы проверить, содержит ли она заданный ключ.
Для того, чтобы удалить элемент в `карте`, используйте `delete`.
// Initialize a map
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// карта возвращает два значения. В качестве второго, если элемента с таким ключом не существует, 'ok' возвращает 'false', иначе - 'true'.
csharpRating, ok := rating["C#"]
if ok {
fmt.Println("C# назодится в карте, его рейтинг - ", csharpRating)
} else {
fmt.Println("Не можем найти рейтинг C# в карте")
}
delete(rating, "C") // удаляем элемент с ключом "c"
Как я уже говорил выше, `карта` является ссылочным типом. Если две `карты`s указываюьт на один и тот же объект, любое его изменение затронет их обе.
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // теперь m["hello"] равняется Salut
### make, new
`make` выделяет память для объектов встроенных типов, таких как `map`, `slice`, и `channel`, в то время как `new` служит для выделения памяти под сами типы.
`new(T)` размещает в памяти нулевое значение типа `T` и возвращает его адрес в памяти, типом которого является `*T`. По определению Go оно возвращает указатель на нулевое значение типа `T`.
`new` возвращает указатели.
У встроенной функции `make(T, args)` другое предназначение, нежели у `new(T)`. `make` используется для `slice(срезов)`, `map(карт)` и `channel(каналов)` и возвращает стартовое значение типа `T`. Это делается потому, что данные для этих трех типов должны быть изначально проинициализированы перед тем, как на них указывать. Например, `срез(slice)` содержит указатель, который указывает на лежащий в его основе `array(массив)`, его длину и емкость. Перед тем, как эти данные инициализированы, значение `slice` равно `nil`, поэтому для `slice`, `map` и `channel`, `make` нициализирует лежащие в их основе данные и присваивает некоторые подходящие значения.
`make` возвращает ненулевые значения.
Следующий рисунок показывает, как отличаются`new` и `make`.
![](images/2.2.makenew.png?raw=true)
Рисунок 2.5 Выделение памяти для данных, лежащих в основе в случае make и new
Zero-value does not mean empty value. It's the value that variables default to in most cases. Here is a list of some zero-values.
Нулевое значение - это не пустое значение. Это то значение, которое в большинстве случаев является значением по умолчанию для переменной соответствующего типа. Вот список нулевых значений для некоторых типов:
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 // по сути rune - это int32
byte 0x0 // по сути byte - это uint8
float32 0 // длина - 4 байта
float64 0 // длина - 8 байт
bool false
string ""
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: ["Hello, Go"](02.1.md)
- Следующий раздел: [Условные операторы и функции](02.3.md)

516
ru/02.3.md Normal file
View File

@@ -0,0 +1,516 @@
# 2.3 Условные операторы и функции
В этом разделе мы поговорим об условных операциях и функциях в Go.
## Control statement
The greatest invention in programming is flow control. Because of them, you are able to use simple control statements that can be used to represent complex logic. There are three categories of flow control: conditional, cycle control and unconditional jump.
### if
`if` will most likely be the most common keyword in your programs. If it meets the conditions, then it does something and it does something else if not.
`if` doesn't need parentheses in Go.
if x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than or equal to 10")
}
The most useful thing concerning `if` in Go is that it can have one initialization statement before the conditional statement. The scope of the variables defined in this initialization statement are only available inside the block of the defining `if`.
// initialize x, then check if x greater than
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
}
// the following code will not compile
fmt.Println(x)
Use if-else for multiple conditions.
if integer == 3 {
fmt.Println("The integer is equal to 3")
} else if integer < 3 {
fmt.Println("The integer is less than 3")
} else {
fmt.Println("The integer is greater than 3")
}
### goto
Go has a `goto` keyword, but be careful when you use it. `goto` reroutes the control flow to a previously defined `label` within the body of same code block.
func myFunc() {
i := 0
Here: // label ends with ":"
fmt.Println(i)
i++
goto Here // jump to label "Here"
}
The label name is case sensitive.
### for
`for` is the most powerful control logic in Go. It can read data in loops and iterative operations, just like `while`.
for expression1; expression2; expression3 {
//...
}
`expression1`, `expression2` and `expression3` are all expressions, where `expression1` and `expression3` are variable definitions or return values from functions, and `expression2` is a conditional statement. `expression1` will be executed once before looping, and `expression3` will be executed after each loop.
Examples are more useful than words.
package main
import "fmt"
func main(){
sum := 0;
for index:=0; index < 10 ; index++ {
sum += index
}
fmt.Println("sum is equal to ", sum)
}
// Printsum is equal to 45
Sometimes we need multiple assignments, but Go doesn't have the `,` operator, so we use parallel assignment like `i, j = i + 1, j - 1`.
We can omit `expression1` and `expression3` if they are not necessary.
sum := 1
for ; sum < 1000; {
sum += sum
}
Omit `;` as well. Feel familiar? Yes, it's identical to `while`.
sum := 1
for sum < 1000 {
sum += sum
}
There are two important operations in loops which are `break` and `continue`. `break` jumps out of the loop, and `continue` skips the current loop and starts the next one. If you have nested loops, use `break` along with labels.
for index := 10; index>0; index-- {
if index == 5{
break // or continue
}
fmt.Println(index)
}
// break prints 10、9、8、7、6
// continue prints 10、9、8、7、6、4、3、2、1
`for` can read data from `slice` and `map` when it is used together with `range`.
for k,v:=range map {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
}
Because Go supports multi-value returns and gives compile errors when you don't use values that were defined, you may want to use `_` to discard certain return values.
for _, v := range map{
fmt.Println("map's val:", v)
}
### switch
Sometimes you may find that you are using too many `if-else` statements to implement some logic, which may make it difficult to read and maintain in the future. This is the perfect time to use the `switch` statement to solve this problem.
switch sExpr {
case expr1:
some instructions
case expr2:
some other instructions
case expr3:
some other instructions
default:
other code
}
The type of `sExpr`, `expr1`, `expr2`, and `expr3` must be the same. `switch` is very flexible. Conditions don't have to be constants and it executes from top to bottom until it matches conditions. If there is no statement after the keyword `switch`, then it matches `true`.
i := 10
switch i {
case 1:
fmt.Println("i is equal to 1")
case 2, 3, 4:
fmt.Println("i is equal to 2, 3 or 4")
case 10:
fmt.Println("i is equal to 10")
default:
fmt.Println("All I know is that i is an integer")
}
In the fifth line, we put many values in one `case`, and we don't need to add the `break` keyword at the end of `case`'s body. It will jump out of the switch body once it matched any case. If you want to continue to matching more cases, you need to use the`fallthrough` statement.
integer := 6
switch integer {
case 4:
fmt.Println("integer <= 4")
fallthrough
case 5:
fmt.Println("integer <= 5")
fallthrough
case 6:
fmt.Println("integer <= 6")
fallthrough
case 7:
fmt.Println("integer <= 7")
fallthrough
case 8:
fmt.Println("integer <= 8")
fallthrough
default:
fmt.Println("default case")
}
This program prints the following information.
integer <= 6
integer <= 7
integer <= 8
default case
## Functions
Use the `func` keyword to define a function.
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
// function body
// multi-value return
return value1, value2
}
We can extrapolate the following information from the example above.
- Use keyword `func` to define a function `funcName`.
- Functions have zero, one or more than one arguments. The argument type comes after the argument name and arguments are separated by `,`.
- Functions can return multiple values.
- There are two return values named `output1` and `output2`, you can omit their names and use their type only.
- If there is only one return value and you omitted the name, you don't need brackets for the return values.
- If the function doesn't have return values, you can omit the return parameters altogether.
- If the function has return values, you have to use the `return` statement somewhere in the body of the function.
Let's see one practical example. (calculate maximum value)
package main
import "fmt"
// return greater value between a and b
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
x := 3
y := 4
z := 5
max_xy := max(x, y) // call function max(x, y)
max_xz := max(x, z) // call function max(x, z)
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // call function here
}
In the above example, there are two arguments in the function `max`, their types are both `int` so the first type can be omitted. For instance, `a, b int` instead of `a int, b int`. The same rules apply for additional arguments. Notice here that `max` only has one return value, so we only need to write the type of its return value -this is the short form of writing it.
### Multi-value return
One thing that Go is better at than C is that it supports multi-value returns.
We'll use the following example here.
package main
import "fmt"
// return results of A + B and A * B
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}
func main() {
x := 3
y := 4
xPLUSy, xTIMESy := SumAndProduct(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}
The above example returns two values without names -you have the option of naming them also. If we named the return values, we would just need to use `return` to return the values since they are initialized in the function automatically. Notice that if your functions are going to be used outside of the package, which means your function names start with a capital letter, you'd better write complete statements for `return`; it makes your code more readable.
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B
Multiplied = A*B
return
}
### Variable arguments
Go supports variable arguments, which means you can give an uncertain numbers of argument to functions.
func myfunc(arg ...int) {}
`arg …int` tells Go that this is a function that has variable arguments. Notice that these arguments are type `int`. In the body of function, the `arg` becomes a `slice` of `int`.
for _, n := range arg {
fmt.Printf("And the number is: %d\n", n)
}
### Pass by value and pointers
When we pass an argument to the function that was called, that function actually gets the copy of our variables so any change will not affect to the original variable.
Let's see one example in order to prove what i'm saying.
package main
import "fmt"
// simple function to add 1 to a
func add1(a int) int {
a = a+1 // we change value of a
return a // return new value of a
}
func main() {
x := 3
fmt.Println("x = ", x) // should print "x = 3"
x1 := add1(x) // call add1(x)
fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
fmt.Println("x = ", x) // should print "x = 3"
}
Can you see that? Even though we called `add1` with `x`, the origin value of `x` doesn't change.
The reason is very simple: when we called `add1`, we gave a copy of `x` to it, not the `x` itself.
Now you may ask how I can pass the real `x` to the function.
We need use pointers here. We know variables are stored in memory and they have some memory addresses. So, if we want to change the value of a variable, we must change its memory address. Therefore the function `add1` has to know the memory address of `x` in order to change its value. Here we pass `&x` to the function, and change the argument's type to the pointer type `*int`. Be aware that we pass a copy of the pointer, not copy of value.
package main
import "fmt"
// simple function to add 1 to a
func add1(a *int) int {
*a = *a+1 // we changed value of a
return *a // return new value of a
}
func main() {
x := 3
fmt.Println("x = ", x) // should print "x = 3"
x1 := add1(&x) // call add1(&x) pass memory address of x
fmt.Println("x+1 = ", x1) // should print "x+1 = 4"
fmt.Println("x = ", x) // should print "x = 4"
}
Now we can change the value of `x` in the functions. Why do we use pointers? What are the advantages?
- Allows us to use more functions to operate on one variable.
- Low cost by passing memory addresses (8 bytes), copy is not an efficient way, both in terms of time and space, to pass variables.
- `string`, `slice`, `map` are reference types, so they use pointers when passing to functions by default. (Attention: If you need to change the length of `slice`, you have to pass pointers explicitly)
### defer
Go has a well designed keyword called `defer`. You can have many `defer` statements in one function; they will execute in reverse order when the program executes to the end of functions. In the case where the program opens some resource files, these files would have to be closed before the function can return with errors. Let's see some examples.
func ReadWrite() bool {
file.Open("file")
// Do some work
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}
We saw some code being repeated several times. `defer` solves this problem very well. It doesn't only help you to write clean code but also makes your code more readable.
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
If there are more than one `defer`s, they will execute by reverse order. The following example will print `4 3 2 1 0`.
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
### Functions as values and types
Functions are also variables in Go, we can use `type` to define them. Functions that have the same signature can be seen as the same type.
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
What's the advantage of this feature? The answer is that it allows us to pass functions as values.
package main
import "fmt"
type testInt func(int) bool // define a function type of variable
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// pass the function `f` as an argument to another function
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // use function as values
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven)
fmt.Println("Even elements of slice are: ", even)
}
It's very useful when we use interfaces. As you can see `testInt` is a variable that has function type, and return values and arguments of `filter` are the same as `testInt`. Therefore, we can have complex logic in our programs, while maintaining flexibility in our code.
### Panic and Recover
Go doesn't have `try-catch` structure like Java does. Instead of throwing exceptions, Go uses `panic` and `recover` to deal with errors. However, you shouldn't use `panic` very much, although it's powerful.
Panic is a built-in function to break the normal flow of programs and get into panic status. When a function `F` calls `panic`, `F` will not continue executing but its `defer` functions will continue to execute. Then `F` goes back to the break point which caused the panic status. The program will not terminate until all of these functions return with panic to the first level of that `goroutine`. `panic` can be produced by calling `panic` in the program, and some errors also cause `panic` like array access out of bounds errors.
Recover is a built-in function to recover `goroutine`s from panic status. Calling `recover` in `defer` functions is useful because normal functions will not be executed when the program is in the panic status. It catches `panic` values if the program is in the panic status, and it gets `nil` if the program is not in panic status.
The following example shows how to use `panic`.
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
The following example shows how to check `panic`.
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() // if f causes panic, it will recover
return
}
### `main` function and `init` function
Go has two retentions which are called `main` and `init`, where `init` can be used in all packages and `main` can only be used in the `main` package. These two functions are not able to have arguments or return values. Even though we can write many `init` functions in one package, I strongly recommend writing only one `init` function for each package.
Go programs will call `init()` and `main()` automatically, so you don't need to call them by yourself. For every package, the `init` function is optional, but `package main` has one and only one `main` function.
Programs initialize and begin execution from the `main` package. If the `main` package imports other packages, they will be imported in the compile time. If one package is imported many times, it will be only compiled once. After importing packages, programs will initialize the constants and variables within the imported packages, then execute the `init` function if it exists, and so on. After all the other packages are initialized, programs will initialize constants and variables in the `main` package, then execute the `init` function inside the package if it exists. The following figure shows the process.
![](images/2.3.init.png?raw=true)
Figure 2.6 Flow of programs initialization in Go
### import
We use `import` very often in Go programs as follows.
import(
"fmt"
)
Then we use functions in that package as follows.
fmt.Println("hello world")
`fmt` is from Go standard library, it is located within $GOROOT/pkg. Go supports third-party packages in two ways.
1. Relative path
import "./model" // load package in the same directory, I don't recommend this way.
2. Absolute path
import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model"
There are some special operators when we import packages, and beginners are always confused by these operators.
1. Dot operator.
Sometime we see people use following way to import packages.
import(
. "fmt"
)
The dot operator means you can omit the package name when you call functions inside of that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`.
2. Alias operation.
It changes the name of the package that we imported when we call functions that belong to that package.
import(
f "fmt"
)
Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`.
3. `_` operator.
This is the operator that is difficult to understand without someone explaining it to you.
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
The `_` operator actually means we just want to import that package and execute its `init` function, and we are not sure if want to use the functions belonging to that package.
## Links
- [Directory](preface.md)
- Previous section: [Go foundation](02.2.md)
- Next section: [struct](02.4.md)

View File

@@ -6,8 +6,8 @@
- 1.5. [Итоги раздела](01.5.md)
- 2.[Основы Go](02.0.md)
- 2.1. ["Hello, Go"](02.1.md)
- 2.2. [Go foundation](02.2.md)
- 2.3. [Control statements and functions](02.3.md)
- 2.2. [Фундамент Go](02.2.md)
- 2.3. [Условные операторы и функции](02.3.md)
- 2.4. [struct](02.4.md)
- 2.5. [Object-oriented](02.5.md)
- 2.6. [interface](02.6.md)