12 |
13 |
14 | {% include footer.html %}
15 | {% include ribbon.html %}
16 | {% include counters.html %}
17 |
18 |
19 |
20 |
21 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/_includes/counters.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/_assets/stylesheets/typo.scss:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 18px;
3 | }
4 |
5 | body {
6 | font-family: "Georgia", "Times New Roman", sans-serif;
7 | line-height: 1.6;
8 | }
9 |
10 | pre, code {
11 | font-family: "Ubuntu Mono", Consolas, monospace;
12 | }
13 |
14 | h1, h2, h3, h4, h5, h6,
15 | p, ul, ol, table, pre {
16 | margin-bottom: 15px;
17 | }
18 |
19 | pre {
20 | border-radius: 3px;
21 | background: #333;
22 | line-height: 1.5;
23 | padding: 20px;
24 | color: #ccc;
25 | }
26 |
27 | img {
28 | max-width: 100%;
29 | }
30 |
31 | p > img {
32 | display: block;
33 | margin: 0 auto;
34 | }
35 |
36 | p > code,
37 | li > code,
38 | em > code {
39 | color: #b00;
40 | }
41 |
42 | kbd {
43 | box-shadow: 1px 1px rgba(0,0,0, 0.1);
44 | font-family: "Ubuntu Mono", Consolas, monospace;
45 | box-sizing: border-box;
46 | border: 1px solid #AAA;
47 | border-radius: 2px;
48 | font-size: 0.8em;
49 | background: #FFF;
50 | padding: 3px;
51 | color: #333;
52 | }
53 |
54 | ul, ol {
55 | margin-left: 2em;
56 | }
57 |
58 | h1, h2, h3, h4, h5, h6 {
59 | line-height: 1.3;
60 | }
61 |
62 | h1 {
63 | font-size: 2.5rem;
64 | font-weight: lighter;
65 | }
66 |
67 | h2 {
68 | font-size: 2rem;
69 | font-weight: lighter;
70 | }
71 |
72 | h3 {
73 | font-weight: lighter;
74 | font-size: 1.5em;
75 | }
76 |
77 | table {
78 | font-size: 0.9rem;
79 | width: 100%;
80 | }
81 |
82 | th {
83 | font-weight: bold;
84 | background: #EEE;
85 | }
86 |
87 | th, td {
88 | padding: 5px 10px;
89 | border: 1px solid #ddd;
90 | }
91 |
92 | a {
93 | color: #06C;
94 |
95 | &:hover {
96 | color: #C04;
97 | }
98 | }
99 |
100 | em {
101 | font-style: italic;
102 | }
103 |
104 | strong {
105 | font-weight: bold;
106 | }
107 |
108 | sup, sub {
109 | font-size: 0.75em;
110 | position: relative;
111 | }
112 |
113 | sup {
114 | top: -0.5em;
115 | }
116 |
117 | sub {
118 | bottom: -0.25em;
119 | }
120 |
--------------------------------------------------------------------------------
/_assets/stylesheets/reset.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | div,
4 | span,
5 | object,
6 | iframe,
7 | p,
8 | blockquote,
9 | pre,
10 | a,
11 | abbr,
12 | acronym,
13 | address,
14 | big,
15 | cite,
16 | code,
17 | del,
18 | dfn,
19 | em,
20 | img,
21 | ins,
22 | kbd,
23 | q,
24 | s,
25 | samp,
26 | small,
27 | strike,
28 | strong,
29 | sub,
30 | sup,
31 | tt,
32 | var,
33 | b,
34 | u,
35 | i,
36 | center,
37 | dl,
38 | dt,
39 | dd,
40 | ol,
41 | ul,
42 | li,
43 | fieldset,
44 | form,
45 | label,
46 | legend,
47 | table,
48 | caption,
49 | tbody,
50 | tfoot,
51 | thead,
52 | tr,
53 | th,
54 | td,
55 | article,
56 | aside,
57 | canvas,
58 | details,
59 | embed,
60 | figure,
61 | figcaption,
62 | footer,
63 | header,
64 | hgroup,
65 | menu,
66 | nav,
67 | output,
68 | ruby,
69 | section,
70 | summary,
71 | time,
72 | mark,
73 | audio,
74 | video,
75 | h1,
76 | h2,
77 | h3,
78 | h4,
79 | h5,
80 | h6 {
81 | margin: 0;
82 | padding: 0;
83 | border: 0;
84 | outline: 0;
85 | font-size: 100%;
86 | vertical-align: baseline;
87 | background: transparent;
88 | font-style: inherit;
89 | }
90 |
91 | html {
92 | height: 100%;
93 | }
94 |
95 | body {
96 | position: relative;
97 | min-height: 100%;
98 | }
99 |
100 | a:active,
101 | a:hover {
102 | outline: 0;
103 | }
104 |
105 | button,
106 | input {
107 | line-height: normal;
108 | }
109 |
110 | button,
111 | select {
112 | text-transform: none;
113 | }
114 |
115 | article,
116 | aside,
117 | details,
118 | figcaption,
119 | figure,
120 | footer,
121 | header,
122 | hgroup,
123 | main,
124 | nav,
125 | section,
126 | summary {
127 | display: block;
128 | }
129 |
130 | pre {
131 | overflow: auto;
132 | }
133 |
134 | audio,
135 | canvas,
136 | video {
137 | display: inline-block;
138 | }
139 |
140 | audio:not([controls]) {
141 | display: none;
142 | height: 0;
143 | }
144 |
145 | blockquote, q {
146 | quotes: none;
147 | }
148 |
149 | blockquote:before, blockquote:after,
150 | q:before, q:after {
151 | content: '';
152 | content: none;
153 | }
154 |
155 | table {
156 | border-collapse: collapse;
157 | border-spacing: 0;
158 | }
159 |
160 | caption,
161 | th, td {
162 | text-align: left;
163 | vertical-align: top;
164 | font-weight: normal;
165 | }
166 |
167 | button,
168 | input,
169 | select,
170 | textarea {
171 | margin: 0;
172 | }
173 |
174 | textarea {
175 | overflow: auto;
176 | vertical-align: top;
177 | }
178 |
179 | button {
180 | width: auto;
181 | overflow: visible;
182 | }
183 |
184 | input[type=button],
185 | input[type=submit],
186 | button {
187 | cursor: pointer;
188 | }
189 |
--------------------------------------------------------------------------------
/_book/chapter-14-next-steps.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Дальнейшие шаги"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Теперь у вас должно быть достаточно знаний, чтобы написать практически любую
8 | программу на Go. Но опасно делать выводы о том, что теперь вы стали компетентным
9 | программистом. Программирование — это большое мастерство, достаточно простое,
10 | если имеются знания. В этой главе я дам вам несколько советов о том, как лучше
11 | освоить ремесло программирования.
12 |
13 | ## Учитесь у мастеров
14 |
15 | Частью становления хорошего художника или писателя является изучения работ
16 | мастеров. Это ничем не отличается от программирования. Один из лучших способов
17 | стать квалифицированным программистом — это изучение исходного кода, созданного
18 | другими людьми. Go отлично подходит для этой задачи, поскольку исходный код
19 | всего проекта находится в свободном доступе.
20 |
21 | Например, мы могли бы взглянуть на исходный код библиотеки `io/ioutil` по
22 | адресу: http://golang.org/src/pkg/io/ioutil/ioutil.go
23 |
24 | Читайте код медленно и осознанно. Постарайтесь понять каждую строку и не
25 | забывайте про прилагаемые комментарии. Например, в методе `ReadFile` есть
26 | комментарий, который гласит:
27 |
28 | // It's a good but not certain bet that FileInfo
29 | // will tell us exactly how much to read, so
30 | // let's try it but be prepared for the answer
31 | // to be wrong.
32 |
33 | Этот метод наверняка раньше был проще, чем он есть в данный момент. Это отличный
34 | пример того, как программы могут развиваться после тестирования и насколько
35 | важно обеспечить комментарием внесённые изменения. Весь исходный код всех
36 | пакетов можно найти по адресу: http://golang.org/src/pkg/
37 |
38 | ## Делайте что-нибудь
39 |
40 | Один из лучших способов оттачивания своих навыков - это практика написания кода.
41 | Есть много способов сделать это: вы могли бы поработать над сложными задачками
42 | по программированию на таких сайтах, как [Project Euler][1] или попробовать себя
43 | в более крупном проекте. Возможно, вы захотите написать веб-сервер или даже
44 | написать небольшую игру.
45 |
46 | ## Работайте в команде
47 |
48 | Большая часть программных проектов в реальном мире созданы командами
49 | программистов. Поэтому умение работать в команде имеет большое значение. Если у
50 | вас есть заинтересованный друг или одноклассник — возьмите его и объединитесь в
51 | команду для работы над общим проектом. Узнайте, как разделить проект на части и
52 | тогда сможете работать над ним в разное время.
53 |
54 | Второй вариант заключается в работе над открытым проектом. Найдите какую-нибудь
55 | стороннюю библиотеку, напишите новую функциональность (или исправьте ошибки) и
56 | отправьте её мейнтейнеру. У Go есть растущее сообщество, которое взаимодействует
57 | с помощью [списков рассылки][2].
58 |
59 |
60 | [1]: http://projecteuler.net/
61 | [2]: http://groups.google.com/group/golang-nuts
62 |
--------------------------------------------------------------------------------
/_assets/stylesheets/main.scss:
--------------------------------------------------------------------------------
1 | @import "reset";
2 | @import "typo";
3 |
4 | .container {
5 | margin: 0 auto;
6 | max-width: 728px;
7 | padding-left: 100px;
8 | padding-right: 100px;
9 |
10 | @media screen and (max-width: 640px) {
11 | padding-left: 10px;
12 | padding-right: 10px;
13 | }
14 | }
15 |
16 | .intro {
17 | margin-bottom: 50px;
18 | }
19 |
20 | .intro_heading {
21 | margin-bottom: 0;
22 | line-height: 1;
23 | }
24 |
25 | .intro_author {
26 | font-size: 0.8em;
27 | font-style: italic;
28 | margin-bottom: 10px;
29 | }
30 |
31 | .chapters {
32 | margin-bottom: 50px;
33 | }
34 |
35 | .chapter {}
36 |
37 | .chapter_navs {
38 | margin-bottom: 15px;
39 | }
40 |
41 | .chapter_back {
42 | font-size: 0.9em;
43 | position: relative;
44 | padding-left: 1.3em;
45 |
46 | &::before {
47 | position: absolute;
48 | content: "←";
49 | top: -0.2em;
50 | left: 0;
51 | }
52 | }
53 |
54 | .download {
55 | margin-bottom: 50px;
56 | }
57 |
58 | .download_wrapper {
59 | margin-bottom: 10px;
60 | }
61 |
62 | .download_btn {
63 | font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
64 | border: 1px solid #2E6DA4;
65 | box-sizing: border-box;
66 | text-decoration: none;
67 | display: inline-block;
68 | background: #337AB7;
69 | border-radius: 2px;
70 | margin-right: 10px;
71 | padding: 5px 15px;
72 | color: #FFF;
73 |
74 | &:hover {
75 | background: #2E6DA4;
76 | color: #FFF;
77 | }
78 |
79 | @media screen and (max-width: 640px) {
80 | margin-bottom: 10px;
81 | text-align: center;
82 | display: block;
83 |
84 | &:last-child {
85 | margin-bottom: 0;
86 | }
87 | }
88 | }
89 |
90 |
91 | .content {
92 | padding-top: 30px;
93 | padding-bottom: 100px;
94 |
95 | @media screen and (max-width: 640px) {
96 | padding-bottom: 150px;
97 | }
98 | }
99 |
100 | .footer {
101 | border-top: 1px solid #ddd;
102 | position: absolute;
103 | font-size: 0.8rem;
104 | bottom: 0;
105 | right: 0;
106 | left: 0;
107 | }
108 |
109 | .footer_container {
110 | padding-top: 10px;
111 | padding-bottom: 10px;
112 | p { margin-bottom: 0; }
113 | }
114 |
115 | .prevnext {
116 | overflow: hidden;
117 | }
118 |
119 | .prevnext_item {
120 | &.-prev {
121 | float: left;
122 | }
123 | &.-next {
124 | float: right;
125 | }
126 | }
127 |
128 | .github-ribbon {
129 | @media screen and (max-width: 640px) {
130 | display: none;
131 | }
132 | }
133 |
134 | .footnotes {
135 | font-size: 0.9rem;
136 | padding: 20px 0;
137 |
138 | hr {
139 | display: none;
140 | }
141 |
142 | ol {
143 | margin-bottom: 0;
144 | }
145 | }
146 |
147 | .ads {
148 | box-shadow: 0 1px 1px rgba(0,0,0, 0.2);
149 | margin-bottom: 30px;
150 | text-align: center;
151 |
152 | @media screen and (max-width: 728px) {
153 | display: none;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/_book/chapter-12-testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Тестирование"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Писать программы — не просто. Даже самые лучшие программисты, зачастую, не в
8 | состоянии написать программу так, чтобы она работала как положено в любых
9 | случаях. Поэтому, важной частью процесса разработки является тестирование.
10 | Написание тестов для нашего кода является отличным способом повышения его
11 | качества и стабильности.
12 |
13 | Go содержит специальную программу, призванную облегчить написание тестов, так
14 | что давайте напишем несколько тестов для пакета, который мы создали в предыдущей
15 | главе. В папке `chapter11/math` создайте файл под именем `math_test.go`, который
16 | будет содержать следующее:
17 |
18 | package math
19 |
20 | import "testing"
21 |
22 | func TestAverage(t *testing.T) {
23 | var v float64
24 | v = Average([]float64{1,2})
25 | if v != 1.5 {
26 | t.Error("Expected 1.5, got ", v)
27 | }
28 | }
29 |
30 | Теперь запустим эту команду:
31 |
32 | go test
33 |
34 | Вы должны увидеть:
35 |
36 | $ go test
37 | PASS
38 | ok golang-book/chapter11/math 0.032s
39 |
40 | Команда `go test` найдет все тесты для всех файлов в текущей директории и
41 | запустит их. Тесты определяются с помощью добавления `Test` к имени функции и
42 | принимают один аргумент типа `*testing.T`. В нашем случае, поскольку мы
43 | тестируем функцию `Average`, тестирующая функция будет называться `TestAverage`.
44 |
45 | После определения тестирующей функции пишется код, который должен использовать
46 | тестируемую функцию. Мы знаем, что среднее от `[1, 2]` будет `1.5`, это и есть
47 | то, что мы проверяем. Возможно, лучшей идеей будет проверить различные
48 | комбинации чисел, так что давайте немного изменим тестирующую функцию:
49 |
50 | package math
51 |
52 | import "testing"
53 |
54 | type testpair struct {
55 | values []float64
56 | average float64
57 | }
58 |
59 | var tests = []testpair{
60 | { []float64{1,2}, 1.5 },
61 | { []float64{1,1,1,1,1,1}, 1 },
62 | { []float64{-1,1}, 0 },
63 | }
64 |
65 | func TestAverage(t *testing.T) {
66 | for _, pair := range tests {
67 | v := Average(pair.values)
68 | if v != pair.average {
69 | t.Error(
70 | "For", pair.values,
71 | "expected", pair.average,
72 | "got", v,
73 | )
74 | }
75 | }
76 | }
77 |
78 | Это очень распространённый способ написания тестов (больше примеров можно найти
79 | в исходном коде пакетов, поставляемых с Go). Мы создали `struct`, представляющий
80 | входы и выходы для функций. Затем мы создали список из этих структур (пар) и
81 | вызвали тестируемую функцию в каждой итерации цикла.
82 |
83 | ## Задачи
84 |
85 | * Написать хороший набор тестов не всегда легко, но даже сам процесс их
86 | написания, зачастую, может выявить много проблем для первой реализации функции.
87 | Например, что произойдет с нашей функцией `Average`, если ей передать пустой
88 | список (`[]float64{}`)? Как нужно изменить функцию, чтобы она возвращала `0` в
89 | таких случаях?
90 |
91 | * Напишите серию тестов для функций `Min` и `Max` из предыдущей главы.
92 |
--------------------------------------------------------------------------------
/_book/chapter-08-pointers.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Указатели"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Когда мы вызываем функцию с аргументами, аргументы копируются в функцию:
8 |
9 | func zero(x int) {
10 | x = 0
11 | }
12 | func main() {
13 | x := 5
14 | zero(x)
15 | fmt.Println(x) // x всё еще равен 5
16 | }
17 |
18 | В этой программе функция `zero` не изменяет оригинальную переменную `x` из
19 | функции `main`. Но что если мы хотим её изменить? Один из способов сделать это —
20 | использовать специальный тип данных — указатель:
21 |
22 | func zero(xPtr *int) {
23 | *xPtr = 0
24 | }
25 | func main() {
26 | x := 5
27 | zero(&x)
28 | fmt.Println(x) // x is 0
29 | }
30 |
31 | Указатели указывают (прошу прощения за тавтологию) на участок в памяти, где
32 | хранится значение. Используя указатель (`*int`) в функции `zero`, мы можем
33 | изменить значение оригинальной переменной.
34 |
35 | ## Операторы * и &
36 |
37 | В Go указатели представлены через оператор * (звёздочка), за которым следует тип
38 | хранимого значения. В функции `zero` `xPtr` является указателем на `int`.
39 |
40 | `*` также используется для «разыменовывания» указателей. Когда мы пишем `*xPtr = 0`,
41 | то читаем это так: «Храним `int` 0 в памяти, на которую указывает `xPtr`».
42 | Если вместо этого мы попробуем написать `xPtr = 0`, то получим ошибку компиляции,
43 | потому что `xPtr` имеет тип не `int`, а `*int`. Соответственно, ему может быть
44 | присвоен только другой `*int`.
45 |
46 | Также существует оператор `&`, который используется для получения адреса
47 | переменной. `&x` вернет `*int` (указатель на `int`) потому что `x` имеет тип
48 | `int`. Теперь мы можем изменять оригинальную переменную. `&x` в функции `main` и
49 | `xPtr` в функции `zero` указывают на один и тот же участок в памяти.
50 |
51 | ## Оператор new
52 |
53 | Другой способ получить указатель — использовать встроенную функцию `new`:
54 |
55 | func one(xPtr *int) {
56 | *xPtr = 1
57 | }
58 | func main() {
59 | xPtr := new(int)
60 | one(xPtr)
61 | fmt.Println(*xPtr) // x is 1
62 | }
63 |
64 | Функция `new` принимает аргументом тип, выделяет для него память и
65 | возвращает указатель на эту память.
66 |
67 | В некоторых языках программирования есть существенная разница между
68 | использованием `new` и `&`, и в них нужно удалять всё, что было создано с
69 | помощью `new`. Go не такой - Go хороший. Go — язык с автоматической сборкой
70 | мусора. Это означает, что область памяти очищается автоматически, когда на неё не
71 | остаётся ссылок.
72 |
73 | Указатели редко используются в Go для встроенных типов, но они будут часто
74 | фигурировать в следующей главе (они чрезвычайно полезны при работе со
75 | структурами).
76 |
77 | ## Задачи
78 |
79 | * Как получить адрес переменной?
80 |
81 | * Как присвоить значение указателю?
82 |
83 | * Как создать новый указатель?
84 |
85 | * Какое будет значение у переменной `x` после выполнения программы:
86 |
87 | ```
88 | func square(x *float64) {
89 | *x = *x * *x
90 | }
91 | func main() {
92 | x := 1.5
93 | square(&x)
94 | }
95 | ```
96 |
97 | * Напишите программу, которая меняет местами два числа
98 | (`x := 1; y := 2; swap(&x, &y)` должно дать `x=2` и `y=1`).
99 |
--------------------------------------------------------------------------------
/_book/chapter-11-packages.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Пакеты и повторное использование кода"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Go разработан как язык, который поощряет хорошие инженерные практики. Одной из
8 | этих практик, позволяющих создавать высококачественное программное обеспечение,
9 | является повторное использование кода, называемое DRY — «Don't Repeat Yourself»
10 | — (акроним, в переводе с английского) — «не повторяйтесь!». Как мы уже видели в
11 | 7 главе, функции являются первым уровнем повторного использование кода. Но Go
12 | поддерживает ещё один механизм для повторного использования кода — пакеты. Почти
13 | любая программа, которую мы видели, включает эту строку:
14 |
15 | import "fmt"
16 |
17 | `fmt` — это имя пакета, включающего множество функций, связанных с
18 | форматированием строк и выводом на экран. Данный метод распространения кода
19 | обусловлен тремя причинами:
20 |
21 | * Снижение вероятности дублирование имён функций, что позволяет именам быть
22 | простыми и краткими
23 |
24 | * Организация кода для упрощения поиска повторно используемых конструкций
25 |
26 | * Ускорение компиляции, так как мы должны перекомпилировать только части
27 | программы. Несмотря на то, что мы используем пакет `fmt`, мы не должны
28 | перекомпилировать его при каждом использовании
29 |
30 | ## Создание пакета
31 |
32 | Использовать пакеты имеет смысл, только когда они востребованы отдельной
33 | программой. Без неё использовать пакеты невозможно.
34 |
35 | Давайте создадим программу, которая будет использовать наш пакет. Создадим
36 | директорию в `~/Go/src/golang-book` под названием `chapter11`. В ней создадим
37 | файл `main.go` с этим кодом:
38 |
39 | package main
40 |
41 | import "fmt"
42 | import "golang-book/chapter11/math"
43 |
44 | func main() {
45 | xs := []float64{1,2,3,4}
46 | avg := math.Average(xs)
47 | fmt.Println(avg)
48 | }
49 |
50 | А теперь создадим ещё одну директорию внутри `chapter11` под названием `math`
51 | В ней мы создадим файл `math.go` с этим кодом:
52 |
53 | package math
54 |
55 | func Average(xs []float64) float64 {
56 | total := float64(0)
57 | for _, x := range xs {
58 | total += x
59 | }
60 | return total / float64(len(xs))
61 | }
62 |
63 | C помощью терминала в папке `math` запустите команду `go install`. В результате
64 | файл `math.go` скомпилируется в объектный файл `~/Go/pkg/os_arch/golang-book/chapter11/math.a`
65 | (при этом, `os` может быть `Windows`, a `arch`, например, — amd64)
66 |
67 | Теперь вернёмся в директорию `chapter11` и выполним `go run main.go`. Программа
68 | выведет `2.5` на экран. Подведём итоги:
69 |
70 | * `math` является встроенным пакетом, но так как пакеты Go используют
71 | иерархические наименование, мы можем перекрыть уже используемое наименование, в
72 | данном случае настоящий пакет `math` и будет называться `math`, а наш —
73 | `golang-book/chapter11/math`.
74 |
75 | * Когда мы импортируем библиотеку, мы используем её полное наименование
76 | `import "golang-book/chapter11/math"`, но внутри файла `math.go` мы используем
77 | только последнюю часть названия — `package math`.
78 |
79 | * Мы используем только краткое имя `math` когда мы обращаемся к функциям в
80 | нашем пакете. Если же мы хотим использовать оба пакета, то мы можем использовать
81 | псевдоним:
82 |
83 | ```
84 | import m "golang-book/chapter11/math"
85 |
86 | func main() {
87 | xs := []float64{1,2,3,4}
88 | avg := m.Average(xs)
89 | fmt.Println(avg)
90 | }
91 | ```
92 |
93 | В этом коде `m` — псевдоним.
94 |
95 | * Возможно вы заметили, что каждая функция в пакете начинается с заглавной
96 | буквы. Любая сущность языка Go, которая начинается с заглавной буквы, означает,
97 | что другие пакеты и программы могут использовать эту сущность. Если бы мы
98 | назвали нашу функцию `average`, а не `Average`, то наша главная программа не
99 | смогла бы обратиться к ней.
100 |
101 | * Рекомендуется делать явными только те сущности нашего пакета, которые могут
102 | быть использованы другими пакетами, и прятать все остальные служебные функции,
103 | не используемые в других пакетах. Данный подход позволяет производить изменения в
104 | скрытых частях пакета без риска нарушить работу других программ, и это облегчает
105 | использование нашего пакета
106 |
107 | * Имена пакетов совпадают с директориями, в которых они размещены. Данное
108 | правило можно обойти, но делать это нежелательно.
109 |
110 | ## Документация к коду
111 |
112 | Go позволяет автоматически создавать документацию к пользовательским пакетам
113 | так же, как и документировать стандартные пакеты. Запустите эту команду в
114 | терминале:
115 |
116 | godoc golang-book/chapter11/math Average
117 |
118 | И вы увидите информацию о функции, которую мы только что написали. Мы можем
119 | улучшить документацию, добавив комментарий перед функцией:
120 |
121 | // Найти среднее в массиве чисел.
122 | func Average(xs []float64) float64 {
123 |
124 | Если вы запустите `go install`, а потом перезапустите:
125 |
126 | godoc golang-book/chapter11/math Average
127 |
128 | то вы увидите наш комментарий — `Найти среднее в массиве чисел`. Также вы можете
129 | увидеть документацию на интернет-странице, запустив в терминале команду:
130 |
131 | godoc -http=":6060"
132 |
133 | и открыв этот адрес в браузере `http://localhost:6060/pkg/`.
134 |
135 | Вы увидите документацию по всем пакетам, установленным в системе, в том числе и
136 | про наш пакет.
137 |
138 | ## Задачи
139 |
140 | * Зачем мы используем пакеты?
141 |
142 | * Чем отличаются программные сущности, названные с большой буквы? То есть, чем
143 | `Average` отличается от `average`?
144 |
145 | * Что такое псевдоним пакета и как его сделать?
146 |
147 | * Мы скопировали функцию `Average` из главы 7 в наш новый пакет. Создайте
148 | `Min` и `Max` функции для нахождения наименьших и наибольших значений в срезах
149 | дробных чисел типа `float64`.
150 |
151 | * Напишите документацию к функциям `Min` и `Max` из предыдущей задачи.
152 |
--------------------------------------------------------------------------------
/_book/chapter-02-your-first-program.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Ваша первая программа"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Традиционно первая программа, с которой начинается изучение любого языка
8 | программирования, называется «Hello World» — эта программа просто выводит в
9 | консоль строку `Hello World`. Давайте напишем её с помощью Go.
10 |
11 | Сначала создадим новую директорию, в которой будем хранить нашу программу.
12 | Установщик, о котором говорилось в первой главе, создал в вашей домашней
13 | директории каталог `Go`. Теперь создайте директорию под названием
14 | `~/Go/src/golang-book/chapter2` (где `~` означает вашу домашнюю директорию).
15 | Вы можете сделать это из терминала с помощью следующих команд:
16 |
17 | mkdir Go/src/golang-book
18 | mkdir Go/src/golang-book/chapter2
19 |
20 | Используя текстовый редактор, введите следующее:
21 |
22 | package main
23 |
24 | import "fmt"
25 |
26 | // this is a comment
27 |
28 | func main() {
29 | fmt.Println("Hello World")
30 | }
31 |
32 | Убедитесь, что содержимое файла идентично показанному здесь примеру, и сохраните
33 | его под именем `main.go` в созданной ранее директории. Затем откройте новое окно
34 | терминала и введите:
35 |
36 | cd Go/src/golang-book/chapter2
37 | go run main.go
38 |
39 | В окне терминала вы должны увидеть сообщение `Hello World`. Команда `go run`
40 | берет указанные файлы (разделенные пробелами), компилирует их в исполняемые
41 | файлы, сохраняет во временной директории и запускает. Если вы не увидели
42 | `Hello World`, то, вероятно, где-то была допущена ошибка, и компилятор подскажет
43 | вам, где конкретно. Как и большинство компиляторов, компилятор Go крайне
44 | педантичен и не прощает ошибок.
45 |
46 | ## Как читать программу на Go
47 |
48 | Теперь давайте рассмотрим программу более детально. Программы на Go читаются
49 | сверху вниз, слева направо (как книга). Первая строка гласит:
50 |
51 | package main
52 |
53 | Это называется «определением пакета». Любая Go программа должна начинаться с определения
54 | имени пакета. Пакеты — это подход Go к организации и повторному использованию
55 | кода. Есть два типа программ на Go: исполняемые файлы и разделяемые библиотеки.
56 | Исполняемые файлы являются видом программ, которые можно запустить прямо из
57 | терминала (в Windows их имя заканчивается на `.exe`). Библиотеки являются
58 | коллекциями кода, который можно использовать из других программ. Детальнее мы
59 | будем рассматривать библиотеки чуть позже, а пока просто не забудьте включать эту
60 | строку в программы, которые вы пишете.
61 |
62 | Далее следует пустая строка. Компьютер представляет новые строки специальным
63 | символом (или несколькими символами). Символы новой строки, пробелы и символы
64 | табуляции называются разделителями. Go не обращает на них внимания, но мы используем
65 | их, чтобы облегчить себе чтение программы (вы можете удалить эту строку и
66 | убедиться, что программа ведет себя в точности как раньше).
67 |
68 | Дальше следует это:
69 |
70 | import "fmt"
71 |
72 | Ключевое слово `import` позволяет подключить сторонние пакеты для использования
73 | их функциональности в нашей программе. Пакет `fmt` (сокращение от format) реализует
74 | форматирование для входных и выходных данных. Учитывая то, что мы только что
75 | узнали о пакетах, как вы думаете, что будет содержаться в верхней части файлов
76 | пакета `fmt`?
77 |
78 | Обратите внимание, что `fmt` взят в двойные кавычки. Использование двойных
79 | кавычек называется «строковым литералом», который в свою очередь является видом
80 | «выражения». Строки в Go представляют собой набор символов (букв, чисел, …)
81 | определенной длины. Строки мы рассмотрим детально в следующей главе, а сейчас
82 | главное иметь в виду, что за открывающим символом `"` в конечном итоге должен
83 | последовать и закрывающий. Всё, что находится между ними, будет являться строкой
84 | (символ `"` сам по себе не является частью строки).
85 |
86 | Строка, начинающаяся с `//`, является комментарием. Комментарии игнорируются
87 | компилятором Go и служат пояснениями исключительно для вас (или для тех, кто будет потом
88 | читать ваш код). Go поддерживает два вида комментариев: `//` превращает в
89 | комментарий весь текст до конца строки и `/* */`, где комментарием является всё,
90 | что содержится между символами `*` (включая переносы строк).
91 |
92 | Далее можно увидеть объявление функции:
93 |
94 | func main() {
95 | fmt.Println("Hello World")
96 | }
97 |
98 | Функции являются кирпичиками программы на Go. Они имеют входы, выходы и ряд
99 | действий, называемых операторами, расположенных в определенном порядке. Любая
100 | функция начинается с ключевого слова `func` за которым следуют: имя функции (в
101 | нашем случае `main`), список из нуля и более параметров в круглых скобках, возвращаемый тип (если
102 | есть) и само «тело», заключенное в фигурные скобки. Наша функция не имеет
103 | входных параметров, ничего не возвращает и содержит всего один оператор. Имя
104 | `main` является особенным, эта функция будет вызываться сама при запуске
105 | программы.
106 |
107 | Заключительной частью нашей программы является эта строка:
108 |
109 | fmt.Println("Hello World")
110 |
111 | Этот оператор содержит три части: доступ к функции пакета `fmt` под
112 | названием `Println` (Print line), затем создание новой строки, содержащей
113 | `Hello World`, и вызов функции с этой строкой в качестве первого и
114 | единственного аргумента.
115 |
116 | На данный момент вы уже можете быть немного перегружены количеством новых
117 | терминов. Иногда полезно не спеша прочесть вашу программу вслух. Программу,
118 | которую мы только что написали, можно прочитать следующим образом:
119 |
120 | > Создать новую исполняемую программу, которая использует библиотеку `fmt` и
121 | > содержит функцию `main`. Эта функция не имеет аргументов, ничего не
122 | > возвращает и делает следующее: использует функцию `Println` из библиотеки `fmt`
123 | > и вызывает её, передавая один аргумент — строку `Hello World`.
124 |
125 | Функция `Println` выполняет основную работу в этой программе. Вы можете узнать о
126 | ней больше, набрав в терминале команду:
127 |
128 | godoc fmt Println
129 |
130 | Среди прочей информации вы должны увидеть это:
131 |
132 | Println formats using the default formats for its operands and writes to
133 | standard output. Spaces are always added between operands and a newline is
134 | appended. It returns the number of bytes written and any write error
135 | encountered.
136 |
137 | Go — очень хорошо документированный язык, но эта документация может быть трудна
138 | для понимания, если вы до этого не были знакомы с другими языками программирования. Тем
139 | не менее, команда `godoc` очень полезна для начала поиска ответов на
140 | возникающие вопросы.
141 |
142 | Сейчас документация говорит нам, что вызов `Println` пошлет передаваемые ей
143 | данные на стандартный вывод — терминал, вы сейчас работаете в нём. Эта функция
144 | является причиной, по которой `Hello World` отображается на экране.
145 |
146 | В следующей главе вы поймете, каким образом Go хранит и представляет вещи вроде
147 | `Hello World` с помощью типов.
148 |
149 | ## Задачи
150 |
151 | * Что такое разделитель?
152 |
153 | * Что такое комментарий? Назовите два способа записи комментариев.
154 |
155 | * Наша программа начиналась с `package main`. С чего начинаются файлы в пакете
156 | `fmt`?
157 |
158 | * Мы использовали функцию `Println` из пакета `fmt`. Если бы мы хотели
159 | использовать функцию `Exit` из пакета `os`, что бы для этого потребовалось сделать?
160 |
161 | * Измените написанную программу так, чтобы вместо `Hello World` она выводила
162 | `Hello, my name is` вместе с вашем именем.
163 |
--------------------------------------------------------------------------------
/_book/chapter-09-structs-and-interfaces.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Структуры и интерфейсы"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Несмотря на то, что вполне можно писать программы на Go используя только
8 | встроенные типы, в какой-то момент это станет очень утомительным занятием. Вот
9 | пример — программа, которая взаимодействует с фигурами:
10 |
11 | package main
12 |
13 | import ("fmt"; "math")
14 |
15 | func distance(x1, y1, x2, y2 float64) float64 {
16 | a := x2 - x1
17 | b := y2 - y1
18 | return math.Sqrt(a*a + b*b)
19 | }
20 | func rectangleArea(x1, y1, x2, y2 float64) float64 {
21 | l := distance(x1, y1, x1, y2)
22 | w := distance(x1, y1, x2, y1)
23 | return l * w
24 | }
25 | func circleArea(x, y, r float64) float64 {
26 | return math.Pi * r*r
27 | }
28 | func main() {
29 | var rx1, ry1 float64 = 0, 0
30 | var rx2, ry2 float64 = 10, 10
31 | var cx, cy, cr float64 = 0, 0, 5
32 |
33 | fmt.Println(rectangleArea(rx1, ry1, rx2, ry2))
34 | fmt.Println(circleArea(cx, cy, cr))
35 | }
36 |
37 | Отслеживание всех переменных мешает нам понять, что делает программа, и
38 | наверняка приведет к ошибкам.
39 |
40 | ## Структуры
41 |
42 | С помощью структур эту программу можно сделать гораздо лучше. Структура — это
43 | тип, содержащий именованные поля. Например, мы можем представить круг таким
44 | образом:
45 |
46 | type Circle struct {
47 | x float64
48 | y float64
49 | r float64
50 | }
51 |
52 | Ключевое слово `type` вводит новый тип. За ним следует имя нового типа
53 | (`Circle`) и ключевое слово `struct`, которое говорит, что мы определяем
54 | структуру и список полей внутри фигурных скобок. Каждое поле имеет имя и тип.
55 | Как и с функциями, мы можем объединять поля одного типа:
56 |
57 | type Circle struct {
58 | x, y, r float64
59 | }
60 |
61 | ## Инициализация
62 |
63 | Мы можем создать экземпляр нового типа `Circle` несколькими способами:
64 |
65 | var c Circle
66 |
67 | Подобно другим типами данных, будет создана локальная переменная типа `Circle`,
68 | чьи поля по умолчанию будут равны нулю (`0` для `int`, `0.0` для `float`, `""`
69 | для `string`, `nil` для указателей, …). Также, для создания экземпляра можно
70 | использовать функцию `new`.
71 |
72 | c := new(Circle)
73 |
74 | Это выделит память для всех полей, присвоит каждому из них нулевое значение и
75 | вернет указатель (`*Circle`). Часто, при создании структуры мы хотим присвоить
76 | полям структуры какие-нибудь значения. Существует два способа сделать это.
77 | Первый способ:
78 |
79 | c := Circle{x: 0, y: 0, r: 5}
80 |
81 | Второй способ — мы можем опустить имена полей, если мы знаем порядок в котором
82 | они определены:
83 |
84 | c := Circle{0, 0, 5}
85 |
86 | ## Поля
87 |
88 | Получить доступ к полям можно с помощью оператора `.` (точка):
89 |
90 | fmt.Println(c.x, c.y, c.r)
91 | c.x = 10
92 | c.y = 5
93 |
94 | Давайте изменим функцию `circleArea` так, чтобы она использовала структуру
95 | `Circle`:
96 |
97 | func circleArea(c Circle) float64 {
98 | return math.Pi * c.r*c.r
99 | }
100 |
101 | В функции `main` у нас будет:
102 |
103 | c := Circle{0, 0, 5}
104 | fmt.Println(circleArea(c))
105 |
106 | Очень важно помнить о том, что аргументы в Go всегда копируются. Если мы
107 | попытаемся изменить любое поле в функции `circleArea`, оригинальная переменная
108 | не изменится. Именно поэтому мы будем писать функции так:
109 |
110 | func circleArea(c *Circle) float64 {
111 | return math.Pi * c.r*c.r
112 | }
113 |
114 | И изменим `main`:
115 |
116 | c := Circle{0, 0, 5}
117 | fmt.Println(circleArea(&c))
118 |
119 | ## Методы
120 |
121 | Несмотря на то, что программа стала лучше, мы все еще можем значительно её
122 | улучшить, используя метод — функцию особого типа:
123 |
124 | func (c *Circle) area() float64 {
125 | return math.Pi * c.r*c.r
126 | }
127 |
128 | Между ключевым словом `func` и именем функции мы добавили «получателя».
129 | Получатель похож на параметр — у него есть имя и тип, но объявление функции
130 | таким способом позволяет нам вызывать функцию с помощью оператора `.`:
131 |
132 | fmt.Println(c.area())
133 |
134 | Это гораздо проще прочесть, нам не нужно использовать оператор `&` (Go
135 | автоматически предоставляет доступ к указателю на `Circle` для этого метода), и
136 | поскольку эта функция может быть использована только для `Circle` мы можем
137 | назвать её просто `area`.
138 |
139 | Давайте сделаем то же самое с прямоугольником:
140 |
141 | type Rectangle struct {
142 | x1, y1, x2, y2 float64
143 | }
144 | func (r *Rectangle) area() float64 {
145 | l := distance(r.x1, r.y1, r.x1, r.y2)
146 | w := distance(r.x1, r.y1, r.x2, r.y1)
147 | return l * w
148 | }
149 |
150 | В `main` будет написано:
151 |
152 | r := Rectangle{0, 0, 10, 10}
153 | fmt.Println(r.area())
154 |
155 | ## Встраиваемые типы
156 |
157 | Обычно, поля структур представляют отношения принадлежности (включения).
158 | Например, у `Circle` (круга) есть `radius` (радиус). Предположим, у нас есть
159 | структура `Person` (личность):
160 |
161 | type Person struct {
162 | Name string
163 | }
164 | func (p *Person) Talk() {
165 | fmt.Println("Hi, my name is", p.Name)
166 | }
167 |
168 | И если мы хотим создать новую структуру `Android`, то можем сделать так:
169 |
170 | type Android struct {
171 | Person Person
172 | Model string
173 | }
174 |
175 | Это будет работать, но мы можем захотеть создать другое отношение. Сейчас у
176 | андроида «есть» личность, можем ли мы описать отношение андроид «является»
177 | личностью? Go поддерживает подобные отношения с помощью встраиваемых типов,
178 | также называемых анонимными полями. Выглядят они так:
179 |
180 | type Android struct {
181 | Person
182 | Model string
183 | }
184 |
185 | Мы использовали тип (`Person`) и не написали его имя. Объявленная таким способом
186 | структура доступна через имя типа:
187 |
188 | a := new(Android)
189 | a.Person.Talk()
190 |
191 | Но мы также можем вызвать любой метод `Person` прямо из `Android`:
192 |
193 | a := new(Android)
194 | a.Talk()
195 |
196 | Это отношение работает достаточно интуитивно: личности могут говорить, андроид
197 | это личность, значит андроид может говорить.
198 |
199 | ## Интерфейсы
200 |
201 | Вы могли заметить, что названия методов для вычисления площади круга и
202 | прямоугольника совпадают. Это было сделано не случайно. И в реальной жизни и в
203 | программировании отношения могут быть очень похожими. В Go есть способ сделать
204 | эти случайные сходства явными с помощью типа называемого интерфейсом. Пример
205 | интерфейса для фигуры (`Shape`):
206 |
207 | type Shape interface {
208 | area() float64
209 | }
210 |
211 | Как и структуры, интерфейсы создаются с помощью ключевого слова `type`, за
212 | которым следует имя интерфейса и ключевое слово `interface`. Однако, вместо того,
213 | чтобы определять поля, мы определяем «множество методов». Множество методов - это
214 | список методов, которые будут использоваться для «реализации» интерфейса.
215 |
216 | В нашем случае у `Rectangle` и `Circle` есть метод `area`, который возвращает
217 | `float64`, получается они оба реализуют интерфейс `Shape`. Само по себе это не
218 | очень полезно, но мы можем использовать интерфейсы как аргументы в функциях:
219 |
220 | func totalArea(shapes ...Shape) float64 {
221 | var area float64
222 | for _, s := range shapes {
223 | area += s.area()
224 | }
225 | return area
226 | }
227 |
228 | Мы будем вызывать эту функцию так:
229 |
230 | fmt.Println(totalArea(&c, &r))
231 |
232 | Интерфейсы также могут быть использованы в качестве полей:
233 |
234 | type MultiShape struct {
235 | shapes []Shape
236 | }
237 |
238 | Мы можем даже хранить в `MultiShape` данные `Shape`, определив в ней метод
239 | `area`:
240 |
241 | func (m *MultiShape) area() float64 {
242 | var area float64
243 | for _, s := range m.shapes {
244 | area += s.area()
245 | }
246 | return area
247 | }
248 |
249 | Теперь `MultiShape` может содержать `Circle`, `Rectangle` и даже другие
250 | `MultiShape`.
251 |
252 | ## Задачи
253 |
254 | * Какая разница между методом и функцией?
255 |
256 | * В каких случаях могут пригодиться встроенные (скрытые) поля?
257 |
258 | * Добавьте новый метод `perimeter` в интерфейс `Shape`, который будет
259 | вычислять периметр фигуры. Имплементируйте этот метод для `Circle` и
260 | `Rectangle`.
261 |
--------------------------------------------------------------------------------
/_book/chapter-05-control-structures.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Управление потоком"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Теперь, когда мы знаем про переменные, самое время написать что-нибудь полезное.
8 | Сначала создадим программу, которая по очереди с новой строки выводит числа от
9 | 1 до 10. Наших знаний достаточно для того, чтобы написать эту программу так:
10 |
11 | package main
12 |
13 | import "fmt"
14 |
15 | func main() {
16 | fmt.Println(1)
17 | fmt.Println(2)
18 | fmt.Println(3)
19 | fmt.Println(4)
20 | fmt.Println(5)
21 | fmt.Println(6)
22 | fmt.Println(7)
23 | fmt.Println(8)
24 | fmt.Println(9)
25 | fmt.Println(10)
26 | }
27 |
28 | или так:
29 |
30 | package main
31 | import "fmt"
32 |
33 | func main() {
34 | fmt.Println(`1
35 | 2
36 | 3
37 | 4
38 | 5
39 | 6
40 | 7
41 | 8
42 | 9
43 | 10`)
44 | }
45 |
46 | Но писать это будет довольно утомительно, так что нам нужен лучший способ
47 | несколько раз повторить определенный набор действий.
48 |
49 | ## For
50 |
51 | Оператор `for` даёт возможность повторять список инструкций (блок) определённое
52 | количество раз. Давайте перепишем предыдущую программу, используя оператор `for`:
53 |
54 | package main
55 |
56 | import "fmt"
57 |
58 | func main() {
59 | i := 1
60 | for i <= 10 {
61 | fmt.Println(i)
62 | i = i + 1
63 | }
64 | }
65 |
66 | Сначала создается переменная `i`, хранящая число, которое нужно вывести на
67 | экран. Затем с помощью ключевого слова `for` создается цикл, указывается
68 | условное выражение, которое может принимать значение `true` или `false`, и, наконец, сам блок
69 | для выполнения. Цикл for работает следующим образом:
70 |
71 | * оценивается (выполняется) условное выражение `i <= 10` («i меньше или равно
72 | десяти»). Если оно истинно, выполняются инструкции внутри блока. В противном
73 | случае управление переходит следующей после блока строке кода (в нашем случае
74 | после цикла ничего нет, поэтому совершается выход из программы);
75 |
76 | * после запуска всех инструкций внутри блока мы возвращаемся в начало цикла и
77 | повторяем первый шаг.
78 |
79 | Строка `i = i + 1` очень важна - без неё выражение `i <= 10` всегда будет `true`,
80 | и выполнение программы никогда не завершится (это называется бесконечным
81 | циклом).
82 |
83 | Следующий пример показывает выполнение программы точно так же, как это делает
84 | компьютер:
85 |
86 | * создать переменную `i` со значением 1;
87 | * `i` меньше или равно `10`? да;
88 | * вывести `i`;
89 | * присвоить `i` значение `i + 1` (теперь равно 2);
90 | * `i` меньше или равно `10`? да;
91 | * вывести `i`;
92 | * присвоить `i` значение `i + 1` (теперь равно 3);
93 | * …
94 | * присвоить `i` значение `i + 1` (теперь равно 11);
95 | * `i` меньше или равно `10`? нет;
96 | * больше нечего делать, выходим.
97 |
98 | В других языках программирования существуют разные виды циклов (while, do,
99 | until, foreach, …). У Go вид цикла один, но он может использоваться в разных
100 | случаях. Предыдущую программу можно также записать следующим образом:
101 |
102 | func main() {
103 | for i := 1; i <= 10; i++ {
104 | fmt.Println(i)
105 | }
106 | }
107 |
108 | Теперь условное значение включает в себя также и две другие инструкции,
109 | разделенные точкой с запятой. Сначала инициализируется переменная, затем
110 | выполняется условное выражение, и в завершении переменная «инкрементируется»
111 | (добавление 1 к значению переменной является настолько распространённым
112 | действием, что для этого существует специальный оператор: `++`; аналогично
113 | вычитание 1 может быть выполнено с помощью `--`).
114 |
115 | В следующих главах мы увидим и другие способы использования циклов.
116 |
117 | ## If
118 |
119 | Давайте изменим программу так, чтобы вместо простого вывода чисел 1–10 она также
120 | указывала, является ли число чётным или нечётным. Вроде этого:
121 |
122 | 1 odd
123 | 2 even
124 | 3 odd
125 | 4 even
126 | 5 odd
127 | 6 even
128 | 7 odd
129 | 8 even
130 | 9 odd
131 | 10 even
132 |
133 | Для начала нам нужен способ узнать, является ли число чётным или нечётным. Самый
134 | простой способ — это разделить число на 2. Если остатка от деления не будет,
135 | значит число чётное, иначе — нечётное. Так как же найти остаток от деления на
136 | Go? Для этого существует оператор `%`. Например:
137 |
138 | * `1 % 2` равно `1`;
139 | * `2 % 2` равно `0`;
140 | * `3 % 2` равно `1` и так далее.
141 |
142 | Далее нам нужен способ, чтобы выполнять действия в зависимости от условия. Для этого мы
143 | используем оператор `if`:
144 |
145 | if i % 2 == 0 {
146 | // even
147 | } else {
148 | // odd
149 | }
150 |
151 | Оператор `if` аналогичен оператору `for` в том, что он выполняет блок в
152 | зависимости от условия. Оператор также может иметь необязательную `else` часть.
153 | Если условие истинно, выполняется блок, расположенный после условия, иначе же
154 | этот блок пропускается и выполняется блок `else`, если он присутствует.
155 |
156 | Еще условия могут содержать `else if` часть:
157 |
158 | if i % 2 == 0 {
159 | // divisible by 2
160 | } else if i % 3 == 0 {
161 | // divisible by 3
162 | } else if i % 5 == 0 {
163 | // divisible by 5
164 | }
165 |
166 | Условия выполняются сверху вниз, и первое условие, которое окажется истинным,
167 | приведет в исполнение связанный с ним блок.
168 |
169 | Собрав всё вместе, мы получим:
170 |
171 | func main() {
172 | for i := 1; i <= 10; i++ {
173 | if i % 2 == 0 {
174 | fmt.Println(i, "even")
175 | } else {
176 | fmt.Println(i, "odd")
177 | }
178 | }
179 | }
180 |
181 | Давайте рассмотрим эту программу:
182 |
183 | * Создать переменную `i` типа `int` и присвоить ей значение `1`;
184 | * `i` меньше или равно `10`? Да - перейти в блок;
185 | * остаток от `i` ÷ `2` равен `0`? Нет - переходим к блоку `else`;
186 | * вывести `i` вместе с `odd`;
187 | * инкрементировать `i` (оператор после условия);
188 | * `i` меньше или равно `10`? Да - перейти в блок;
189 | * остаток от `i` ÷ `2` равен `0`? Да - переходим к блоку `if`;
190 | * вывести `i` вместе с `even`;
191 | * …
192 |
193 | Оператор деления с остатком (деление по модулю), который редко можно увидеть за
194 | пределами начальной школы, оказывается действительно полезным при
195 | программировании. Он будет встречаться везде - от раскрашивания таблиц зеброй до
196 | секционирования наборов данных.
197 |
198 | ## Switch
199 |
200 | Предположим, мы захотели написать программу, которая печатала бы английские
201 | названия для чисел. С использованием того, что мы знали до текущего момента, это могло бы
202 | выглядеть примерно так:
203 |
204 | if i == 0 {
205 | fmt.Println("Zero")
206 | } else if i == 1 {
207 | fmt.Println("One")
208 | } else if i == 2 {
209 | fmt.Println("Two")
210 | } else if i == 3 {
211 | fmt.Println("Three")
212 | } else if i == 4 {
213 | fmt.Println("Four")
214 | } else if i == 5 {
215 | fmt.Println("Five")
216 | }
217 |
218 | Но эта запись слишком громоздка. Go содержит в себе другой оператор, позволяющий
219 | делать такие вещи проще: оператор `switch` (переключатель). С ним программа
220 | может выглядеть так:
221 |
222 | switch i {
223 | case 0: fmt.Println("Zero")
224 | case 1: fmt.Println("One")
225 | case 2: fmt.Println("Two")
226 | case 3: fmt.Println("Three")
227 | case 4: fmt.Println("Four")
228 | case 5: fmt.Println("Five")
229 | default: fmt.Println("Unknown Number")
230 | }
231 |
232 | Переключатель начинается с ключевого слова `switch`, за которым следует выражение
233 | (в нашем случае `i`) и серия возможных значений (`case`). Значение выражения по
234 | очереди сравнивается с выражениями, следующими после ключевого слова `case`.
235 | Если они оказываются равны, то выполняется действие, описанное после `:`.
236 |
237 | Как и условия, обход возможных значений осуществляется сверху вниз, и выбирается
238 | первое значение, которое сошлось с выражением. Переключатель также поддерживает
239 | действие по умолчанию, которое будет выполнено в случае, если не подошло ни одно
240 | из возможных значений (напоминает `else` в операторе `if`).
241 |
242 | Таковы основные операторы управления потоком. Дополнительные операторы будут
243 | рассмотрены в следующих главах.
244 |
245 | ## Задачи
246 |
247 | * Что делает следующий код?
248 |
249 | ```
250 | i := 10
251 |
252 | if i > 10 {
253 | fmt.Println("Big")
254 | } else {
255 | fmt.Println("Small")
256 | }
257 | ```
258 |
259 | * Напишите программу, которая выводит числа от 1 до 100, которые делятся на 3.
260 | (3, 6, 9, …).
261 |
262 | * Напишите программу, которая выводит числа от 1 до 100. Но для кратных трём
263 | нужно вывести «Fizz» вместо числа, для кратных пяти - «Buzz», а для
264 | кратных как трём, так и пяти — «FizzBuzz».
265 |
--------------------------------------------------------------------------------
/_book/chapter-10-concurrency.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Многопоточность"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Очень часто, большие приложения состоят из множества небольших подпрограмм.
8 | Например, web-сервер принимает запросы от браузера и отправляет HTML страницы в
9 | ответ. Каждый такой запрос выполняется как отдельная небольшая программа.
10 |
11 | Такой способ идеально подходит для подобных приложений, так как обеспечивает
12 | возможность одновременного запуска множества более мелких компонентов (обработки
13 | нескольких запросов одновременно, в случае веб-сервера). Одновременное
14 | выполнение более чем одной задачи известно как многопоточность. Go имеет богатую
15 | функциональность для работы с многопоточностью, в частности, такие инструменты как
16 | горутины и каналы.
17 |
18 | ## Горутины
19 |
20 | Горутина — это функция, которая может работать параллельно с другими функциями.
21 | Для создания горутины используется ключевое слово `go`, за которым следует
22 | вызов функции.
23 |
24 | package main
25 |
26 | import "fmt"
27 |
28 | func f(n int) {
29 | for i := 0; i < 10; i++ {
30 | fmt.Println(n, ":", i)
31 | }
32 | }
33 |
34 | func main() {
35 | go f(0)
36 | var input string
37 | fmt.Scanln(&input)
38 | }
39 |
40 | Эта программа состоит из двух горутин. Функция `main`, сама по себе, является
41 | горутиной. Вторая горутина создаётся, когда мы вызываем `go f(0)`. Обычно, при
42 | вызове функции, программа выполнит все конструкции внутри вызываемой функции, а
43 | только потом перейдет к следующей после вызова, строке. С горутиной программа
44 | немедленно прейдет к следующей строке, не дожидаясь, пока вызываемая функция
45 | завершится. Вот почему здесь присутствует вызов `Scanln`, без него программа
46 | завершится еще перед тем, как ей удастся вывести числа.
47 |
48 | Горутины очень легкие, мы можем создавать их тысячами. Давайте изменим
49 | программу так, чтобы она запускала 10 горутин:
50 |
51 | func main() {
52 | for i := 0; i < 10; i++ {
53 | go f(i)
54 | }
55 | var input string
56 | fmt.Scanln(&input)
57 | }
58 |
59 | При запуске вы наверное заметили, что все горутины выполняются
60 | последовательно, а не одновременно, как вы того ожидали. Давайте добавим
61 | небольшую задержку функции с помощью функции `time.Sleep` и `rand.Intn`:
62 |
63 | package main
64 |
65 | import (
66 | "fmt"
67 | "time"
68 | "math/rand"
69 | )
70 |
71 | func f(n int) {
72 | for i := 0; i < 10; i++ {
73 | fmt.Println(n, ":", i)
74 | amt := time.Duration(rand.Intn(250))
75 | time.Sleep(time.Millisecond * amt)
76 | }
77 | }
78 | func main() {
79 | for i := 0; i < 10; i++ {
80 | go f(i)
81 | }
82 | var input string
83 | fmt.Scanln(&input)
84 | }
85 |
86 | `f` выводит числа от 0 до 10, ожидая от 0 до 250 мс после каждой операции
87 | вывода. Теперь горутины должны выполняться одновременно.
88 |
89 | ## Каналы
90 |
91 | Каналы обеспечивают возможность общения нескольких горутин друг с другом,
92 | чтобы синхронизировать их выполнение. Вот пример программы с использованием
93 | каналов:
94 |
95 | package main
96 |
97 | import (
98 | "fmt"
99 | "time"
100 | )
101 |
102 | func pinger(c chan string) {
103 | for i := 0; ; i++ {
104 | c <- "ping"
105 | }
106 | }
107 | func printer(c chan string) {
108 | for {
109 | msg := <- c
110 | fmt.Println(msg)
111 | time.Sleep(time.Second * 1)
112 | }
113 | }
114 | func main() {
115 | var c chan string = make(chan string)
116 |
117 | go pinger(c)
118 | go printer(c)
119 |
120 | var input string
121 | fmt.Scanln(&input)
122 | }
123 |
124 | Программа будет постоянно выводить «ping» (нажмите enter, чтобы её остановить).
125 | Тип канала представлен ключевым словом `chan`, за которым следует тип, который
126 | будет передаваться по каналу (в данном случае мы передаем строки). Оператор `<-`
127 | (стрелка влево) используется для отправки и получения сообщений по каналу.
128 | Конструкция `c <- "ping"` означает отправку `"ping"`, а `msg := <- c` — его
129 | получение и сохранение в переменную `msg`. Строка с `fmt` может быть записана
130 | другим способом: `fmt.Println(<-c)`, тогда можно было бы удалить предыдущую
131 | строку.
132 |
133 | Данное использование каналов позволяет синхронизировать две горутины. Когда
134 | `pinger` пытается послать сообщение в канал, он ожидает, пока `printer` будет
135 | готов получить сообщение. Такое поведение называется блокирующим. Давайте
136 | добавим ещё одного отправителя сообщений в программу и посмотрим, что будет.
137 | Добавим эту функцию:
138 |
139 | func ponger(c chan string) {
140 | for i := 0; ; i++ {
141 | c <- "pong"
142 | }
143 | }
144 |
145 | и изменим функцию `main`:
146 |
147 | func main() {
148 | var c chan string = make(chan string)
149 |
150 | go pinger(c)
151 | go ponger(c)
152 | go printer(c)
153 |
154 | var input string
155 | fmt.Scanln(&input)
156 | }
157 |
158 | Теперь программа будет выводить на экран то `ping`, то `pong` по очереди.
159 |
160 | ## Направление каналов
161 |
162 | Мы можем задать направление передачи сообщений в канале, сделав его только
163 | отправляющим или принимающим. Например, мы можем изменить функцию `pinger`:
164 |
165 | func pinger(c chan<- string)
166 |
167 | и канал `c` будет только отправлять сообщение. Попытка получить сообщение из
168 | канала `c` вызовет ошибку компилирования. Также мы можем изменить функцию
169 | `printer`:
170 |
171 | func printer(c <-chan string)
172 |
173 | Существуют и двунаправленные каналы, которые могут быть переданы в функцию,
174 | принимающую только принимающие или отправляющие каналы. Но только отправляющие или
175 | принимающие каналы не могут быть переданы в функцию, требующую двунаправленного
176 | канала!
177 |
178 | ## Оператор Select
179 |
180 | В языке Go есть специальный оператор `select` который работает как `switch`, но
181 | для каналов:
182 |
183 | func main() {
184 | c1 := make(chan string)
185 | c2 := make(chan string)
186 |
187 | go func() {
188 | for {
189 | c1 <- "from 1"
190 | time.Sleep(time.Second * 2)
191 | }
192 | }()
193 | go func() {
194 | for {
195 | c2 <- "from 2"
196 | time.Sleep(time.Second * 3)
197 | }
198 | }()
199 | go func() {
200 | for {
201 | select {
202 | case msg1 := <- c1:
203 | fmt.Println(msg1)
204 | case msg2 := <- c2:
205 | fmt.Println(msg2)
206 | }
207 | }
208 | }()
209 |
210 | var input string
211 | fmt.Scanln(&input)
212 | }
213 |
214 | Эта программа выводит «from 1» каждые 2 секунды и «from 2» каждые 3 секунды.
215 | Оператор `select` выбирает первый готовый канал, и получает сообщение из него,
216 | или же передает сообщение через него. Когда готовы несколько каналов, получение
217 | сообщения происходит из случайно выбранного готового канала. Если же ни один из
218 | каналов не готов, оператор блокирует ход программы до тех пор, пока какой-либо
219 | из каналов будет готов к отправке или получению.
220 |
221 | Обычно `select` используется для таймеров:
222 |
223 | select {
224 | case msg1 := <- c1:
225 | fmt.Println("Message 1", msg1)
226 | case msg2 := <- c2:
227 | fmt.Println("Message 2", msg2)
228 | case <- time.After(time.Second):
229 | fmt.Println("timeout")
230 | }
231 |
232 | `time After` создаёт канал, по которому посылаем метки времени с заданным
233 | интервалом. В данном случае мы не заинтересованы в значениях временных меток,
234 | поэтому мы не сохраняем его в переменные. Также мы можем задать команды, которые
235 | выполняются по умолчанию, используя конструкцию `default`:
236 |
237 | select {
238 | case msg1 := <- c1:
239 | fmt.Println("Message 1", msg1)
240 | case msg2 := <- c2:
241 | fmt.Println("Message 2", msg2)
242 | case <- time.After(time.Second):
243 | fmt.Println("timeout")
244 | default:
245 | fmt.Println("nothing ready")
246 | }
247 |
248 | Выполняемые по умолчанию команды исполняются сразу же, если все каналы заняты.
249 |
250 | ## Буферизированный канал
251 |
252 | При инициализации канала можно использовать второй параметр:
253 |
254 | c := make(chan int, 1)
255 |
256 | и мы получим буферизированный канал с ёмкостью `1`. Обычно каналы работают
257 | синхронно - каждая из сторон ждёт, когда другая сможет получить или передать
258 | сообщение. Но буферизованный канал работает асинхронно — получение или отправка
259 | сообщения не заставляют стороны останавливаться. Но канал теряет пропускную
260 | способность, когда он занят, в данном случае, если мы отправим в канал 1
261 | сообщение, то мы не сможем отправить туда ещё одно до тех пор, пока первое не
262 | будет получено.
263 |
264 | ## Задачи
265 |
266 | * Как задать направление канала?
267 |
268 | * Напишите собственную функцию `Sleep`, используя `time.After`
269 |
270 | * Что такое буферизированный канал? Как создать такой канал с ёмкостью в 20 сообщений?
271 |
--------------------------------------------------------------------------------
/_book/chapter-04-variables.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Переменные"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Ранее в этой книге мы имели дело с литеральными значениями (числами, строками и
8 | т.д.), но программы с одними только литералами фактически бесполезны. Для того,
9 | чтобы сделать по-настоящему полезные программы, нам нужно узнать о двух важных
10 | вещах: переменных и инструкциях, управляющих ходом выполнения. В этой главе
11 | будут рассмотрены переменные.
12 |
13 | Переменная - это именованное место хранения какого-то типа данных. Давайте изменим
14 | программу, которую мы написали в главе 2 так, чтобы там использовались
15 | переменные.
16 |
17 | package main
18 |
19 | import "fmt"
20 |
21 | func main() {
22 | var x string = "Hello World"
23 | fmt.Println(x)
24 | }
25 |
26 | Обратите внимание, что мы по-прежнему используем строковый литерал из
27 | оригинальной программы, но вместо того, чтобы напрямую передать его в функцию
28 | `Println`, мы присваиваем его переменной. Переменные в Go создаются с помощью
29 | ключевого слова `var`, за которым следуют имя переменной (`x`), тип (`string`) и
30 | присваиваемое значение (`Hello World`). Последний шаг не обязателен, поэтому
31 | программа может быть переписана так:
32 |
33 | package main
34 |
35 | import "fmt"
36 |
37 | func main() {
38 | var x string
39 | x = "Hello World"
40 | fmt.Println(x)
41 | }
42 |
43 | Переменные в Go похожи на переменные в алгебре, но есть несколько различий.
44 | Во-первых, когда мы видим символ `=`, то по привычке читаем его как «х равен
45 | строке Hello World». Нет ничего неверного в том, чтобы читать программу таким
46 | образом, но лучше читать это как «х принимает значение строки Hello World» или
47 | «x присваивается строка Hello World». Это различие важно потому, что
48 | переменные могут менять свои значения во время выполнения программы
49 | (как понятно по их названию). Попробуйте сделать следующее:
50 |
51 | package main
52 |
53 | import "fmt"
54 |
55 | func main() {
56 | var x string
57 | x = "first"
58 | fmt.Println(x)
59 | x = "second"
60 | fmt.Println(x)
61 | }
62 |
63 | На самом деле вы можете сделать даже так:
64 |
65 | var x string
66 | x = "first "
67 | fmt.Println(x)
68 | x = x + "second"
69 | fmt.Println(x)
70 |
71 | Эта программа будет бессмысленной, если вы будете читать её как теорему из
72 | алгебры. Но она обретет смысл, если вы будете внимательно читать программу как
73 | список команд. Когда мы видим `x = x + "second"`, то должны читать это так:
74 | «Присвоить конкатенацию значения переменной x и литерала строки переменной x».
75 | Операции справа от `=` выполняются первыми, и результат присваивается левой
76 | части.
77 |
78 | Запись `x = x + y` настолько часто встречается в программировании, что в Go есть
79 | специальный оператор присваивания `+=`. Мы можем записать `x = x + "second"`
80 | как `x += "second"`, и результат будет тем же (прочие операторы могут быть
81 | использованы подобным же образом).
82 |
83 | Другое отличие между Go и алгеброй в том, что для равенства используется другой символ: `==`
84 | (два знака равно, один за другим). `==` - это оператор. Как и `+`,
85 | он возвращает логический тип. Например:
86 |
87 | var x string = "hello"
88 | var y string = "world"
89 | fmt.Println(x == y)
90 |
91 | Эта программа напечатает `false`, потому что `hello` отличается от `world`. С другой стороны:
92 |
93 | var x string = "hello"
94 | var y string = "hello"
95 | fmt.Println(x == y)
96 |
97 | напечатает `true`, потому что обе строки одинаковы.
98 |
99 | Если мы хотим присвоить значение переменной при её создании, то можем
100 | использовать сокращенную запись:
101 |
102 | x := "Hello World"
103 |
104 | Обратите внимание на то, что `:` стоит перед `=`, и на отсутствие типа. Тип в
105 | данном случае указывать не обязательно, так как компилятор Go способен определить
106 | тип по литералу, которым мы инициализируем переменную. Тут мы присваиваем
107 | строку, поэтому x будет иметь тип `string`. Компилятор может определить тип и
108 | при использовании `var`:
109 |
110 | var x = "Hello World"
111 |
112 | И так со всеми типами:
113 |
114 | x := 5
115 | fmt.Println(x)
116 |
117 | В общем, желательно всегда использовать краткий вариант написания.
118 |
119 | ## Как назвать переменную
120 |
121 | Правильное именование переменных — важная часть разработки ПО. Имена должны
122 | начинаться с буквы и могут содержать буквы, цифры и знак _ (знак подчеркивания).
123 | Компилятору Go, в принципе, всё равно, как вы назовете переменную, но не
124 | забудьте, что вам (и может быть кому-то еще) потом это придется читать.
125 | Предположим, у нас есть:
126 |
127 | x := "Max"
128 | fmt.Println("My dog's name is", x)
129 |
130 | В этом случае `x` не самое лучшее имя переменной. Лучше было бы так:
131 |
132 | name := "Max"
133 | fmt.Println("My dog's name is", name)
134 |
135 | или даже так:
136 |
137 | dogsName := "Max"
138 | fmt.Println("My dog's name is", dogsName)
139 |
140 | В последнем случае мы использовали специальный способ написания имени
141 | переменной, состоящей из нескольких слов, известный как lower CamelCase (или
142 | camelBack). Первая буква первого слова записывается в нижнем регистре, первая
143 | буква последующих слов записывается в верхнем регистре, всё остальное - в нижнем.
144 |
145 | ## Область видимости
146 |
147 | Вернемся к программе, которую мы рассматривали в начале главы:
148 |
149 | package main
150 |
151 | import "fmt"
152 |
153 | func main() {
154 | var x string = "Hello World"
155 | fmt.Println(x)
156 | }
157 |
158 | Эту программу можно записать следующим образом:
159 |
160 | package main
161 |
162 | import "fmt"
163 |
164 | var x string = "Hello World"
165 |
166 | func main() {
167 | fmt.Println(x)
168 | }
169 |
170 | Мы вынесли переменные за пределы функции main. Это означает, что теперь другие
171 | функции имеют доступ к этой переменной:
172 |
173 | var x string = "Hello World"
174 |
175 | func main() {
176 | fmt.Println(x)
177 | }
178 |
179 | func f() {
180 | fmt.Println(x)
181 | }
182 |
183 | Функция `f` имеет доступ к переменной `x`. Теперь предположим, что вместо этого
184 | мы написали:
185 |
186 | func main() {
187 | var x string = "Hello World"
188 | fmt.Println(x)
189 | }
190 |
191 | func f() {
192 | fmt.Println(x)
193 | }
194 |
195 | Если вы попробуете выполнить эту программу, то получите ошибку:
196 |
197 | .\main.go:11: undefined: x
198 |
199 | Компилятор говорит вам, что переменная `x` внутри функции `f` не существует. Она
200 | существует только внутри функции `main`. Места, где может использоваться
201 | переменная `x`, называются областью видимости переменной. Согласно спецификации
202 | «в Go область видимости ограничена блоками». В основном это значит, что
203 | переменные существуют только внутри текущих фигурных скобок `{` `}` (в блоке),
204 | включая все вложенные скобки (блоки). Область видимости поначалу может запутать
205 | вас, но когда вы увидите больше примеров, то всё станет ясно.
206 |
207 | ## Константы
208 |
209 | Go также поддерживает константы. Константы — это переменные, чьи значения не
210 | могут быть изменены после инициализации. Они создаются таким же образом, как и
211 | переменные, только вместо `var` используется ключевое слово `const`:
212 |
213 | package main
214 |
215 | import "fmt"
216 |
217 | func main() {
218 | const x string = "Hello World"
219 | fmt.Println(x)
220 | }
221 |
222 | А вот этот код:
223 |
224 | const x string = "Hello World"
225 | x = "Some other string"
226 |
227 | вызовет ошибку компиляции:
228 |
229 | .\main.go:7: cannot assign to x
230 |
231 | Константы — хороший способ использовать определенные значения в программе без
232 | необходимости писать их каждый раз. Например, константа `Pi` из пакета `math`.
233 |
234 | ## Определение нескольких переменных
235 |
236 | В Go существует еще одно сокращение на случай, если необходимо определить несколько переменных:
237 |
238 | var (
239 | a = 5
240 | b = 10
241 | c = 15
242 | )
243 |
244 | Используя ключевые слово `var` (или `const`), за которым идут круглые скобки с
245 | одной переменной в каждой строке.
246 |
247 | ## Пример программы
248 |
249 | package main
250 |
251 | import "fmt"
252 |
253 | func main() {
254 | fmt.Print("Enter a number: ")
255 | var input float64
256 | fmt.Scanf("%f", &input)
257 |
258 | output := input * 2
259 |
260 | fmt.Println(output)
261 | }
262 |
263 |
264 | Тут мы используем другую функцию из пакета `fmt`, чтобы считать пользовательский
265 | ввод (`Scanf`). `&input` будет объяснен в следующих главах, а все, что нам нужно
266 | знать сейчас, это то, что `Scanf` заполняет переменную `input` числом, введенным нами.
267 |
268 | ## Задачи
269 |
270 | * Существуют два способа для создания новой переменной. Какие?
271 |
272 | * Какое будет значение у `x` после выполнения `x := 5; x += 1`?
273 |
274 | * Что такое область видимости и как определяется область видимости переменной
275 | в Go?
276 |
277 | * В чем отличие `var` от `const`?
278 |
279 | * Используя пример программы выше напишите программу, переводящую температуру
280 | из градусов Фаренгейта в градусы Цельсия. (`C = (F - 32) * 5/9`)
281 |
282 | * Напишите другую программу для перевода футов в метры (1 фут = 0.3048 метр).
283 |
--------------------------------------------------------------------------------
/_book/chapter-01-gettting-started.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Приступая к работе"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Программирование — это искусство, ремесло и наука о написании программ,
8 | определяющих то, как компьютер будет работать. Эта книга научит вас писать
9 | компьютерные программы с использованием языка программирования, разработанного в
10 | компании Google, под названием Go.
11 |
12 | Go — язык общего назначения с широкими возможностями и понятным синтаксисом.
13 | Благодаря мультиплатформенности, надежной, хорошо документированной стандартной
14 | библиотеке и ориентированности на удобные подходы к самой разработке, Go
15 | является идеальным языком для первых шагов в программировании.
16 |
17 | Процесс разработки приложений на Go (и на большинстве других языков
18 | программирования) довольно прост:
19 |
20 | * сбор требований,
21 | * поиск решения,
22 | * написание кода, реализующего решения,
23 | * компиляция кода в исполняемый файл,
24 | * запуск и тестирование программы.
25 |
26 | Процесс этот итеративный (то есть повторяющийся много раз), и шаги, как правило,
27 | совпадают. Но прежде чем мы напишем нашу первую программу на Go, нужно понять
28 | несколько основных принципов.
29 |
30 | ## Файлы и директории
31 |
32 | Файл представляет собой набор данных, хранящийся в блоке с определенным именем.
33 | Современные операционные системы (такие как Windows или Mac OS X) состоят из
34 | миллионов файлов, содержащих большой объем различной информации — начиная от
35 | текстовых документов и заканчивая программами и мультимедиа-файлами.
36 |
37 | Файлы определенным образом хранятся в компьютере: все они имеют имя,
38 | определенный размер (измеряемый в байтах) и соответствующий тип. Обычно тип
39 | файла определяется по его расширению — части имени, которая стоит после
40 | последней `.`. Например, файл, названный `hello.txt`, имеет расширение `txt`,
41 | а значит содержит текстовую информацию.
42 |
43 | Папки (также называемые директориями) используются для группирования нескольких
44 | файлов.
45 |
46 | ## Терминал
47 |
48 | Большая часть взаимодействия с компьютером сейчас осуществляется с помощью
49 | графического пользовательского интерфейса (GUI). Мы используем клавиатуру, мышь,
50 | сенсорные экраны для взаимодействия с визуальными кнопками и другими
51 | отображаемыми элементами.
52 |
53 | Но так было не всегда. Перед GUI в ходу был терминал — простой текстовый
54 | интерфейс к компьютеру, где вместо работы с кнопками на экране мы вводили
55 | команды и получали ответы.
56 |
57 | И хотя может показаться, что большая часть компьютерного мира оставила терминал
58 | далеко позади как пережиток прошлого, правда в том, что терминал всё еще
59 | остаётся фундаментальным пользовательским интерфейсом, используемым большинством
60 | языков программирования на большинстве компьютеров. Go не исключение, поэтому
61 | прежде чем писать программу на Go, понадобится элементарное понимание того, как
62 | работает терминал.
63 |
64 | ### Windows
65 |
66 | Чтобы вызвать терминал (командную строку) в Windows, нужно нажать комбинацию
67 | клавиш Win+R (удерживая клавишу с логотипом Windows нажмите R), ввести в
68 | появившееся окно `cmd.exe` и нажать Enter. Вы должны увидеть черное окно,
69 | похожее на то, что ниже:
70 |
71 | 
72 |
73 | По умолчанию командная строка запускается из вашей домашней директории (в моём
74 | случае это `C:\Users\caleb`). Вы отдаёте команды компьютеру, набирая их в этом
75 | окне и нажимая Enter. Попробуйте ввести команду `dir`, которая выводит
76 | содержимое текущего каталога на экран. Вы должны увидеть что-то вроде этого:
77 |
78 | C:\Users\caleb>dir
79 | Volume in drive C has no label.
80 | Volume Serial Number is B2F5-F125
81 |
82 | Вы можете изменить текущий каталог с помощью команды `cd`. Например, там
83 | наверняка есть директория под названием `Desktop`. Вы можете посмотреть её
84 | содержимое, набрав `cd Desktop`, а затем `dir`. Чтобы вернуться в домашнюю
85 | директорию, используйте специальное имя `..` (две точки): `cd ..`. Одна точка
86 | обозначает текущий каталог (известен как рабочая директория), так что `cd .`
87 | ничего не сделает. Конечно, существует намного больше команд, которые можно
88 | использовать, но этих будет вполне достаточно для начала.
89 |
90 | ### OSX
91 |
92 | В OSX терминал можно найти, перейдя в Finder → Applications → Utilities →
93 | Terminal. Вы увидите такое окно:
94 |
95 | 
96 |
97 | По умолчанию, командная строка запускается из вашей домашней директории (в моём
98 | случае это `/Users/caleb`). Вы отдаёте команды компьютеру, набирая их в этом
99 | окне и нажимая Enter. Попробуйте ввести команду `ls`, которая выводит содержимое
100 | текущего каталога на экран. Вы должны увидеть что-то вроде этого:
101 |
102 | caleb-min:~ caleb$ ls
103 | Desktop Downloads Movies Pictures
104 | Documents Library Music Public
105 |
106 | Вы можете изменить текущий каталог с помощью команды `cd`. Например, там
107 | наверняка есть директория под названием `Desktop`. Вы можете посмотреть её
108 | содержимое набрав `cd Desktop`, а затем `ls`. Чтобы вернуться в домашнюю
109 | директорию, используйте специальное имя `..` (две точки): `cd ..`. Одна точка
110 | обозначает текущий каталог (известен как рабочая директория), так что `cd .`
111 | ничего не сделает. Конечно, существует намного больше команд, которые можно
112 | использовать, но этих будет вполне достаточно для начала.
113 |
114 | ## Текстовый редактор
115 |
116 | Основным инструментом программиста при разработке программного обеспечения
117 | является текстовый редактор. Текстовые редакторы в целом похожи на программы
118 | обработки текста (такие как Microsoft Word или OpenOffice), но в отличие от последних,
119 | там отсутствует какое-либо форматирование (полужирный, курсив и т.п.), что
120 | делает их ориентированными только на работу с простым текстом. Как в OSX, так и
121 | в Windows, по умолчанию уже присутствует встроенные текстовые редакторы. Но они
122 | очень ограничены в возможностях, поэтому я бы порекомендовал что-нибудь получше.
123 |
124 | Дабы упростить установку, на сайте книги [golang-book.com][1] доступен
125 | специальный инсталлятор. Он установит Go, необходимые инструменты, текстовый редактор и
126 | настроит переменные окружения.
127 |
128 | ### Windows
129 |
130 | Для Windows инсталлятор установит текстовый редактор SciTe. Вы сможете найти его
131 | в меню Пуск → Все программы → Go → SciTe. После запуска вы должны увидеть
132 | такое окно:
133 |
134 | 
135 |
136 | Текстовый редактор содержит большую белую область для ввода текста. Слева от
137 | этой области можно увидеть номера строк. В нижней части окна находится строка
138 | состояния, где отображается информация о файле и вашем текущем местоположении в
139 | нём (сейчас он говорит, что мы находимся у первого символа первой строки,
140 | используется режим вставки текста, а окончания строк обозначаются в
141 | Windows-стиле).
142 |
143 | Вы можете открыть файл, выбрав его в диалоге, находящимся в меню File → Open.
144 | Файлы могут быть сохранены с помощью меню File → Save или File → Save As.
145 |
146 | Так как подобные действия вы будете выполнять достаточно часто, неплохо было
147 | бы узнать сочетания клавиш для быстрого доступа к пунктам меню. Вот самые
148 | распространённые из них:
149 |
150 | * Ctrl + S — сохранить текущий файл
151 |
152 | * Ctrl + X — вырезать выделенный текст (удалить его,
153 | предварительно сохранив в буфере обмена, для возможной вставки позже)
154 |
155 | * Ctrl + C — скопировать выделенный фрагмент текста в
156 | буфер обмена
157 |
158 | * Ctrl + V — вставить текст на место текущего положения
159 | курсора из буфера обмена
160 |
161 | * Используйте клавиши со стрелками для навигации по файлу, Home для
162 | перехода в начало строки, а End для перехода в конец
163 |
164 | * Удерживайте Shift при использовании клавиш навигации, чтобы
165 | выделить фрагмент текста без использования мыши
166 |
167 | * Ctrl + F — открыть диалоговое окно поиска по
168 | содержимому файла
169 |
170 | ### OSX
171 |
172 | Для OSX установщик поставит редактор Text Wrangler:
173 |
174 | 
175 |
176 | Как и Scite на Windows, окно Text Wrangler содержит большую белую область, где
177 | вводится текст. Файлы могут быть открыты при помощи File → Open, а сохранены
178 | с помощью File → Save или File → Save As. Вот некоторые полезные сочетания
179 | клавиш:
180 |
181 | * ⌘ + S — сохранить текущий файл
182 |
183 | * ⌘ + X — вырезать выделенный текст (удалить его,
184 | предварительно сохранив в буфере обмена, для возможной вставки позже)
185 |
186 | * ⌘ + C — скопировать выделенный фрагмент текста в
187 | буфер обмена
188 |
189 | * ⌘ + V — вставить текст на место текущего положения
190 | курсора из буфера обмена
191 |
192 | * Используйте клавиши со стрелками для навигации по файлу
193 |
194 | * ⌘ + F — открыть диалоговое окно поиска по
195 | содержимому файла
196 |
197 |
198 | ## Инструментарий Go
199 |
200 | Go — компилируемый язык программирования. Это означает, что исходный код
201 | (написанный вами код) переводится в язык, понятный компьютеру. Поэтому, прежде
202 | чем написать первую программу на Go, нужно разобраться с его компилятором.
203 |
204 | Инсталлятор установит Go автоматически. Мы будем использовать первую версию
205 | языка. (Больше информации можно найти на http://golang.org/)
206 |
207 | Давайте убедимся, что всё работает. Откроем терминал и введём там:
208 |
209 | go version
210 |
211 | В ответ вы должны увидеть что-то вроде:
212 |
213 | go version go1.0.2
214 |
215 | Ваш номер версии может быть немного другим. Если вы получили ошибку, попробуйте
216 | перезагрузить компьютер.
217 |
218 | Инструментарий Go состоит из нескольких команд и подкоманд. Список всех
219 | доступных команд можно увидеть, набрав:
220 |
221 | go help
222 |
223 | О том, как их использовать, мы узнаем в следующих главах.
224 |
225 | [1]: http://www.golang-book.com
226 |
--------------------------------------------------------------------------------
/_book/chapter-03-types.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Типы"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | В предыдущей главе мы использовали строковый тип данных, чтобы хранить
8 | `Hello World`. Типы данных определяют множество принимаемых значений, описывают,
9 | какие операции могут быть применены к ним, и определяют, как данные будут храниться.
10 | Поскольку типы данных могут быть сложны для понимания, мы попробуем рассмотреть
11 | их подробнее, прежде чем разбираться, как они реализованы в Go.
12 |
13 | Предположим, у вас есть собака по имени Шарик. Тут «Шарик» — это «Собака», этот тип
14 | описывает какой-то набор свойств, присущий всем собакам. Наши рассуждения должны
15 | быть примерно следующие: у собак 4 лапы, Шарик — собака, значит, у Шарика 4 лапы.
16 | Типы данных в языках программирования работают похожим образом: у всех строк есть
17 | длина; `x` — строка, а значит у `x` есть длина.
18 |
19 | В математике мы часто говорим о множествах. Например, `ℝ` (множество всех
20 | вещественных чисел) или `ℕ` (множество всех натуральных чисел). Каждый элемент
21 | этих множеств имеет такие же свойства, как и все прочие элементы этого
22 | множества. Например, все натуральные числа ассоциативны - «для всех натуральных
23 | чисел a, b и c выполняется: `a + (b + c) = (a + b) + c` и `a × (b × c) = (a × b) ×
24 | c`»; в этом смысле множества схожи с типами данных в языках программирования тем,
25 | что все значения одного типа имеют общие свойства.
26 |
27 | Go — это язык программирования со статической типизацией. Это означает, что
28 | переменные всегда имеют определенный тип и этот тип нельзя изменить. Статическая
29 | типизация, на первый взгляд, может показаться неудобной. Вы потратите кучу
30 | времени только на попытки исправить ошибки, не позволяющие программе
31 | скомпилироваться. Однако типы дают вам возможность понять, что именно делает
32 | программа, и помогают избежать распространённых ошибок.
33 |
34 | В Go есть несколько встроенных типов данных, с которыми мы сейчас ознакомимся.
35 |
36 | ## Числа
37 |
38 | В Go есть несколько различных типов для представления чисел. Вообще, мы разделим
39 | числа на два различных класса: целые числа и числа с плавающей точкой.
40 |
41 | ### Целые числа
42 |
43 | Целые числа, точно так же, как их математические коллеги, — это числа без
44 | вещественной части. В отличие от десятичного представления чисел, которое
45 | используем мы, компьютеры используют двоичное представление.
46 |
47 | Наша система строится на 10 различных цифрах. Когда мы исчерпываем доступные нам
48 | цифры, мы представляем большое число, используя новую цифру 2 (а затем 3, 4, 5, …)
49 | числа следуют одно за другим. Например, число, следующее за 9, это 10, число,
50 | следующее за 99, это 100 и так далее. Компьютеры делают то же самое, но они имеют
51 | только 2 цифры вместо 10. Поэтому, подсчет выглядит так: 0, 1, 10, 11, 100, 101,
52 | 110, 111 и так далее. Другое отличие между той системой счисления, что используем
53 | мы, и той, что использует компьютер - все типы чисел имеют строго определенный
54 | размер. У них есть ограниченное количество цифр. Поэтому четырехразрядное число
55 | может выглядеть так: 0000, 0001, 0010, 0011, 0100. В конце концов мы можем
56 | выйти за лимит, и большинство компьютеров просто вернутся к самому началу (что
57 | может стать причиной очень странного поведения программы).
58 |
59 | В Go существуют следующие типы целых чисел: `uint8`, `uint16`, `uint32`,
60 | `uint64`, `int8`, `int16`, `int32` и `int64`. 8, 16, 32 и 64 говорит нам,
61 | сколько бит использует каждый тип. `uint` означает «unsigned integer»
62 | (беззнаковое целое), в то время как `int` означает «signed integer» (знаковое
63 | целое). Беззнаковое целое может принимать только положительные значения (или
64 | ноль). В дополнение к этому существуют два типа-псевдонима: `byte` (то же
65 | самое, что `uint8`) и `rune` (то же самое, что `int32`). Байты — очень
66 | распространенная единица измерения в компьютерах (1 байт = 8 бит, 1024 байта = 1
67 | килобайт, 1024 килобайта = 1 мегабайт, …), и именно поэтому тип `byte` в Go часто
68 | используется для определения других типов. Также существует 3 машинно-зависимых
69 | целочисленных типа: `uint`, `int` и `uintptr`. Они машинно-зависимы, потому что
70 | их размер зависит от архитектуры используемого компьютера.
71 |
72 | В общем, если вы работаете с целыми числами — просто используйте тип `int`.
73 |
74 | ## Числа с плавающей точкой
75 |
76 | Числа с плавающей точкой — это числа, которые содержат вещественную часть
77 | (вещественные числа) (1.234, 123.4, 0.00001234, 12340000). Их представление в
78 | компьютере довольно сложно и не особо необходимо для их использования. Так что мы
79 | просто должны помнить:
80 |
81 | * Числа с плавающей точкой неточны. Бывают случаи, когда число вообще нельзя
82 | представить. Например, результатом вычисления `1.01 - 0.99` будет
83 | `0.020000000000000018` - число очень близкое к ожидаемому, но не то же самое.
84 |
85 | * Как и целые числа, числа с плавающей точкой имеют определенный размер (32 бита
86 | или 64 бита). Использование большего размера увеличивает точность (сколько цифр
87 | мы можем использовать для вычисления)
88 |
89 | * В дополнение к числам существуют несколько других значений, таких как: «not a
90 | number» (не число) (`NaN`, для вещей наподобие `0/0`), а также положительная и
91 | отрицательная бесконечность (`+∞` и `−∞`).
92 |
93 | В Go есть два вещественных типа: `float32` и `float64` (соответственно, часто
94 | называемые вещественными числами с одинарной и двойной точностью). А также два
95 | дополнительных типа для представления комплексных чисел (чисел с мнимой частью):
96 | `complex64` и `complex128`. Как правило, мы должны придерживаться типа `float64`,
97 | когда работаем с числами с плавающей точкой.
98 |
99 | ### Пример
100 |
101 | Давайте напишем программу-пример, использующую числа. Во-первых, создайте папку
102 | «chapter3» с файлом main.go внутри со следующим содержимым:
103 |
104 | package main
105 |
106 | import "fmt"
107 |
108 | func main() {
109 | fmt.Println("1 + 1 = ", 1 + 1)
110 | }
111 |
112 | Если вы запустите программу, то должны увидеть это:
113 |
114 | $ go run main.go
115 | 1 + 1 = 2
116 |
117 | Заметим, что эта программа очень схожа с программой, которую мы написали в главе 2.
118 | Она содержит ту же строку с указанием пакета, ту же строку с импортом, то же
119 | определение функции и использует ту же функцию `Println`. В этот раз вместо
120 | печати строки `Hello World` мы печатаем строку `1 + 1 = ` с последующим
121 | результатом выражения `1 + 1`. Это выражение состоит из трех частей: числового
122 | литерала `1` (который является типом `int`), оператора `+` (который представляет
123 | сложение) и другого числового литерала `1`. Давайте попробуем сделать то же
124 | самое, используя числа с плавающей точкой:
125 |
126 | fmt.Println("1 + 1 =", 1.0 + 1.0)
127 |
128 | Обратите внимание, что мы используем `.0`, чтобы сказать Go, что это число с
129 | плавающей точкой, а не целое. При выполнении этой программы результат будет тот
130 | же, что и прежде.
131 |
132 | В дополнение к сложению, в Go имеется несколько других операций:
133 |
134 | | Литерал | Пояснение |
135 | |---------|--------------------|
136 | | + | сложение |
137 | | - | вычитание |
138 | | * | умножение |
139 | | / | деление |
140 | | % | остаток от деления |
141 |
142 | ## Строки
143 |
144 | Как мы видели в главе 2, строка — это последовательность символов определенной
145 | длины, используемая для представления текста. Строки в Go состоят из независимых
146 | байтов, обычно по одному на каждый символ (символы из других языков, таких как
147 | китайский, представляются несколькими байтами).
148 |
149 | Строковые литералы могут быть созданы с помощью двойных кавычек `"Hello World"`
150 | или с помощью апострофов `` `Hello World` ``. Различие между ними в том, что
151 | строки в двойных кавычках не могут содержать новые строки и они позволяют
152 | использовать особые управляющие последовательности символов. Например, `\n` будет
153 | заменена символом новой строки, а `\t` - символом табуляции.
154 |
155 | Распространенные операции над строками включают в себя нахождение длины строки
156 | `len("Hello World")`, доступ к отдельному символу в строке `"Hello World"[1]`, и
157 | конкатенацию двух строк `"Hello " + "World"`. Давайте модифицируем
158 | созданную ранее программу, чтобы проверить всё это:
159 |
160 | package main
161 |
162 | import "fmt"
163 |
164 | func main() {
165 | fmt.Println(len("Hello World"))
166 | fmt.Println("Hello World"[1])
167 | fmt.Println("Hello " + "World")
168 | }
169 |
170 | На заметку:
171 |
172 | * Пробел тоже считается символом, поэтому длина строки 11 символов, а не 10 и
173 | третья строка содержит `"Hello "` вместо `"Hello"`.
174 |
175 | * Строки “индексируются” начиная с 0, а не с 1. [1] даст вам второй элемент, а не
176 | первый. Также заметьте, что вы видите `101` вместо `e`, когда выполняете
177 | программу. Это происходит из-за того, что символ представляется байтом (помните,
178 | байт — это целое число).
179 |
180 | Можно думать об индексации так: `"Hello World"` `1`. Читайте это так: «строка
181 | Hello World позиция 1», «на 1 позиции строки Hello World» или «второй символ
182 | строки Hello World».
183 |
184 | * Конкатенация использует тот же символ, что и сложение. Компилятор Go выясняет,
185 | что должно происходить, полагаясь на типы аргументов. Если по обе стороны от `+`
186 | находятся строки, компилятор предположит, что вы имели в виду конкатенацию, а не
187 | сложение (ведь сложение для строк бессмысленно).
188 |
189 | ## Логические типы
190 |
191 | Булевский тип (названный так в честь Джорджа Буля) — это специальный однобитный
192 | целочисленный тип, используемый для представления истинности и ложности. С этим типом
193 | используются три логических оператора:
194 |
195 | | Литерал | Пояснение |
196 | |--------------|-----------|
197 | | && | И |
198 | | || | ИЛИ |
199 | | ! | НЕ |
200 |
201 | Вот пример программы, показывающей их использование:
202 |
203 | func main() {
204 | fmt.Println(true && true)
205 | fmt.Println(true && false)
206 | fmt.Println(true || true)
207 | fmt.Println(true || false)
208 | fmt.Println(!true)
209 | }
210 |
211 | Запуск этой программы должен вывести:
212 |
213 | $ go run main.go
214 | true
215 | false
216 | true
217 | true
218 | false
219 |
220 | Используем таблицы истинности, чтобы определить, как эти операторы работают:
221 |
222 | | Выражение | Значение |
223 | |----------------|----------|
224 | | true && true | true |
225 | | true && false | false |
226 | | false && true | false |
227 | | false && false | false |
228 |
229 | | Выражение | Значение |
230 | |--------------------------|----------|
231 | | true || true | true |
232 | | true || false | true |
233 | | false || true | true |
234 | | false || false | false |
235 |
236 |
237 | | Выражение | Значение |
238 | |-----------|----------|
239 | | !true | false |
240 | | !false | true |
241 |
242 | Всё это — простейшие типы, включенные в Go и являющиеся основой, с помощью
243 | которой строятся все остальные типы.
244 |
245 | ## Задачи
246 |
247 | * Как хранятся числа в компьютере?
248 |
249 | * Мы знаем, что в десятичной системе самое большое число из одной цифры - это 9, а
250 | из двух - 99. В бинарной системе самое большое число из
251 | двух цифр это 11 (3), самое большое число из трех цифр это 111 (7) и самое
252 | большое число из 4 цифр это 1111 (15). Вопрос: каково самое большое число из 8
253 | цифр? (Подсказка: 101-1=9 и 102-1=99)
254 |
255 | * В зависимости от задачи вы можете использовать Go как калькулятор. Напишите
256 | программу, которая вычисляет `32132 × 42452` и печатает это в терминал
257 | (используйте оператор `*` для умножения).
258 |
259 | * Что такое строка? Как найти её длину?
260 |
261 | * Какое значение примет выражение
262 | `(true && false) || (false && true) || !(false && false)`?
263 |
--------------------------------------------------------------------------------
/_book/chapter-07-functions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Функции"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Функция является независимой частью кода, связывающей один или несколько входных
8 | параметров с одним или несколькими выходными параметрами. Функции (также
9 | известные как процедуры и подпрограммы) можно представить как черный ящик:
10 |
11 | 
12 |
13 | До сих пор мы писали программы, используя лишь одну функцию:
14 |
15 | func main() {}
16 |
17 | Но сейчас мы начнем создавать код, содержащий более одной функции.
18 |
19 | ## Ваша вторая функция
20 |
21 | Вспомните эту программу из предыдущей главы:
22 |
23 | func main() {
24 | xs := []float64{98,93,77,82,83}
25 |
26 | total := 0.0
27 | for _, v := range xs {
28 | total += v
29 | }
30 | fmt.Println(total / float64(len(xs)))
31 | }
32 |
33 | Эта программа вычисляет среднее значение ряда чисел. Поиск среднего значения
34 | — основная задача и идеальный кандидат для вынесения в отдельную функцию.
35 |
36 | Функция `average` должна взять срез из нескольких `float64` и вернуть один
37 | `float64`. Напишем перед функцией `main`:
38 |
39 | func average(xs []float64) float64 {
40 | panic("Not Implemented")
41 | }
42 |
43 | Функция начинается с ключевого слова `func`, за которым следует имя функции.
44 | Аргументы (входы) определяются так: `имя тип, имя тип, …`. Наша функция имеет
45 | один параметр (список оценок) под названием `xs`. За параметром следует
46 | возвращаемый тип. В совокупности аргументы и возвращаемое значение также
47 | известны как сигнатура функции.
48 |
49 | Наконец, далее идет тело функции, заключенное в фигурные скобки. В теле вызывается
50 | встроенная функция `panic`, которая вызывает ошибку выполнения (о ней я расскажу
51 | чуть позже в этой главе). Процесс написания функций может быть сложен, поэтому
52 | деление этого процесса на несколько частей вместо попытки реализовать всё за
53 | один большой шаг — хорошая идея.
54 |
55 | Теперь давайте перенесём часть кода из функции `main` в функцию `average`:
56 |
57 | func average(xs []float64) float64 {
58 | total := 0.0
59 | for _, v := range xs {
60 | total += v
61 | }
62 | return total / float64(len(xs))
63 | }
64 |
65 | Обратите внимание, что мы заменили вызов `fmt.Println` на оператор `return`.
66 | Оператор возврата немедленно прервет выполнение функции и вернет значение,
67 | указанное после оператора, в функцию, которая вызвала текущую. Приведем `main` к
68 | следующему виду:
69 |
70 | func main() {
71 | xs := []float64{98,93,77,82,83}
72 | fmt.Println(average(xs))
73 | }
74 |
75 | Запуск этой программы должен дать точно такой же результат, что и раньше.
76 | Несколько моментов, которые нужно иметь ввиду:
77 |
78 | * имена аргументов не обязательно должны совпадать с именами переменных при
79 | вызове функции. Например, можно сделать так:
80 |
81 | ```
82 | func main() {
83 | someOtherName := []float64{98,93,77,82,83}
84 | fmt.Println(average(someOtherName))
85 | }
86 | ```
87 |
88 | и программа продолжит работать;
89 |
90 | * функции не имеют доступа к области видимости родительской функции, то есть это не сработает:
91 |
92 | ```
93 | func f() {
94 | fmt.Println(x)
95 | }
96 | func main() {
97 | x := 5
98 | f()
99 | }
100 | ```
101 |
102 | Как минимум нужно сделать так:
103 |
104 | ```
105 | func f(x int) {
106 | fmt.Println(x)
107 | }
108 | func main() {
109 | x := 5
110 | f(x)
111 | }
112 | ```
113 |
114 | или так:
115 |
116 | ```
117 | var x int = 5
118 | func f() {
119 | fmt.Println(x)
120 | }
121 | func main() {
122 | f()
123 | }
124 | ```
125 |
126 | * функции выстраиваются в «стек вызовов». Предположим, у нас есть такая
127 | программа:
128 |
129 | ```
130 | func main() {
131 | fmt.Println(f1())
132 | }
133 | func f1() int {
134 | return f2()
135 | }
136 | func f2() int {
137 | return 1
138 | }
139 | ```
140 |
141 | Её можно представить следующим образом:
142 |
143 | 
144 |
145 | Каждая вызываемая функция помещается в стек вызовов, каждый возврат из функции возвращает нас
146 | к предыдущей приостановленной подпрограмме;
147 |
148 | * можно также явно указать имя возвращаемого значения:
149 |
150 | ```
151 | func f2() (r int) {
152 | r = 1
153 | return
154 | }
155 | ```
156 |
157 | ## Возврат нескольких значений
158 |
159 | Go способен возвращать несколько значений из функции:
160 |
161 | func f() (int, int) {
162 | return 5, 6
163 | }
164 |
165 | func main() {
166 | x, y := f()
167 | }
168 |
169 | Для этого необходимы три вещи: указать несколько типов возвращаемых значений,
170 | разделенных `,`, изменить выражение после `return` так, чтобы оно содержало
171 | несколько значений, разделенных `,`, и, наконец, изменить конструкцию присвоения
172 | так, чтобы она содержала несколько значений в левой части перед `:=` или `=`.
173 |
174 | Возврат нескольких значений часто используется для возврата ошибки вместе с
175 | результатом (`x, err := f()`) или логического значения, говорящего об успешном
176 | выполнении (`x, ok := f()`).
177 |
178 | ## Переменное число аргументов функции
179 |
180 | Существует особая форма записи последнего аргумента в функции Go:
181 |
182 | func add(args ...int) int {
183 | total := 0
184 | for _, v := range args {
185 | total += v
186 | }
187 | return total
188 | }
189 | func main() {
190 | fmt.Println(add(1,2,3))
191 | }
192 |
193 | Использование `...` перед типом последнего аргумента означает, что функция может
194 | содержать ноль и более таких параметров. В нашем случае мы берем ноль и более
195 | `int`. Функцию можно вызывать, как и раньше, но при этом ей можно передать любое
196 | количество аргументов типа `int`.
197 |
198 | Это похоже на реализацию функции `Println`:
199 |
200 | func Println(a ...interface{}) (n int, err error)
201 |
202 | Функция `Println` может принимать любое количество аргументов любого типа (тип
203 | `interface` мы рассмотрим в главе 9).
204 |
205 | Мы также можем передать срез `int`-ов, указав `...` после среза:
206 |
207 | func main() {
208 | xs := []int{1,2,3}
209 | fmt.Println(add(xs...))
210 | }
211 |
212 | ## Замыкания
213 |
214 | Возможно создавать функции внутри функций:
215 |
216 | func main() {
217 | add := func(x, y int) int {
218 | return x + y
219 | }
220 | fmt.Println(add(1,1))
221 | }
222 |
223 | `add` является локальной переменной типа `func(int, int) int` (функция принимает
224 | два аргумента типа `int` и возвращает `int`). При создании локальная функция
225 | также получает доступ к локальным переменным (вспомните области видимости из
226 | главы 4):
227 |
228 | func main() {
229 | x := 0
230 | increment := func() int {
231 | x++
232 | return x
233 | }
234 | fmt.Println(increment())
235 | fmt.Println(increment())
236 | }
237 |
238 | `increment` прибавляет `1` к переменной `x`, которая определена в рамках функции
239 | `main`. Значение переменной `x` может быть изменено в функции `increment`. Вот
240 | почему при первом вызове `increment` на экран выводится `1`, а при втором — `2`.
241 |
242 | Функцию, использующую переменные, определенные вне этой функции, называют
243 | замыканием. В нашем случае функция `increment` и переменная `x` образуют
244 | замыкание.
245 |
246 | Один из способов использования замыкания — функция, возвращающая другую функцию,
247 | которая при вызове генерирует некую последовательность чисел. Например,
248 | следующим образом мы могли бы сгенерировать все четные числа:
249 |
250 | func makeEvenGenerator() func() uint {
251 | i := uint(0)
252 | return func() (ret uint) {
253 | ret = i
254 | i += 2
255 | return
256 | }
257 | }
258 | func main() {
259 | nextEven := makeEvenGenerator()
260 | fmt.Println(nextEven()) // 0
261 | fmt.Println(nextEven()) // 2
262 | fmt.Println(nextEven()) // 4
263 | }
264 |
265 | `makeEvenGenerator` возвращает функцию, которая генерирует чётные числа. Каждый
266 | раз, когда она вызывается, к переменной `i` добавляется `2`, но в отличие от
267 | обычных локальных переменных её значение сохраняется между вызовами.
268 |
269 | ## Рекурсия
270 |
271 | Наконец, функция может вызывать саму себя. Вот один из способов вычисления
272 | факториала числа:
273 |
274 | func factorial(x uint) uint {
275 | if x == 0 {
276 | return 1
277 | }
278 |
279 | return x * factorial(x-1)
280 | }
281 |
282 | `factorial` вызывает саму себя, что делает эту функцию рекурсивной. Для того,
283 | чтобы лучше понять, как работает эта функция, давайте пройдемся по
284 | `factorial(2)`:
285 |
286 | * `x == 0`? Нет. (`x` равен `2`);
287 | * ищем факториал от `x - 1`;
288 | * `x == 0`? Нет. (`x` равен `1`);
289 | * ищем факториал от `0`;
290 | * `x == 0`? Да, возвращаем `1`;
291 | * возвращаем `1 * 1`;
292 | * возвращаем `2 * 1`.
293 |
294 | Замыкание и рекурсивный вызов — сильные техники программирования, формирующие
295 | основу парадигмы, известной как функциональное программирование. Большинство
296 | людей находят функциональное программирование более сложным для понимания, чем
297 | подход на основе циклов, логических операторов, переменных и простых функций.
298 |
299 | ## Отложенный вызов, паника и восстановление
300 |
301 | В Go есть специальный оператор `defer`, который позволяет отложить вызов
302 | указанной функции до тех пор, пока не завершится текущая. Рассмотрим следующий
303 | пример:
304 |
305 | package main
306 |
307 | import "fmt"
308 |
309 | func first() {
310 | fmt.Println("1st")
311 | }
312 | func second() {
313 | fmt.Println("2nd")
314 | }
315 | func main() {
316 | defer second()
317 | first()
318 | }
319 |
320 | Эта программа выводит `1st`, затем `2nd`. Грубо говоря `defer` перемещает вызов
321 | `second` в конец функции:
322 |
323 | func main() {
324 | first()
325 | second()
326 | }
327 |
328 | `defer` часто используется в случаях, когда нужно освободить ресурсы после
329 | завершения. Например, открывая файл необходимо убедиться, что позже он должен
330 | быть закрыт. C `defer` это выглядит так:
331 |
332 | f, _ := os.Open(filename)
333 | defer f.Close()
334 |
335 | Такой подход дает нам три преимущества: (1) вызовы `Close` и `Open`
336 | располагаются рядом, что облегчает понимание программы, (2) если функция
337 | содержит несколько операций возврата (например, одна произойдет в блоке `if`,
338 | другая в блоке `else`), `Close` будет вызван до выхода из функции, (3)
339 | отложенные функции вызываются, даже если во время выполнения происходит ошибка.
340 |
341 | ### Паника и восстановление
342 |
343 | Ранее мы создали функцию, которая вызывает `panic`, чтобы сгенерировать ошибку
344 | выполнения. Мы можем обрабатывать паники с помощью встроенной функции `recover`.
345 | Функция `recover` останавливает панику и возвращает значение, которое было
346 | передано функции `panic`. Можно попытаться использовать `recover` следующим
347 | образом:
348 |
349 | package main
350 |
351 | import "fmt"
352 |
353 | func main() {
354 | panic("PANIC")
355 | str := recover()
356 | fmt.Println(str)
357 | }
358 |
359 | Но в данном случае `recover` никогда не будет вызвана, поскольку вызов `panic`
360 | немедленно останавливает выполнение функции. Вместо этого мы должны использовать
361 | его вместе с `defer`:
362 |
363 | package main
364 |
365 | import "fmt"
366 |
367 | func main() {
368 | defer func() {
369 | str := recover()
370 | fmt.Println(str)
371 | }()
372 | panic("PANIC")
373 | }
374 |
375 | Паника обычно указывает на ошибку программиста (например, попытку получить
376 | доступ к несуществующему индексу массива, забытая и непроинициализированная карта и т.д.)
377 | или неожиданное поведение (исключение), которое нельзя обработать (поэтому оно и
378 | называется «паника»).
379 |
380 | ## Задачи
381 |
382 | * Функция `sum` принимает срез чисел и складывает их вместе. Как бы выглядела
383 | сигнатура этой функции?
384 |
385 | * Напишите функцию, которая принимает число, делит его пополам и возвращает
386 | `true` в случае, если исходное число чётное, и `false`, если нечетное. Например, `half(1)` должна вернуть `(0, false)`, в то время как
387 | `half(2)` вернет `(1, true)`.
388 |
389 | * Напишите функцию с переменным числом параметров, которая находит наибольшее
390 | число в списке.
391 |
392 | * Используя в качестве примера функцию `makeEvenGenerator` напишите
393 | `makeOddGenerator`, генерирующую нечётные числа.
394 |
395 | * Последовательность чисел Фибоначчи определяется как `fib(0) = 0`,
396 | `fib(1) = 1`, `fib(n) = fib(n-1) + fib(n-2)`. Напишите рекурсивную функцию,
397 | находящую `fib(n)`.
398 |
399 | * Что такое отложенный вызов, паника и восстановление? Как восстановить функцию
400 | после паники?
401 |
--------------------------------------------------------------------------------
/_book/chapter-06-arrays-slices-maps.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Массивы, срезы, карты"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | В главе 3 мы изучили базовые типы Go. В этой главе мы рассмотрим еще три
8 | встроенных типа: массивы, срезы и карты.
9 |
10 | ## Массивы
11 |
12 | Массив — это нумерованная последовательность элементов одного типа с
13 | фиксированной длиной. В Go они выглядят так:
14 |
15 | var x [5]int
16 |
17 | `x` — это пример массива, состоящего из пяти элементов типа `int`. Запустим
18 | следующую программу:
19 |
20 | package main
21 |
22 | import "fmt"
23 |
24 | func main() {
25 | var x [5]int
26 | x[4] = 100
27 | fmt.Println(x)
28 | }
29 |
30 | Вы должны увидеть следующее:
31 |
32 | [0 0 0 0 100]
33 |
34 | `x[4] = 100` должно читаться как «присвоить пятому элементу массива x значение
35 | 100». Может показаться странным то, что `x[4]` является пятым элементом массива, а не
36 | четвертым, но, как и строки, массивы нумеруются с нуля. Доступ к элементам
37 | массива выглядит так же, как у строк. Вместо `fmt.Println(x)` мы можем написать
38 | `fmt.Println(x[4])` и в результате будет выведено `100`.
39 |
40 | Пример программы, использующей массивы:
41 |
42 | func main() {
43 | var x [5]float64
44 | x[0] = 98
45 | x[1] = 93
46 | x[2] = 77
47 | x[3] = 82
48 | x[4] = 83
49 |
50 | var total float64 = 0
51 | for i := 0; i < 5; i++ {
52 | total += x[i]
53 | }
54 | fmt.Println(total / 5)
55 | }
56 |
57 | Эта программа вычисляет среднюю оценку за экзамен. Если вы выполните её, то
58 | увидите `86.6`. Давайте рассмотрим её внимательнее:
59 |
60 | * сперва мы создаем массив длины 5 и заполняем его;
61 | * затем мы в цикле считаем общее количество баллов;
62 | * и в конце мы делим общую сумму баллов на количество элементов, чтобы узнать средний балл.
63 |
64 | Эта программа работает, но её всё еще можно улучшить. Во-первых, бросается в
65 | глаза следующее: `i < 5` и `total / 5`. Если мы изменим количество оценок с 5 на
66 | 6, то придется переписывать код в этих двух местах. Будет лучше использовать
67 | длину массива:
68 |
69 | var total float64 = 0
70 | for i := 0; i < len(x); i++ {
71 | total += x[i]
72 | }
73 | fmt.Println(total / len(x))
74 |
75 | Напишите этот кусок кода и запустите программу. Вы должны получить ошибку:
76 |
77 | $ go run tmp.go
78 | # command-line-arguments
79 | .\tmp.go:19: invalid operation: total / len(x) (mismatched types float64 and int)
80 |
81 | Проблема в том, что `len(x)` и `total` имеют разный тип. `total` имеет тип
82 | `float64`, а `len(x)` — `int`. Так что, нам надо конвертировать `len(x)` в
83 | `float64`:
84 |
85 | fmt.Println(total / float64(len(x)))
86 |
87 | Это был пример преобразования типов. В целом, для преобразования типа можно
88 | использовать имя типа в качестве функции.
89 |
90 | Другая вещь, которую мы можем изменить в нашей программе - это цикл:
91 |
92 | var total float64 = 0
93 | for i, value := range x {
94 | total += value
95 | }
96 | fmt.Println(total / float64(len(x)))
97 |
98 | В этом цикле `i` представляет текущую позицию в массиве, а `value` будет тем же
99 | самым что и `x[i]`. Мы использовали ключевое слово `range` перед переменной, по
100 | которой мы хотим пройтись циклом.
101 |
102 | Выполнение этой программы вызовет другую ошибку:
103 |
104 | $ go run tmp.go
105 | # command-line-arguments
106 | .\tmp.go:16: i declared and not used
107 |
108 | Компилятор Go не позволяет вам создавать переменные, которые никогда не
109 | используются в коде. Поскольку мы не используем `i` внутри нашего цикла, то надо
110 | изменить код следующим образом:
111 |
112 | var total float64 = 0
113 | for _, value := range x {
114 | total += value
115 | }
116 | fmt.Println(total / float64(len(x)))
117 |
118 | Одиночный символ подчеркивания `_` используется, чтобы сказать компилятору, что
119 | переменная нам не нужна (в данном случае нам не нужна переменная итератора).
120 |
121 | А еще в Go есть короткая запись для создания массивов:
122 |
123 | x := [5]float64{ 98, 93, 77, 82, 83 }
124 |
125 | Указывать тип не обязательно — Go сам может его выяснить по содержимому
126 | массива.
127 |
128 | Иногда массивы могут оказаться слишком длинными для записи в одну строку, в этом
129 | случае Go позволяет записывать их в несколько строк:
130 |
131 | x := [5]float64{
132 | 98,
133 | 93,
134 | 77,
135 | 82,
136 | 83,
137 | }
138 |
139 | Обратите внимание на последнюю `,` после `83`. Она обязательна и позволяет легко
140 | удалить элемент из массива просто закомментировав строку:
141 |
142 | x := [4]float64{
143 | 98,
144 | 93,
145 | 77,
146 | 82,
147 | // 83,
148 | }
149 |
150 | ## Срезы
151 |
152 | Срез это часть массива. Как и массивы, срезы индексируются и имеют длину. В
153 | отличии от массивов их длину можно изменить. Вот пример среза:
154 |
155 | var x []float64
156 |
157 | Единственное отличие объявления среза от объявления массива — отсутствие
158 | указания длины в квадратных скобках. В нашем случае `x` будет иметь длину 0.
159 |
160 | Срез создается встроенной функцией `make`:
161 |
162 | x := make([]float64, 5)
163 |
164 | Этот код создаст срез, который связан с массивом типа `float64`, длиной `5`.
165 | Срезы всегда связаны с каким-нибудь массивом. Они не могут стать больше чем
166 | массив, а вот меньше — пожалуйста. Функция `make` принимает и третий параметр:
167 |
168 | x := make([]float64, 5, 10)
169 |
170 | `10` — это длина массива, на который указывает срез:
171 |
172 | 
173 |
174 | Другой способ создать срез — использовать выражение `[low : high]`:
175 |
176 | arr := [5]float64{1,2,3,4,5}
177 | x := arr[0:5]
178 |
179 | `low` - это позиция, с которой будет начинаться срез, а `high` - это позиция, где он
180 | закончится. Например: `arr[0:5]` вернет `[1,2,3,4,5]`, `arr[1:4]` вернет
181 | `[2,3,4]`.
182 |
183 | Для удобства мы также можем опустить `low`, `high` или и то, и другое. `arr[0:]`
184 | это то же самое что `arr[0:len(arr)]`, `arr[:5]` то же самое что `arr[0:5] ` и
185 | `arr[:]` то же самое что `arr[0:len(arr)]`.
186 |
187 | ### Функции срезов
188 |
189 | В Go есть две встроенные функции для срезов: `append` и `copy`. Вот пример
190 | работы функции `append`:
191 |
192 | func main() {
193 | slice1 := []int{1,2,3}
194 | slice2 := append(slice1, 4, 5)
195 | fmt.Println(slice1, slice2)
196 | }
197 |
198 | После выполнения программы `slice1` будет содержать `[1,2,3]`, а `slice2` —
199 | `[1,2,3,4,5]`. `append` создает новый срез из уже существующего (первый
200 | аргумент) и добавляет к нему все следующие аргументы.
201 |
202 | Пример работы `copy`:
203 |
204 | func main() {
205 | slice1 := []int{1,2,3}
206 | slice2 := make([]int, 2)
207 | copy(slice2, slice1)
208 | fmt.Println(slice1, slice2)
209 | }
210 |
211 | После выполнения этой программы `slice1` будет содержать `[1,2,3]`, а `slice2` —
212 | `[1,2]`. Содержимое `slice1` копируется в `slice2`, но поскольку в `slice2` есть
213 | место только для двух элементов, то только два первых элемента `slice1` будут
214 | скопированы.
215 |
216 | ## Карта
217 |
218 | Карта (также известна как ассоциативный массив или словарь) — это
219 | неупорядоченная коллекция пар вида ключ-значение. Пример:
220 |
221 | var x map[string]int
222 |
223 | Карта представляется в связке с ключевым словом `map`, следующим за ним типом
224 | ключа в скобках и типом значения после скобок. Читается это следующим образом:
225 | «`x ` — это карта `string`-ов для `int`-ов».
226 |
227 | Подобно массивам и срезам, к элементам карт можно обратиться с помощью скобок.
228 | Запустим следующую программу:
229 |
230 | var x map[string]int
231 | x["key"] = 10
232 | fmt.Println(x)
233 |
234 | Вы должны увидеть ошибку, похожую на эту:
235 |
236 | panic: runtime error: assignment to entry in nil map
237 |
238 | goroutine 1 [running]:
239 | main.main()
240 | main.go:7 +0x4d
241 |
242 | goroutine 2 [syscall]:
243 | created by runtime.main
244 | C:/Users/ADMINI~1/AppData/Local/Temp/2/bindi
245 | t269497170/go/src/pkg/runtime/proc.c:221
246 | exit status 2
247 |
248 | До этого момента мы имели дело только с ошибками во время компиляции. Сейчас мы
249 | видим ошибку исполнения.
250 |
251 | Проблема нашей программы в том, что карта должна быть инициализирована перед
252 | тем, как будет использована. Надо написать так:
253 |
254 | x := make(map[string]int)
255 | x["key"] = 10
256 | fmt.Println(x["key"])
257 |
258 | Если выполнить эту программу, то вы должны увидеть `10`. Выражение `x["key"] =
259 | 10` похоже на те, что использовались при работе с массивами, но ключ тут не
260 | число, а строка (потому что в карте указан тип ключа `string`). Мы также можем
261 | создать карты с ключом типа `int`:
262 |
263 | x := make(map[int]int)
264 | x[1] = 10
265 | fmt.Println(x[1])
266 |
267 | Это выглядит очень похоже на массив, но существует несколько различий. Во-первых,
268 | длина карты (которую мы можем найти так: `len(x)`) может измениться, когда мы
269 | добавим в нее новый элемент. В самом начале при создании длина `0`, после
270 | `x[1] = 10` она станет равна `1`. Во-вторых, карта не является
271 | последовательностью. В нашем примере у нас есть элемент `x[1]`, в случае массива
272 | должен быть и первый элемент `x[0]`, но в картах это не так.
273 |
274 | Также мы можем удалить элементы из карты используя встроенную функцию `delete`:
275 |
276 | delete(x, 1)
277 |
278 | Давайте посмотрим на пример программы, использующей карты:
279 |
280 | package main
281 |
282 | import "fmt"
283 |
284 | func main() {
285 | elements := make(map[string]string)
286 | elements["H"] = "Hydrogen"
287 | elements["He"] = "Helium"
288 | elements["Li"] = "Lithium"
289 | elements["Be"] = "Beryllium"
290 | elements["B"] = "Boron"
291 | elements["C"] = "Carbon"
292 | elements["N"] = "Nitrogen"
293 | elements["O"] = "Oxygen"
294 | elements["F"] = "Fluorine"
295 | elements["Ne"] = "Neon"
296 |
297 | fmt.Println(elements["Li"])
298 | }
299 |
300 | В данном примере `elements` - это карта, которая представляет 10 первых
301 | химических элементов, индексируемых символами. Это очень частый способ
302 | использования карт — в качестве словаря или таблицы. Предположим, мы пытаемся
303 | обратиться к несуществующему элементу:
304 |
305 | fmt.Println(elements["Un"])
306 |
307 | Если вы выполните это, то ничего не увидите. Технически карта вернет нулевое
308 | значение хранящегося типа (для строк это пустая строка). Несмотря на то, что мы
309 | можем проверить нулевое значение с помощью условия (`elements["Un"] == ""`), в
310 | Go есть лучший способ сделать это:
311 |
312 | name, ok := elements["Un"]
313 | fmt.Println(name, ok)
314 |
315 | Доступ к элементу карты может вернуть два значения вместо одного. Первое
316 | значение это результат запроса, второе говорит, был ли запрос успешен. В Go часто
317 | встречается такой код:
318 |
319 | if name, ok := elements["Un"]; ok {
320 | fmt.Println(name, ok)
321 | }
322 |
323 | Сперва мы пробуем получить значение из карты, а затем, если это удалось, мы
324 | выполняем код внутри блока.
325 |
326 | Объявления карт можно записывать сокращенно - так же, как массивы:
327 |
328 | elements := map[string]string{
329 | "H": "Hydrogen",
330 | "He": "Helium",
331 | "Li": "Lithium",
332 | "Be": "Beryllium",
333 | "B": "Boron",
334 | "C": "Carbon",
335 | "N": "Nitrogen",
336 | "O": "Oxygen",
337 | "F": "Fluorine",
338 | "Ne": "Neon",
339 | }
340 |
341 | Карты часто используются для хранения общей информации. Давайте изменим
342 | нашу программу так, чтобы вместо имени элемента хранить какую-нибудь
343 | дополнительную информацию о нем. Например его агрегатное состояние:
344 |
345 | func main() {
346 | elements := map[string]map[string]string{
347 | "H": map[string]string{
348 | "name":"Hydrogen",
349 | "state":"gas",
350 | },
351 | "He": map[string]string{
352 | "name":"Helium",
353 | "state":"gas",
354 | },
355 | "Li": map[string]string{
356 | "name":"Lithium",
357 | "state":"solid",
358 | },
359 | "Be": map[string]string{
360 | "name":"Beryllium",
361 | "state":"solid",
362 | },
363 | "B": map[string]string{
364 | "name":"Boron",
365 | "state":"solid",
366 | },
367 | "C": map[string]string{
368 | "name":"Carbon",
369 | "state":"solid",
370 | },
371 | "N": map[string]string{
372 | "name":"Nitrogen",
373 | "state":"gas",
374 | },
375 | "O": map[string]string{
376 | "name":"Oxygen",
377 | "state":"gas",
378 | },
379 | "F": map[string]string{
380 | "name":"Fluorine",
381 | "state":"gas",
382 | },
383 | "Ne": map[string]string{
384 | "name":"Neon",
385 | "state":"gas",
386 | },
387 | }
388 |
389 | if el, ok := elements["Li"]; ok {
390 | fmt.Println(el["name"], el["state"])
391 | }
392 | }
393 |
394 | Заметим, что тип нашей карты теперь `map[string]map[string]string`. Мы получили
395 | карту строк для карты строк. Внешняя карта используется как поиск по символу
396 | химического элемента, а внутренняя — для хранения информации об элементе. Не
397 | смотря на то, что карты часто используется таким образом, в главе 9 мы узнаем
398 | лучший способ хранения данных.
399 |
400 | ## Задачи
401 |
402 | * Как обратиться к четвертому элементу массива или среза?
403 |
404 | * Чему равна длина среза, созданного таким способом: `make([]int, 3, 9)`?
405 |
406 | * Дан массив:
407 |
408 | ```
409 | x := [6]string{"a","b","c","d","e","f"}
410 | ```
411 |
412 | что вернет вам `x[2:5]`?
413 |
414 | * Напишите программу, которая находит самый наименьший элемент в этом списке:
415 |
416 | ```
417 | x := []int{
418 | 48,96,86,68,
419 | 57,82,63,70,
420 | 37,34,83,27,
421 | 19,97, 9,17,
422 | }
423 | ```
424 |
--------------------------------------------------------------------------------
/_book/chapter-13-core-packages.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Стандартная библиотека"
3 | layout: "chapter"
4 |
5 | ---
6 |
7 | Вместо того, чтобы каждый раз писать всё с нуля, реальный мир программирования
8 | требует от нас умения взаимодействовать с уже существующими библиотеками. В этой
9 | главе мы рассмотрим самые часто используемые пакеты, включенные в Go.
10 |
11 | Предупреждаю: некоторые библиотеки достаточно очевидны (или были объяснены в
12 | предыдущих главах), многие из библиотек, включённых в Go требуют специальных
13 | знаний (например: криптография). Объяснение этих технологий выходит за рамки
14 | этой книги.
15 |
16 | ## Строки
17 |
18 | Go содержит большое количество функций для работы со строками в пакете `strings`:
19 |
20 | package main
21 |
22 | import (
23 | "fmt"
24 | "strings"
25 | )
26 |
27 | func main() {
28 | fmt.Println(
29 | // true
30 | strings.Contains("test", "es"),
31 |
32 | // 2
33 | strings.Count("test", "t"),
34 |
35 | // true
36 | strings.HasPrefix("test", "te"),
37 |
38 | // true
39 | strings.HasSuffix("test", "st"),
40 |
41 | // 1
42 | strings.Index("test", "e"),
43 |
44 | // "a-b"
45 | strings.Join([]string{"a","b"}, "-"),
46 |
47 | // == "aaaaa"
48 | strings.Repeat("a", 5),
49 |
50 | // "bbaa"
51 | strings.Replace("aaaa", "a", "b", 2),
52 |
53 | // []string{"a","b","c","d","e"}
54 | strings.Split("a-b-c-d-e", "-"),
55 |
56 | // "test"
57 | strings.ToLower("TEST"),
58 |
59 | // "TEST"
60 | strings.ToUpper("test"),
61 |
62 | )
63 | }
64 |
65 | Иногда нам понадобится работать с бинарными данными. Чтобы преобразовать строку
66 | в набор байт (и наоборот), выполните следующие действия:
67 |
68 | arr := []byte("test")
69 | str := string([]byte{'t','e','s','t'})
70 |
71 | ## Ввод / Вывод
72 |
73 | Прежде чем мы перейдем к работе с файлами, нужно узнать про пакет `io`. Пакет `io`
74 | состоит из нескольких функций, но в основном, это интерфейсы, используемые в
75 | других пакетах. Два основных интерфейса — это `Reader` и `Writer`. `Reader`
76 | занимается чтением с помощью метода `Read`. `Writer` занимается записью с помощью
77 | метода `Write`. Многие функции принимают в качестве аргумента `Reader` или
78 | `Writer`. Например, пакет `io` содержит функцию `Copy`, которая копирует данные из
79 | `Reader` во `Writer`:
80 |
81 | func Copy(dst Writer, src Reader) (written int64, err error)
82 |
83 | Чтобы прочитать или записать `[]byte` или `string`, можно использовать структуру
84 | `Buffer` из пакета `bytes`:
85 |
86 | var buf bytes.Buffer
87 | buf.Write([]byte("test"))
88 |
89 | `Buffer` не требует инициализации и поддерживает интерфейсы `Reader` и `Writer`.
90 | Вы можете конвертировать его в `[]byte` вызвав `buf.Bytes()`. Если нужно только
91 | читать строки, можно так же использовать функцию `strings.NewReader()`, которая
92 | более эффективна, чем чтение в буфер.
93 |
94 | ## Файлы и папки
95 |
96 | Для открытия файла Go использует функцию `Open` из пакета `os`. Вот пример того,
97 | как прочитать файл и вывести его содержимое в консоль:
98 |
99 | package main
100 |
101 | import (
102 | "fmt"
103 | "os"
104 | )
105 |
106 | func main() {
107 | file, err := os.Open("test.txt")
108 | if err != nil {
109 | // здесь перехватывается ошибка
110 | return
111 | }
112 | defer file.Close()
113 |
114 | // получить размер файла
115 | stat, err := file.Stat()
116 | if err != nil {
117 | return
118 | }
119 | // чтение файла
120 | bs := make([]byte, stat.Size())
121 | _, err = file.Read(bs)
122 | if err != nil {
123 | return
124 | }
125 |
126 | str := string(bs)
127 | fmt.Println(str)
128 | }
129 |
130 | Мы используем `defer file.Close()` сразу после открытия файла, чтобы быть
131 | уверенным, что файл будет закрыт после выполнения функции. Чтение файлов является
132 | частым действием, так что вот самый короткий способ сделать это:
133 |
134 | package main
135 |
136 | import (
137 | "fmt"
138 | "io/ioutil"
139 | )
140 |
141 | func main() {
142 | bs, err := ioutil.ReadFile("test.txt")
143 | if err != nil {
144 | return
145 | }
146 | str := string(bs)
147 | fmt.Println(str)
148 | }
149 |
150 | А вот так мы можем создать файл:
151 |
152 | package main
153 |
154 | import (
155 | "os"
156 | )
157 |
158 | func main() {
159 | file, err := os.Create("test.txt")
160 | if err != nil {
161 | // здесь перехватывается ошибка
162 | return
163 | }
164 | defer file.Close()
165 |
166 | file.WriteString("test")
167 | }
168 |
169 | Чтобы получить содержимое каталога, мы используем тот же `os.Open()`, но передаём
170 | ему путь к каталогу вместо имени файла. Затем вызывается функция `Readdir`:
171 |
172 | package main
173 |
174 | import (
175 | "fmt"
176 | "os"
177 | )
178 |
179 | func main() {
180 | dir, err := os.Open(".")
181 | if err != nil {
182 | return
183 | }
184 | defer dir.Close()
185 |
186 | fileInfos, err := dir.Readdir(-1)
187 | if err != nil {
188 | return
189 | }
190 | for _, fi := range fileInfos {
191 | fmt.Println(fi.Name())
192 | }
193 | }
194 |
195 | Иногда мы хотим рекурсивно обойти каталоги (прочитать содержимое текущего и всех
196 | вложенных каталогов). Это делается просто с помощью функции `Walk`,
197 | предоставляемой пакетом `path/filepath`:
198 |
199 | package main
200 |
201 | import (
202 | "fmt"
203 | "os"
204 | "path/filepath"
205 | )
206 |
207 | func main() {
208 | filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
209 | fmt.Println(path)
210 | return nil
211 | })
212 | }
213 |
214 | Функция, передаваемая вторым аргументом, вызывается для каждого файла и каталога в
215 | корневом каталоге (в данном случае).
216 |
217 | ## Ошибки
218 |
219 | Go имеет встроенный тип для сообщений об ошибках, который мы уже рассматривали
220 | (тип `error`). Мы можем создать свои собственные типы сообщений об ошибках
221 | используя функцию `New` из пакета `errors`.
222 |
223 | package main
224 |
225 | import "errors"
226 |
227 | func main() {
228 | err := errors.New("error message")
229 | }
230 |
231 | ## Контейнеры и сортировки
232 |
233 | В дополнение к спискам и картам, Go предоставляет еще несколько видов коллекций,
234 | доступных в пакете `container`. В качестве примера рассмотрим `container/list`.
235 |
236 | ### Список
237 |
238 | Пакет `container/list` реализует двусвязный список. Структура типа данных связного
239 | списка выглядит следующим образом:
240 |
241 | 
242 |
243 | Каждый узел списка содержит значение (в нашем случае: 1, 2 или 3) и указатель на
244 | следующий узел. Но так как это двусвязный список, узел так же содержит указатель
245 | на предыдущий. Такой список может быть создан с помощью следующей программы:
246 |
247 | package main
248 |
249 | import ("fmt" ; "container/list")
250 |
251 | func main() {
252 | var x list.List
253 | x.PushBack(1)
254 | x.PushBack(2)
255 | x.PushBack(3)
256 |
257 | for e := x.Front(); e != nil; e=e.Next() {
258 | fmt.Println(e.Value.(int))
259 | }
260 | }
261 |
262 | Пустым значением `List` *(вероятно, опечатка и имелось ввиду `x` — прим. пер.)*
263 | является пустой список (`*List` создаётся при вызове `list.New`). Значения
264 | добавляются в список при помощи `PushBack`. Далее, мы перебираем каждый элемент в
265 | списке, получая ссылку на следующий, пока не достигнем `nil`.
266 |
267 | ### Сортировка
268 |
269 | Пакет `sort` содержит функции для сортировки произвольных данных. Есть несколько
270 | предопределённых функций (для срезов, целочисленных значений и чисел с плавающей
271 | точкой). Вот пример, как отсортировать ваши данные:
272 |
273 | package main
274 |
275 | import ("fmt" ; "sort")
276 |
277 | type Person struct {
278 | Name string
279 | Age int
280 | }
281 |
282 | type ByName []Person
283 |
284 | func (this ByName) Len() int {
285 | return len(this)
286 | }
287 | func (this ByName) Less(i, j int) bool {
288 | return this[i].Name < this[j].Name
289 | }
290 | func (this ByName) Swap(i, j int) {
291 | this[i], this[j] = this[j], this[i]
292 | }
293 |
294 | func main() {
295 | kids := []Person{
296 | {"Jill",9},
297 | {"Jack",10},
298 | }
299 | sort.Sort(ByName(kids))
300 | fmt.Println(kids)
301 | }
302 |
303 | ## Хэши и криптография
304 |
305 | Функция хэширования принимает набор данных и уменьшает его до фиксированного
306 | размера. Хэши используются в программировании повсеместно, начиная от поиска
307 | данных, заканчивая быстрым детектированием изменений. Хэш-функции в Go
308 | подразделяются на две категории: криптографические и некриптографические.
309 |
310 | Некриптографические функции можно найти в пакете `hash`, который включает такие
311 | алгоритмы как `adler32`, `crc32`, `crc64` и `fnv`. Вот пример использования
312 | `crc32`:
313 |
314 | package main
315 |
316 | import (
317 | "fmt"
318 | "hash/crc32"
319 | )
320 |
321 | func main() {
322 | h := crc32.NewIEEE()
323 | h.Write([]byte("test"))
324 | v := h.Sum32()
325 | fmt.Println(v)
326 | }
327 |
328 | Объект `crc32` реализует интерфейс `Writer`, так что мы можем просто записать в
329 | него набор байт, как и в любой другой `Writer`. После записи мы вызываем
330 | `Sum32()`, который вернёт `uint32`. Обычным применением `crc32` является сравнение
331 | двух файлов. Если значение `Sum32()` для обоих файлов одинаковы, то, весьма
332 | вероятно (не со стопроцентной гарантией), содержимое этих файлов идентично. Если
333 | же значения отличаются, значит файлы, безусловно, разные:
334 |
335 | package main
336 |
337 | import (
338 | "fmt"
339 | "hash/crc32"
340 | "io/ioutil"
341 | )
342 |
343 | func getHash(filename string) (uint32, error) {
344 | bs, err := ioutil.ReadFile(filename)
345 | if err != nil {
346 | return 0, err
347 | }
348 | h := crc32.NewIEEE()
349 | h.Write(bs)
350 | return h.Sum32(), nil
351 | }
352 |
353 | func main() {
354 | h1, err := getHash("test1.txt")
355 | if err != nil {
356 | return
357 | }
358 | h2, err := getHash("test2.txt")
359 | if err != nil {
360 | return
361 | }
362 | fmt.Println(h1, h2, h1 == h2)
363 | }
364 |
365 | Криптографические хэш-функции аналогичны их некриптографическим коллегам, однако у
366 | них есть одна особенность: их сложно обратить вспять. Очень сложно определить, что
367 | за набор данных содержится в криптографическом хэше, поэтому такие хэши часто
368 | используются в системах безопасности.
369 |
370 | Одним из криптографических хэш-алгоритмов является SHA-1. Вот как можно его
371 | использовать:
372 |
373 | package main
374 |
375 | import (
376 | "fmt"
377 | "crypto/sha1"
378 | )
379 |
380 | func main() {
381 | h := sha1.New()
382 | h.Write([]byte("test"))
383 | bs := h.Sum([]byte{})
384 | fmt.Println(bs)
385 | }
386 |
387 | Этот пример очень похож на пример использования `crc32`, потому что оба они
388 | реализуют интерфейс `hash.Hash`. Основное отличие в том, что в то время как
389 | `crc32` вычисляет 32-битный хэш, `sha1` вычисляет 160-битный хэш. В Go нет
390 | встроенного типа для хранения 160-битного числа, поэтому мы используем вместо него
391 | срез размером 20 байт.
392 |
393 | ## Серверы
394 |
395 | На Go очень просто создавать сетевые серверы. Сначала давайте взглянем, как
396 | создать TCP сервер:
397 |
398 | package main
399 |
400 | import (
401 | "encoding/gob"
402 | "fmt"
403 | "net"
404 | )
405 |
406 | func server() {
407 | // слушать порт
408 | ln, err := net.Listen("tcp", ":9999")
409 | if err != nil {
410 | fmt.Println(err)
411 | return
412 | }
413 | for {
414 | // принятие соединения
415 | c, err := ln.Accept()
416 | if err != nil {
417 | fmt.Println(err)
418 | continue
419 | }
420 | // обработка соединения
421 | go handleServerConnection(c)
422 | }
423 | }
424 |
425 | func handleServerConnection(c net.Conn) {
426 | // получение сообщения
427 | var msg string
428 | err := gob.NewDecoder(c).Decode(&msg)
429 | if err != nil {
430 | fmt.Println(err)
431 | } else {
432 | fmt.Println("Received", msg)
433 | }
434 |
435 | c.Close()
436 | }
437 |
438 | func client() {
439 | // соединиться с сервером
440 | c, err := net.Dial("tcp", "127.0.0.1:9999")
441 | if err != nil {
442 | fmt.Println(err)
443 | return
444 | }
445 |
446 | // послать сообщение
447 | msg := "Hello World"
448 | fmt.Println("Sending", msg)
449 | err = gob.NewEncoder(c).Encode(msg)
450 | if err != nil {
451 | fmt.Println(err)
452 | }
453 |
454 | c.Close()
455 | }
456 |
457 | func main() {
458 | go server()
459 | go client()
460 |
461 | var input string
462 | fmt.Scanln(&input)
463 | }
464 |
465 | Этот пример использует пакет `encoding/gob`, который позволяет легко кодировать
466 | выходные данные, чтобы другие программы на Go (или конкретно эта программа, в
467 | нашем случае) могли их прочитать. Дополнительные способы кодирования доступны в
468 | пакете `encoding` (например `encoding/json`), а так-же в пакетах сторонних
469 | разработчиков (например, можно использовать `labix.org/v2/mgo/bson` для работы с
470 | BSON).
471 |
472 | ### HTTP
473 |
474 | HTTP-серверы еще проще в настройке и использовании:
475 |
476 | package main
477 |
478 | import ("net/http" ; "io")
479 |
480 | func hello(res http.ResponseWriter, req *http.Request) {
481 | res.Header().Set(
482 | "Content-Type",
483 | "text/html",
484 | )
485 | io.WriteString(
486 | res,
487 | `
488 |
489 |
490 | Hello World
491 |
492 |
493 | Hello World!
494 |
495 | `,
496 | )
497 | }
498 | func main() {
499 | http.HandleFunc("/hello", hello)
500 | http.ListenAndServe(":9000", nil)
501 | }
502 |
503 | `HandleFunc` обрабатывает URL-маршрут (`/hello`) с помощью указанной функции. Мы
504 | так же можем обрабатывать статические файлы при помощи `FileServer`:
505 |
506 | http.Handle(
507 | "/assets/",
508 | http.StripPrefix(
509 | "/assets/",
510 | http.FileServer(http.Dir("assets")),
511 | ),
512 | )
513 |
514 | ### RPC
515 |
516 | Пакеты `net/rpc` (remote procedure call — удаленный вызов процедур) и
517 | `net/rpc/jsonrpc` обеспечивают простоту вызова методов по сети (а не только из
518 | программы, в которой они используются).
519 |
520 | package main
521 |
522 | import (
523 | "fmt"
524 | "net"
525 | "net/rpc"
526 | )
527 |
528 | type Server struct {}
529 | func (this *Server) Negate(i int64, reply *int64) error {
530 | *reply = -i
531 | return nil
532 | }
533 |
534 | func server() {
535 | rpc.Register(new(Server))
536 | ln, err := net.Listen("tcp", ":9999")
537 | if err != nil {
538 | fmt.Println(err)
539 | return
540 | }
541 | for {
542 | c, err := ln.Accept()
543 | if err != nil {
544 | continue
545 | }
546 | go rpc.ServeConn(c)
547 | }
548 | }
549 | func client() {
550 | c, err := rpc.Dial("tcp", "127.0.0.1:9999")
551 | if err != nil {
552 | fmt.Println(err)
553 | return
554 | }
555 | var result int64
556 | err = c.Call("Server.Negate", int64(999), &result)
557 | if err != nil {
558 | fmt.Println(err)
559 | } else {
560 | fmt.Println("Server.Negate(999) =", result)
561 | }
562 | }
563 | func main() {
564 | go server()
565 | go client()
566 |
567 | var input string
568 | fmt.Scanln(&input)
569 | }
570 |
571 | Эта программа похожа на пример использования TCP-сервера, за исключением того,
572 | что теперь мы создали объект, который содержит методы, доступные для вызова,
573 | а затем вызвали `Negate` из функции-клиента. Посмотрите документацию по
574 | `net/rpc` для получения дополнительной информации.
575 |
576 | ## Получение аргументов из командной строки
577 |
578 | При вызове команды в консоли, есть возможность передать ей определенные
579 | аргументы. Мы видели это на примере вызова команды `go`:
580 |
581 | go run myfile.go
582 |
583 | `run` и `myfile.go` являются аргументами. Мы так же можем передать команде
584 | флаги:
585 |
586 | go run -v myfile.go
587 |
588 | Пакет `flag` позволяет анализировать аргументы и флаги, переданные нашей
589 | программе. Вот пример программы, которая генерирует число от 0 до 6. Но мы
590 | можем изменить максимальное значение, передав программе флаг `-max=100`.
591 |
592 | package main
593 |
594 | import ("fmt";"flag";"math/rand")
595 |
596 | func main() {
597 | // Определение флагов
598 | maxp := flag.Int("max", 6, "the max value")
599 | // Парсинг
600 | flag.Parse()
601 | // Генерация числа от 0 до max
602 | fmt.Println(rand.Intn(*maxp))
603 | }
604 |
605 | Любые дополнительные не-флаговые аргументы могут быть получены с помощью
606 | `flag.Args()`, которая вернет `[]string`.
607 |
608 | ## Синхронизация примитивов
609 |
610 | Предпочтительный способ справиться с параллелизмом и синхронизацией в Go, с
611 | помощью горутин и каналов уже описан в главе 10. Однако, Go предоставляет более
612 | традиционные способы работать с процедурами в отдельных потоках - в пакетах
613 | `sync` и `sync/atomic`.
614 |
615 | ### Мьютексы
616 |
617 | Мьютекс (или взаимная блокировка) единовременно блокирует часть кода в одном
618 | потоке, а так же используется для защиты общих ресурсов из не-атомарных
619 | операций. Вот пример использования мьютекса:
620 |
621 | package main
622 |
623 | import (
624 | "fmt"
625 | "sync"
626 | "time"
627 | )
628 | func main() {
629 | m := new(sync.Mutex)
630 |
631 | for i := 0; i < 10; i++ {
632 | go func(i int) {
633 | m.Lock()
634 | fmt.Println(i, "start")
635 | time.Sleep(time.Second)
636 | fmt.Println(i, "end")
637 | m.Unlock()
638 | }(i)
639 | }
640 |
641 | var input string
642 | fmt.Scanln(&input)
643 | }
644 |
645 | Когда мьютекс (`m`) заблокирован из одного процесса, любые попытки повторно
646 | блокировать его из других процессов приведут к блокировке самих процессов до тех
647 | пор, пока мьютекс не будет разблокирован. Следует проявлять большую
648 | осторожность при использовании мьютексов или примитивов синхронизации из пакета
649 | `sync/atomic`.
650 |
651 | Традиционное многопоточное программирование является достаточно сложным:
652 | сделать ошибку просто, а обнаружить её трудно, поскольку она может зависеть от
653 | специфичных и редких обстоятельств. Одна из сильных сторон Go в том, что он
654 | предоставляет намного более простой и безопасный способ распараллеливания
655 | задач, чем потоки и блокировки.
656 |
--------------------------------------------------------------------------------