├── CNAME ├── _includes ├── header.html ├── disqus.html ├── advert.html ├── ribbon.html ├── head.html ├── prevnext.html ├── footer.html └── counters.html ├── _plugins └── ext.rb ├── img ├── cover.png ├── chapter-01 │ ├── 01.png │ ├── 02.png │ ├── 03.png │ └── 04.png ├── chapter-06 │ └── 01.png ├── chapter-07 │ ├── 01.png │ └── 02.png └── chapter-13 │ └── 01.png ├── .gitignore ├── Gemfile ├── fabfile.py ├── _layouts ├── chapter.html └── default.html ├── README.md ├── _config.yml ├── index.html ├── _assets └── stylesheets │ ├── typo.scss │ ├── reset.scss │ └── main.scss └── _book ├── chapter-14-next-steps.md ├── chapter-12-testing.md ├── chapter-08-pointers.md ├── chapter-11-packages.md ├── chapter-02-your-first-program.md ├── chapter-09-structs-and-interfaces.md ├── chapter-05-control-structures.md ├── chapter-10-concurrency.md ├── chapter-04-variables.md ├── chapter-01-gettting-started.md ├── chapter-03-types.md ├── chapter-07-functions.md ├── chapter-06-arrays-slices-maps.md └── chapter-13-core-packages.md /CNAME: -------------------------------------------------------------------------------- 1 | golang-book.ru -------------------------------------------------------------------------------- /_includes/header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_plugins/ext.rb: -------------------------------------------------------------------------------- 1 | require 'jekyll-assets' 2 | -------------------------------------------------------------------------------- /img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/cover.png -------------------------------------------------------------------------------- /img/chapter-01/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-01/01.png -------------------------------------------------------------------------------- /img/chapter-01/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-01/02.png -------------------------------------------------------------------------------- /img/chapter-01/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-01/03.png -------------------------------------------------------------------------------- /img/chapter-01/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-01/04.png -------------------------------------------------------------------------------- /img/chapter-06/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-06/01.png -------------------------------------------------------------------------------- /img/chapter-07/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-07/01.png -------------------------------------------------------------------------------- /img/chapter-07/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-07/02.png -------------------------------------------------------------------------------- /img/chapter-13/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpoletaev/golang-book/HEAD/img/chapter-13/01.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | .bundle 3 | build 4 | vendor 5 | Gemfile.lock 6 | .idea/ 7 | *.pyc 8 | _site 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jekyll' 4 | gem 'uglifier' 5 | gem 'redcarpet' 6 | gem 'jekyll-assets' 7 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from fabric.contrib.project import rsync_project 2 | from fabric.api import local, env, task 3 | 4 | env.user = 'poletaev' 5 | env.hosts = ['zenwalker.ru'] 6 | 7 | 8 | @task 9 | def deploy(): 10 | local('bundle exec jekyll build') 11 | rsync_project(local_dir='build/', remote_dir='www/golang-book.ru') 12 | -------------------------------------------------------------------------------- /_includes/disqus.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | -------------------------------------------------------------------------------- /_includes/advert.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /_includes/ribbon.html: -------------------------------------------------------------------------------- 1 | Fork me on GitHub 2 | -------------------------------------------------------------------------------- /_layouts/chapter.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 |
7 | Оглавление 8 |
9 |

{{ page.title }}

10 | 11 |
12 | {{ content }} 13 |
14 | 15 |
16 | {% include prevnext.html %} 17 |
18 | 19 | {% include advert.html slot="6541630584" %} 20 | 21 |
22 | {% include disqus.html %} 23 |
24 |
25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Введение в программирование на Go 2 | 3 | Перевод книги [An Introduction to Programming in Go][1]. 4 | 5 | [1]: http://www.golang-book.com/ 6 | 7 | 8 | ## Как собрать книжку 9 | 10 | Для сборки нужен установленый Ruby 2.x. 11 | 12 | **Для установки зависимостей выполните:** 13 | 14 | ``` 15 | bundle install --path .bundle/vendor 16 | ``` 17 | 18 | **Для сборки запустите:** 19 | 20 | ``` 21 | bundle exec jekyll build 22 | ``` 23 | 24 | Собранные .html файлы будут лежать в директории `_site`. 25 | 26 | **Для запуска dev сервера запустите:** 27 | 28 | ``` 29 | bundle exec jekyll serve 30 | ``` 31 | -------------------------------------------------------------------------------- /_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title }} — {{ site.title }}{% else %}{{ site.title }}{% endif %} 7 | 8 | 9 | {% stylesheet main %} 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /_includes/prevnext.html: -------------------------------------------------------------------------------- 1 | {% for chapter in site.book %} 2 | {% if chapter.url == page.url %} 3 | {% assign next = forloop.index0 | plus: 1 %} 4 | {% assign prev = forloop.index0 | minus: 1 %} 5 | {% endif %} 6 | {% endfor %} 7 | 8 |
9 | {% if site.book[prev] %} 10 |
{{ site.book[prev].title }}
11 | {% endif %} 12 | 13 | {% if site.book[next] %} 14 |
{{ site.book[next].title }}
15 | {% endif %} 16 |
17 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Site 2 | title: "Введение в программирование на Go" 3 | url: "http://golang-book.ru" 4 | locale: "ru" 5 | baseurl: "" 6 | 7 | exclude: 8 | - vendor 9 | 10 | # Collections 11 | collections: 12 | book: 13 | output: true 14 | permalink: '/:name.html' 15 | 16 | # Build 17 | permalink: /:title.html 18 | markdown: redcarpet 19 | highlighter: null 20 | 21 | # Assets 22 | assets: 23 | js_compressor: uglifier 24 | css_compressor: sass 25 | cache: false 26 | 27 | # Assets 28 | assets: 29 | js_compressor: uglifier 30 | css_compressor: sass 31 | cache: false 32 | 33 | # Markdown 34 | redcarpet: 35 | extensions: 36 | - no_intra_emphasis 37 | - autolink 38 | - strikethrough 39 | - tables 40 | - footnotes 41 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 |
Калеб Докси
7 |

Введение в программирование на Go

8 |
9 | 10 |
11 |
    12 | {% for chapter in site.book %} 13 |
  1. 14 | {{ chapter.title }} 15 |
  2. 16 | {% endfor %} 17 |
18 |
19 | 20 |
21 |
22 | 23 | Загрузить Go 24 | 25 | 26 | Документация 27 | 28 |
29 | 30 |

Есть вопросы? Спросить можно в сообществе на G+ или в Google Groups.

31 |
32 | 33 | {% include advert.html slot="6541630584" %} 34 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include head.html %} 4 | 5 | 6 | {% include header.html %} 7 | 8 |
9 |
10 | {{ content }} 11 |
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 | ![](/img/chapter-01/01.png) 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 | ![](/img/chapter-01/02.png) 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 | ![](/img/chapter-01/03.png) 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 | ![](/img/chapter-01/04.png) 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 | ![](/img/chapter-07/01.png) 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 | ![](/img/chapter-07/02.png) 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 | ![](/img/chapter-06/01.png) 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 | ![](/img/chapter-13/01.png) 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 | --------------------------------------------------------------------------------