Ссылки:
1 2 3 |
https://go.dev/play/ https://wiki.archlinux.org/title/Go_(%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9) https://github.com/b14esh/c-test/tree/main/02_lesson_golang |
Установка golang на linux
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
0. Установка # debian \ ubuntu apt update && apt upgrade && apt install golang # archlinux pacman -Sy go 1. Компиляция и запуск файлов Go — это компилируемый язык. Первый способ — через команду "go run". Эта команда компилирует исполняемый файл, запускает его и удаляет. Этим способом пользуются, когда нужно разово запустить небольшую программу на Go и забыть. Второй способ — через команду "go build". Она выполняет компиляцию и создает исполняемый файл в текущей директории. go build hello.go # Создаем бинарный файл hello.go В директории с файлом должен появиться новый файл hello. Можно запустить его как обычный исполняемый файл: ./hello Пример: go fmt hello-world.go go build hello-world.go go run build hello-world.go Кросс компиляция под разные платформы: go mod init hello # Создаем Go-модуль GOOS=windows GOARCH=386 go build -o hello_windows.exe # Исполняемый файл для Windows GOOS=windows GOARCH=amd64 go build -o hello_windows64.exe # Файл для Windows с архитектурой x64 GOOS=linux GOARCH=arm go build -o hello_linux_arm # Файл для Linux на arm GOOS=darwin GOARCH=arm64 go build -o hello_mac_arm64 # для mac os |
Показать переменные go / рабочее пространство
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
go env В языке Go поиск программ и их зависимостей (например, import "пакет"), сначала выполняется в каталогах, прописанных в переменную $GOPATH, а затем - в переменной $GOROOT (путь установки go, по умолчанию /usr/lib/go). $GOPATH работает как $PATH и может содержать несколько записей. Это может быть полезно для отделения пакетов, скачанных через go get, от вашего кода, например GOPATH=$HOME/go:$HOME/mygo Создать само рабочее пространство: $ mkdir -p ~/go/src каталог ~/go/src предназначен для хранения исходных текстов проектов. При компиляции Go также создаст каталог bin для исполняемых файлов и pkg для кэша отдельных пакетов. Вы можете добавить ~/go/bin в переменную окружения $PATH для запуска установленных Go-программ: export PATH="$PATH:$HOME/go/bin" |
00. Первый пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
0. Открываем сайт https://go.dev/play/ 1. Вводим код package main import "fmt" func main () { fmt.Println("Hello, Go") } 3. Жмем format а потом run 4. Объяснение: Кнопка Format приводит код в порядок, создаются отступы, выставляются пробелы. Когда вы распространяете свой код, другие разработчики ожидают, что он будет оформлен в стандартном формате Go. Это означает стандартное форматирование отступов, пробелов и т. д., чтобы другим людям было проще читать ваш код. Если в других языках программирования разработчикам приходилось вручную переформатировать свой код, чтобы он соответствовал стилевому руководству, в Go достаточно выполнить команду go fmt, которая автоматически сделает все за вас. package main - Эта строка сообщает, что остальной код относится к пакету "main" import "fmt" - Означает что мы будем использовать код форматирования из пакета "fmt" Функция main играет особую роль - именно: func main () { fmt.Println("Hello, Go") - Эта строка выводит сообщение "Hello,Go!" } Для этого она вызывает функцию Println из пакета "fmt" Пакет представляет собой набор блоков кода, выполняющих похожие операции — например, форматирование строк или построение графических, изображений. Директива package задает имя пакета, частью которого станет код этого файла. В нашем случае используется специальный пакет main; это необходимо для того, чтобы код можно было запускать напрямую (чаще всего в терминале). При запуске программа Go ищет функцию с именем main и выполняет ее в первую очередь, поэтому-то мы и присвоили своей функции имя main. Структура типичного файла Go 1. Директива package. package main 2. Директива import. import "fmt" 3. Собственно код программы. function main{} Каждый файл Go должен начинаться с директивы package Каждый файл Go должен импортировать все пакеты, которые в нем используются Файлы Go должны импортировать только те пакеты, которые в них используются. (Это ускоряет компиляцию кода!) Компилятор Go ищет функцию с именем main, чтобы выполнить ее при запуске программы В Go учитывается регистр символов. fmt.Println — действительное имя, но имени fmt.println не существует Функция Println не принадлежит пакету main, поэтому перед вызовом функции необходимо указать имя пакета |
01. Вызов функций
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
В нашем примере вызывается функция Println из пакета fmt. Чтобы вызвать функцию, введите имя функции (в данном случае Println) и пару круглых скобок. Как и многие функции, Println может получать один или несколько аргументов — значений, с которыми должна работать функция. Аргументы перечисляются в круглых скобках после имени функции fmt.Println("First argument", "Second argument") Хотя функции Println и можно передать несколько аргументов, она может вызываться без них. Когда мы будем заниматься другими функциями, вы заметите, что они обычно должны получать строго определенное количество аргументов. Если аргументов будет слишком мало или слишком много, то вы получите сообщение об ошибке, в котором будет указано ожидаемое количество аргументов. В этом случае программу нужно будет исправить. package main import "fmt" func main () { fmt.Println("First argument", "Second argument") } |
02. Функция Println
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
При помощи функции Println можно узнать, как идет выполнение программы. Все аргументы, переданные этой функции, выводятся в терминале (а их значения разделяются пробелами) После вывода всех аргументов Println переходит на следующую строку в терминале. (Отсюда суффикс «ln» — сокращение от «line» — в конце имени.) package main import "fmt" func main() { fmt.Println("One", "Two", "Three") fmt.Println("Another line") fmt.Println("End") } |
03. Использование функций из других пакетов. / Возвращаемые значения функций.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
Весь код нашей первой программы является частью пакета main, но функция Println принадлежит пакету fmt (сокращение от «format»). Чтобы в программе можно было вызвать функцию Println, необходимо сначала импортировать пакет, содержащий эту функцию. // - комментарий package main import "fmt" // Пакет fmt необходимо импортировать чтобы вызвать его функции Println func main() { fmt.Println("Hello, Go!") // Сообщает что вызываемая функция является частью пакета fmt } После того как пакет будет импортирован, вы сможете вызывать функции из этого пакета. Для этого укажите имя пакета, поставьте точку (.) и введите имя нужной функции. fmt.Println() fmt - пакет .Println() - имя функции Следующий пример демонстрирует вызов функций из двух других пакетов. Так как мы собираемся импортировать несколько пакетов, то переходим на альтернативный формат инструкции, который позволяет перечислять в круглых скобках сразу несколько пакетов, по одному имени пакета в строке. package main import ( "math" "strings" ) func main() { math.Floor(2.75) strings.Title("head first go") } !!! Программа ничего не выводит! :) При вызове функции fmt.Println вам не нужно обмениваться с ней дополнительной информацией. Вы передаете Println одно или несколько выводимых значений и ожидаете вывод. Но иногда программа должна вызвать функцию и получить от нее дополнительные данные. Из-за этого в большинстве языков программирования функции имеют возвращаемые значения, которые вычисляются функциями и возвращаются на сторону вызова. Функции math.Floor и strings.Title относятся к числу функций, имеющих возвращаемое значение. Функция math.Floor получает число с плавающей точкой, округляет его до ближайшего меньшего целого и возвращает полученное число. А функция strings.Title получает строку, преобразует первую букву каждого слова к верхнему регистру и возвращает полученную строку. Чтобы увидеть результаты этих вызовов функций, необходимо взять возвращаемые значения и передать их fmt.Println: package main import ( "fmt" "math" "strings" ) func main() { fmt.Println(math.Floor(2.75)) fmt.Println(strings.Title("head first go")) } Что делает: строка: fmt.Println(math.Floor(2.75)) Функция fmt.Println вызывается для возвращаемого значения функции math.Floor. math.Floor получает число, округляет его в меньшую сторону и возвращает полученное значение. строка: fmt.Println(strings.Title("head first go")) Функция fmt.Println вызывается для возвращаемого значения функции strings.Title. strings.Title получает строку и возвращает новую строку, в которой все слова начинаются с буквы верхнего регистра. Результат: 2 Head First Go |
04. шаблон программы GO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
vim test.go ----------- package main import "fmt" func main() { fmt.Println("hello world") } ----------- Проверка: Производим форматирование скрипта go fmt test.go Производим тестовый запуск go run test.go Производим сборку go build test.go Проверяем еще раз работоспособность |
05. Строки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
В аргументах Println передавались строки. Строка представляет собой последовательность байтов, которые обычно представляют символы текста. Строки можно определять прямо в программе в виде строковых литералов: компилятор Go интерпретирует текст, заключенный в двойные кавычки, как строку Открывающая двойная кавычка "Hello, Go!" Закрывающая двойная кавычка Результат: Hello, Go! Некоторые управляющие символы, которые неудобно вводить с клавиатуры (символы новой строки, табуляции и т. д.), внутри строк могут представляться в виде служебных последовательностей: символа «обратный слеш», за которым следует другой символ (или символы). \n Символ новой строки \t Символ табуляции \" Двойная кавычка \\ Обратный слеш Пример 0: "Hello,\nGo!" Результат: Hello, Go! Пример 1: "Hello, \tGo!" Результат: Hello, Go! Пример 2: "\"Hello, Go!\"" "Hello, Go!" |
06. Руны
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Если строки обычно используются для представления последовательностей символов, то руны в языке Go представляют отдельные символы. Строковые литералы заключаются в двойные кавычки ("), а рунные литералы записываются в одиночных кавычках ('). В программах Go могут использоваться практически любые символы любых мировых языков, потому что в Go для хранения рун используется стандарт Юникод. Руны хранятся в виде числовых кодов, а не в виде символов; если передать руну функции fmt.Println, то выведется числовой код, а не исходный символ. В рунных литералах (как и в строковых) можно использовать служебные последовательности для представления символов, которые неудобно вводить с клавиатуры для включения в программу. 'A' - 65 'B' - 66 'Ж' - 1174 '\t' - 9 '\n' - 10 '\\' - 92 Пример кода: package main import "fmt" func main() { fmt.Println('A', 'B', 'Ж', '\t', '\n', '\\') } |
07. Логические значения
1 2 3 4 |
Логические величины принимают всего два возможных значения: true или false. Они особенно удобны в условных командах, в которых выполнение блока кода зависит от того, истинно или ложно некоторое условие. Чуть позже разберем... |
08. Числа
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Числа тоже можно определять прямо в программном коде. Это еще проще, чем определять строковые литералы: просто введите нужное число. Как вы вскоре увидите, в языке Go целые числа и числа с плавающей точкой интерпретируются как разные типы. Помните, что целое число можно отличить от числа с плавающей точкой по разделителю дробной части — точке. Пример: 42 Целое число. 3.1415 Число с плавающей точкой Пример в коде: package main import "fmt" func main() { fmt.Println(0, 3.1415) } |
09. Математические операции и сравнения
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Основные математические операторы Go работают так же, как и в большинстве других языков. оператор + выполняет сложение оператор - выполняет вычитание оператор * — умножение оператор / — деление Пример кода: package main import "fmt" func main() { fmt.Println("\t1 + 2 =", 1+2, "\n\t5.4 - 2.2 =", 5.4-2.2, "\n\t3 * 4 =", 3*4, "\n\t7.5 / 5 =", 7.5/5) } При помощи операторов < и > можно сравнить два значения и проверить, какое из них больше другого. оператор == (два знака равенства) проверяет, что два значения равны оператор != проверяет, что два значения не равны оператор <= проверяет, что второе значение меньше или равно первому оператор >= проверяет, что второе значение больше или равно первому Результатом сравнения является логическое значение (true или false) package main import "fmt" func main() { fmt.Println("\t4 < 6 \t\t\t", 4 < 6, "\n\t4 > 6 \t\t\t", 4 > 6, "\n\t2 + 2 == 5 \t\t", 2+2 == 5, "\n\t2 + 2 !=5 \t\t", 2+2 != 5, "\n\t4 < = 6 \t\t", 4 <= 6, "\n\t4 >= 4\t\t\t", 4 >= 4) } |
10. Типы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
В предыдущем примере кода использовалась функция math.Floor, округляющая число с плавающей точкой с уменьшением, и функция strings.Title, преобразующая первую букву каждого слова в строке к верхнему регистру. Логично ожидать, что в аргументе функции Floor передается число, а в аргументе функции Title передается строка. Но что произойдет, если передать функции Floor строку, а функции Title число? Go выводит два сообщения об ошибках — по одному для каждого вызова функции, а программа даже не запускается! Объекты в окружающем мире часто можно разделить на типы в зависимости от того, для чего они используются. Машину или грузовик нельзя съесть на завтрак, а на омлете или чашке с кукурузными хлопьями не поедешь на работу — они предназначены для другого. В языке Go используется статическая типизация — это означает, что типы всех значений известны еще до запуска программы. Функции ожидают, что их аргументы относятся к конкретным типам, а их возвращаемые значения тоже имеют типы (которые могут совпадать или не совпадать с типами аргументов). Go — язык со статической типизацией. Если вы используете неправильный тип значения в неподходящем месте, Go сообщит вам об этом. Пример не правильного использование типов: package main import ( "fmt" "math" "strings" ) func main() { fmt.Println(math.Floor("head first go")) fmt.Println(strings.Title(2.75)) } Пример ошибки с неправильными типами: # command-line-arguments ./00_type_bad.go:10:25: cannot use "head first go" (type untyped string) as type float64 in argument to math.Floor ./00_type_bad.go:11:28: cannot use 2.75 (type untyped float) as type string in argument to strings.Title |
11. Узнаем типы значений
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Чтобы узнать тип любого значения, передайте его функции TypeOf из пакета reflect. Давайте узнаем типы некоторых значений, которые уже встречались в примерах программ: package main import ( "fmt" "reflect" ) func main() { fmt.Println(reflect.TypeOf(42)) fmt.Println(reflect.TypeOf(3.1234)) fmt.Println(reflect.TypeOf(true)) fmt.Println(reflect.TypeOf("Hello world")) } Типы: int Целое число (не имеющее дробной части) float64 Число с плавающей точкой. Тип используется для хранения чисел, имеющих дробную часть. (Для хранения числа используются 64 бита данных, отсюда суффикс 64 в имени. Значения типа float64 обеспечивают очень высокую, хотя и не бесконечную точность.) bool Логическое значение (true или false) string Строка— последовательность данных, которые обычно представляют символы текста |
12. Объявление переменных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
В языке Go переменная представляет собой область памяти, в которой хранится значение. Чтобы к переменной можно было обращаться по имени, используйте объявление переменной. Объявление состоит из ключевого слова var, за которым следует имя и тип значений, которые будут храниться в переменной. var quantity int var length, width float64 var customerName string После того как переменная будет объявлена, ей можно будет присвоить любое значение этого типа оператором = (один знак равенства): quantity = 2 customerName = "Damon Cole" В одной команде можно присвоить значения сразу нескольким переменным. Для этого перечислите имена переменных слева от = и такое же количество значений в правой части, разделяя их запятыми. length, width = 1.2, 2.4 После того как переменной будет присвоено значение, вы сможете использовать ее в любом контексте, где может использоваться исходное значение: package main import "fmt" func main() { var quantity int var length, width float64 var customerName string quantity = 4 length, width = 1.2, 2.4 customerName = "Damon Cole" fmt.Println(customerName) fmt.Println("has ordered", quantity, "sheets") fmt.Println("each with an area of") fmt.Println(length*width, "square meters") } Пример 2: package main import "fmt" func main() { var x1 int var x2 int var sumx int x1 = 500 x2 = 600 sumx = x1+x2 fmt.Println(x1, "+", x2, "=", sumx) } Если значение переменной известно заранее, можно объявить переменную и присвоить ей значение в одной строке: var quantity int = 4 var length, width float64 = 1.2, 2.4 var customerName string = "Damon Cole" Существующим переменным можно присваивать новые значения, но эти значения должны относиться к тому же типу. Статическая типизация в Go гарантирует, что переменной не будет случайно присвоено значение неподходящего типа. Если значение переменной присваивается одновременно с ее объявлением, тип переменной в объявлении обычно не указывают. Тип значения, присвоенного переменной, будет считаться типом этой переменной. var quantity = 4 var length, width = 1.2, 2.4 var customerName = "Damon Cole" fmt.Println(reflect.TypeOf(quantity)) fmt.Println(reflect.TypeOf(length)) fmt.Println(reflect.TypeOf(width)) fmt.Println(reflect.TypeOf(customerName)) Пример: package main import "fmt" func main() { var x1 int = 700 var x2 int = 800 var sumx = x1+x2 fmt.Println(x1, "+", x2, "=", sumx) } |
13. Нулевые значения
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Если переменная объявляется без присваивания значения, то она будет содержать нулевое значение для этого типа. Для числовых типов нулевое значение равно 0. Пример: package main import "fmt" func main() { var myInt int var myFloat float64 fmt.Println(myInt, myFloat) } Но для других типов значение 0 будет недействительным, поэтому нулевое значение для этого типа будет отличаться. Скажем, для строковых переменных нулевым значением является пустая строка, а для переменных bool — значение false. Пример: package main import "fmt" func main() { var myString string var myBool bool fmt.Println(myString, myBool) } |
14. Короткие объявления переменных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Вместо того чтобы явно объявлять тип переменной и позднее присваивать ей значение оператором =, вы совмещаете эти две операции с помощью синтаксиса :=. Обычное объявление переменной выглядит вот так: var quantity int = 4 var length, width float64 = 1.2, 2.4 var customerName string = "Damon Cole" Короткие объявления переменных выглядит так: quantity := 4 length, width := 1.2, 2.4 customerName := "Damon Cole" Пример в коде: package main import "fmt" func main() { quantity := 4 length, width := 1.2, 2.4 customerName := "Damon Cole" fmt.Println(customerName) fmt.Println("has ordered", quantity, "sheets") fmt.Println("each with an area of") fmt.Println(length*width, "square meters") } Явно объявлять тип переменной не обязательно: тип значения, присвоенного переменной, становится типом этой переменной. Поскольку короткие объявления переменных очень удобны и компактны, они используются чаще обычных объявлений. Впрочем, время от времени вам будут встречаться обе формы, поэтому важно знать их. |
15. Возможные ошибки при добавлении переменных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
00. хз что это было.... // не знаю что это было но назвав файл 05_vats_test.go он не запускался :( // go fmt 05_vats_test.go - выполняло форматирование // go run 05_vats_test.go - приводило к ошибке "go run: cannot run *_test.go files (05_vars_test.go)" // go build 05_vats_test.go - молча выполнялся но бинарник не производился // mv 05_vats_test.go 05_dz.go - но когда я переименовал файл, файл стал выполнятся, сборка также свершилась. Код для тестов: package main import "fmt" func main() { quantity := 4 length, width := 1.2, 2.4 customerName := "Damon Cole" fmt.Println(customerName) fmt.Println("has ordered", quantity, "sheets") fmt.Println("each with an area of") fmt.Println(length*width, "square meters") } 01. Пример, добавить второе объявление для той же переменной: quantity := 4 quantity := 4 Ошибка при выполнении: # command-line-arguments ./07_dd.go:7:11: no new variables on left side of := Переменную можно объявить только один раз. (Хотя при желании ей можно сколько угодно раз присваивать новые значения. Также можно объявлять другие переменные с тем же именем, но для этого они должны принадлежать другой области видимости. 02. Пример, убрать символ «:» из короткого объявления переменной: quantity = 4 Ошибка: # command-line-arguments ./07_dd.go:8:2: undefined: quantity ./07_dd.go:12:29: undefined: quantity Если вы забудете поставить двоеточие, конструкция рассматривается как присваивание, а не как объявление, а переменной, которая не была объявлена, нельзя будет присвоить значение. 03. Пример, присвоить строковое значение переменной int: quantity := 4 quantity = "a" Ошибка: # command-line-arguments ./07_dd.go:10:11: no new variables on left side of := ./07_dd.go:10:11: cannot use "a" (type untyped string) as type int in assignment Переменным могут присваиваться только значения того же типа. 04. Пример, количества переменных и значений не совпадают length, width := 1.2 Ошибка: # command-line-arguments ./07_dd.go:12:23: assignment mismatch: 2 variables but 1 values Вы должны предоставить значение для каждой переменной и переменную для каждого значения 05. Пример, удалить код, в котором используется переменная //fmt.Println(customerName) - например можно просто закомментировать Ошибка: # command-line-arguments ./07_dd.go:14:2: customerName declared but not used Все объявленные переменные должны использоваться в программе. Если вы удаляете код, в котором используется переменная, необходимо также удалить и объявление. |
16. Правила выбора имен для переменных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
В Go существует один простой набор правил, применяемых к именам переменных, функций и типов: !!! Эти правила должны обязательно выполняться на уровне языка 00. Имя должно начинаться с буквы и может содержать любое количество дополнительных букв и цифр. 01. Если имя переменной, функции или типа начинается с буквы верхнего регистра, оно считается экспортируемым и может использоваться в других пакетах, кроме текущего. (Именно поэтому буква P в fmt.Println имеет верхний регистр: это нужно для того, чтобы его можно было использовать в main или любом другом пакете.) Если имя переменной/функции/типа начинается с буквы нижнего регистра, оно считается не экспортируемым. Такие имена доступны только в текущем пакете. !!! Но сообщество Go также соблюдает ряд дополнительных соглашений: 02. Если имя состоит из нескольких слов, каждое слово после первого должно начинаться с буквы верхнего регистра, и они должны следовать друг за другом без разделения пробелами: topPrice, RetryConnection и т. д. (Первая буква имени имеет верхний регистр только в том случае, если оно должно экспортироваться из пакета.) Этот стиль записи часто называется верблюжьим регистром, потому что буквы верхнего регистра напоминают горбы у верблюда. 03. Если смысл имени очевиден по контексту, в сообществе Go принято сокращать его: использовать i вместо index, max вместо maximum и т. д. Примеры: Нормально: sheetLength TotalUnits i нарушают соглашения: sheetlength - Остальные слова должны начинаться с буквы верхнего регистра! Total_Units - Допустимо, но слова должны записываться подряд! index - Хорошо бы заменить сокращением! !!! Только переменные, функции и типы, имена которых начинаются с буквы верхнего регистра, считаются экспортируемыми, то есть доступными за пределами текущего пакета. |
17. Преобразования
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
Пример не рабочего кода: package main import "fmt" func main() { var length float64 = 1.2 var width int = 2 fmt.Println("Area is", length*width) fmt.Println("length > width?", length > width) } При выполнении математических операций и операций сравнения в Go значения должны относиться к одному типу. Если же типы различаются, то при попытке выполнения кода вы получите сообщение об ошибке. Ошибка: # command-line-arguments ./02_badcode.go:8:31: invalid operation: length * width (mismatched types float64 and int) ./02_badcode.go:9:40: invalid operation: length > width (mismatched types float64 and int) Этот принцип действует и при присваивании новых значений переменным. Если тип присваиваемого значения не соответствует объявленному типу переменной, вы получите сообщение об ошибке. package main import "fmt" func main() { var length float64 = 1.2 var width int = 2 length = width fmt.Println(length) } Ошибка: # command-line-arguments ./03_badcode.go:8:8: cannot use width (type int) as type float64 in assignment Проблема решается преобразованием значений одного типа к другому типу. Для этого следует указать тип, к которому должно быть преобразовано значение, а за ним преобразуемое значение в скобках. var myInt int = 2 float64(myInt) В результате преобразования вы получите новое значение нужного типа. Пример кода: package main //import "fmt" import ( "fmt" "reflect" ) func main() { var myInt int = 2 fmt.Println(reflect.TypeOf(myInt)) fmt.Println(reflect.TypeOf(float64(myInt))) } Ошибка забыл добавить компонент reflect # command-line-arguments ./04_pre.go:7:14: undefined: reflect ./04_pre.go:8:14: undefined: reflect |
18. Преобразование переменных
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
Пример не рабочего кода: package main import "fmt" func main() { var length float64 = 1.2 var width int = 2 fmt.Println("Area is", length*width) fmt.Println("length > width?", length > width) } Исправим код: package main import "fmt" func main() { var length float64 = 1.2 var width int = 2 fmt.Println("Area is", length*float64(width)) fmt.Println("length > width?", length > float64(width)) } Теперь математические операции и операции сравнения работают правильно! Попробуем преобразовать значение int к типу float64 перед присваиванием переменной float64: var length float64 = 1.2 var width int = 2 length = float64(width) - Преобразование int к типу float64 перед присваиванием переменной float64 fmt.Println(length) Всегда держите в голове, как преобразования изменяют выходные значения. Например, переменные float64 могут хранить дробные значения, а переменные int — нет. Когда вы преобразовываете float64 в int, дробная часть просто отбрасывается! Это может внести путаницу в любые операции, выполняемые с результирующим значением. var length float64 = 3.75 var width int = 5 width = int(length) - В результате этого преобразования дробная часть теряется! fmt.Println(width) Но если действовать внимательно, вы поймете, что преобразования исключительно важны для работы с Go. С ними вы сможете совместно использовать несовместимые типы. Пример: var price int = 100 var taxRate float64 = 0.08 var tax float64 = float64(price) * taxRate fmt.Println(tax) |
19. Кратко о чем писал выше
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
go build - Компилирует файлы с исходным кодом в двоичные файлы go run - Компилирует и запускает программу без сохранения в исполняемом файле go fmt - Переформатирует исходные файлы с использованием стандартного форматирования Go go version - Выводит текущую версию Go - Пакет представляет собой группу взаимосвязанных функций и других блоков кода. - Прежде чем использовать функции из пакета в файле Go, необходимо импортировать этот пакет. - Строка — последовательность байтов, обычно представляющих символы текста. - Руна представляет отдельный символ текста. - Два самых распространенных числовых типа Go — int (для хранения целых чисел) и float64 (для хранения чисел с плавающей точкой). - Тип bool используется для хранения логических значений (true или false). - Переменная представляет собой блок памяти для хранения значения заданного типа. - Если переменной не присвоено значение, то она содержит нулевое значение для своего типа. Примеры нулевых значений: 0 для переменных int или float64, "" для строковых переменных. - Объявление переменной можно совместить с присваиванием ей значения при помощи короткого объявления переменной :=. - К переменной, функции или типу можно обращаться из кода других пакетов только в том случае, если ее имя начинается с буквы верхнего регистра. - Команда go fmt автоматически переформатирует исходные файлы по стандартам Go. Всегда выполняйте команду go fmt для любого кода, который вы собираетесь передавать другим разработчикам. - Команда go build компилирует исходный код Go в двоичный формат, который может выполняться компьютером. - Команда go run компилирует и выполняет программу без сохранения в исполняемом файле в текущем каталоге. Вызовы функций Функция представляет собой блок кода, который может вызываться в других местах программы. При вызове функции ей могут передаваться данные в аргументах. Типы У всех значений в Go имеется тип, который определяет, для чего могут использоваться эти значения. Математические операции и сравнения с разными типами запрещены, хотя при необходимости значение можно преобразовать к другому типу. В переменных Go могут храниться значения только того типа, с которым они были объявлены |
20. Вызов методов на примере пакета time
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
В языке Go можно определять методы: функции, связанные со значениями определенного типа. Методы Go похожи на связываемые с "объектами" методы других языков программирования, но в Go все проще. В пакете time определен тип Time, представляющий дату (год, месяц и день) и время (час, минуты, секунды и т. д.). Каждое значение time.Time содержит метод Year, который возвращает год. Приведенный ниже код использует этот метод для вывода текущего года: package main import ( "fmt" "time" //Необходимо импортировать пакет «time», чтобы использовать тип time.Time ) func main() { var now time.Time = time.Now() //Метод time.Now возвращает значение time.Time, представляющее текущую дату и время. var year int = now.Year() //У значений time.Time имеется метод Year, который возвращает текущий год. fmt.Println(year) } Функция time.Now возвращает новое значение Time для текущей даты и времени; это значение сохраняется в переменной now. Затем мы вызываем метод Year для значения, ссылка на которое хранится в now: now.Year() | | | Вызывает метод Year для значения time.Time Содержит значение time.Time Метод Yer возвращает целое значение года, которое выводится программой. Методы — это функции, связанные со значениями конкретного типа |
21. Вызов методов на примере пакета strings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Пакет strings содержит тип Replacer, который ищет подстроку в строке и заменяет каждое вхождение этой подстроки в другой строке. Следующий код заменяет каждый символ # в строке буквой o: package main import ( "fmt" "strings" ) func main() { broken := "G# r#cks!" replacer := strings.NewReplacer("#", "o") fixed := replacer.Replace(broken) fmt.Println(fixed) } Функция strings.NewReplacer получает аргументы — заменяемую строку ("#") и заменяющую строку ("o") — и возвращает значение strings.Replacer. Когда мы передаем строку методу Replace значения Replacer, то метод возвращает строку, в которой выполнена указанная замена офф топ: Значение. Имя метода. | | replacer.Replace(broken) now.Year() | | Значение. Имя метода. |
21. Комментарии
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Самая распространенная форма комментариев обозначается двумя слешами (//). Все символы от // до конца строки рассматриваются как часть комментария. Комментарий // может занимать всю строку или следовать после кода. // Общее количество виджетов в системе. var TotalCount int // Должно быть целым числом. Более редкая форма комментариев занимает несколько строк. Блочные комментарии начинаются с /* и заканчиваются */, а весь текст между этими маркерами (включая символы новой строки) является частью комментария. /* Пакет widget включает все функции, используемые для работы с виджетами. */ |
22. Получение значения от пользователя
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
Пример кода: //Внимание: этот код не будет компилироваться в том виде, в котором он здесь приведен // pass_fail сообщает, сдал ли пользователь экзамен. package main import ( "bufio" "fmt" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input := reader.ReadString('\n') - Возвращает текст, введенный пользователем до нажатия клавиши Enter. fmt.Println(input) - Вывод введенных данных. } Сначала нужно запросить данные у пользователя, и для вывода приглашения используется функция fmt.Print. (В отличие от Println, функция Print не переходит на новую строку в терминале после вывода сообщения; таким образом, запрос и введенные данные размещаются в одной строке.) Затем нам понадобится механизм чтения (получения и хранения) ввода из стандартного ввода программы, в который поступает весь ввод с клавиатуры. Строка reader := bufio. NewReader(os.Stdin) сохраняет bufio.Reader в переменной reader, которая сделает это за вас. Чтобы получить введенные данные от пользователя, мы вызываем метод ReadString для Reader. Методу ReadString требуется аргумент с руной (символом), отмечающей конец ввода. Мы хотим прочитать весь текст, введенный пользователем до нажатия Enter, поэтому ReadString передается руна новой строки. После того как от пользователя будут получены данные, мы просто выводим их. bufio.Reader - Пока достаточно знать, что это средство позволяет читать текст, введенный с клавиатуры. Возвращает новое значение bufio.Reader. | reader := bufio.NewReader(os.Stdin) | Reader читает данные из стандартного ввода (с клавиатуры). Возвращает данные, введенные пользователем, в виде строки | input := reader.ReadString('\n') | Будет прочитан весь текст до руны новой строки. Ошибка: 0. go build 00_input.go - при сборке # command-line-arguments ./00_input.go:14:8: assignment mismatch: 1 variable but reader.ReadString returns 2 values 2. go run 00_input.go - при запуске без сборки # command-line-arguments ./00_input.go:14:8: assignment mismatch: 1 variable but reader.ReadString returns 2 values 3. из документации / мы должны были увидеть: multiple-value reader.ReadString() in single-value context Мы пытаемся прочитать ввод с клавиатуры, но получаем сообщение об ошибке. Компилятор сообщает о проблеме в следующей строке кода: input := reader.ReadString('\n') В большинстве языков программирования функции и методы могут возвращать только одно значение, но в Go функция может возвращать сколько угодно значений. Чаще всего множественные возвращаемые значения в Go используются для возвращения дополнительного значения ошибки, по которому можно определить, не возникли ли проблемы во время выполнения функции или метода. Несколько примеров: bool, err := strconv.ParseBool("true") - Возвращает ошибку, если строку не удается преобразовать в логическое значение. file, err := os.Open("myfile.txt") - Возвращает ошибку, если файл не удалось открыть. response, err := http.Get("http://golang.org") - Возвращает ошибку, если страницу не удалось загрузить. И в чем проблема? Просто добавьте переменную для хранения ошибки и не используйте ее! Но Go не позволит объявить переменную, если она не используется в программе!!! input, err := reader.ReadString('\n') - не сработает !!! Ошибка "err declared and not used" Go требует, чтобы каждая объявленная переменная также использовалась где-то в программе. Если вы добавите переменную err и не проверите ее, программа не будет компилироваться. Неиспользуемые переменные часто указывают на ошибки в программе; это один из примеров того, как Go помогает вам выявлять и исправлять ошибки!а |
23. Вариант 1. Игнорировать возвращаемое значение ошибки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Если имеется значение, которое должно быть присвоено переменной, но вы не собираетесь его использовать, можно воспользоваться пустым идентификатором Go. Присваивание значения пустому идентификатору фактически приводит к тому, что оно теряется (а другим читателям вашего кода становится очевидно, что вы поступаете так намеренно). Чтобы использовать пустой идентификатор, просто введите символ подчеркивания ( _ ) в команде присваивания, где должно использоваться имя переменной. Попробуем использовать пустой идентификатор вместо обычной переменной err: // pass_fail сообщает, сдал ли пользователь экзамен. package main import ( "bufio" "fmt" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, _ := reader.ReadString('\n') fmt.Println(input) } |
24. Вариант 2. Обработка ошибки / пакет log
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
Игнорирование ошибок не является признаком небрежного кода. Из предыдущего примера кода: fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, _ := reader.ReadString('\n') | Возвращаемый признак ошибки игнорируется! fmt.Println(input) - Выводит значение, которое может быть недействительным! В данном случае при возникновении ошибки правильнее было бы предупредить пользователя и прервать выполнение программы. Пакет log содержит функцию Fatal, которая выполняет обе операции одновременно: вывод сообщения в терминале и остановку программы. (В этом контексте «фатальной» называется ошибка, «смертельная» для вашей программы.) Давайте избавимся от пустого идентификатора "_" и заменим его переменной err, чтобы ошибка снова сохранялась в программе. Затем воспользуемся функцией Fatal для вывода сообщения об ошибке и прерывания работы программы. Пример кода: // pass_fail сообщает, сдал ли пользователь экзамен. package main import ( "bufio" "fmt" "log" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') log.Fatal(err) fmt.Println(input) Но при запуске программы обнаружится новая проблема: Enter a grade: 22 2023/07/26 19:53:50 <nil> exit status 1 Такие функции и методы, как ReadString, возвращают значение ошибки nil, что по сути означает «здесь ничего нет». Другими словами, если переменная err равна nil, значит, ошибки не было. Но наша программа написана так, что она просто сообщает об ошибке nil! Что же нужно сделать, чтобы программа завершалась только в том случае, если значение переменной не равно nil? Для этого можно воспользоваться условными командами, в которых блок кода (одна или несколько команд, заключенных в фигурные скобки) выполняется только при истинности заданного условия. if 1 < 2 { fmt.Println("It's true!") } На этом пока стопе нужно разобраться с условными командами |
25. Условные команды
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
Выражение вычисляется, и если полученный результат равен true, то выполняется код в теле условного блока. Если же результат равен false, условный блок пропускается. if true { fmt.Println("I'll be printed!") } if false { fmt.Println("I won't!") } Как и многие другие языки, Go поддерживает множественное ветвление в условных командах. Такие команды записываются в форме if...else if...else. if grade == 100 { fmt.Println("Perfect!") } else if grade >= 60 { fmt.Println("You pass.") } else { fmt.Println("You fail!") } Условные команды используют логическое выражение (результат которого равен true или false), чтобы определить, должен ли выполняться содержащийся в них код. if 1 == 1 { fmt.Println("I'll be printed!") } if 1 >= 2 { fmt.Println("I won't!") } if 1 > 2 { fmt.Println("I won't!") } if 2 <= 2 { fmt.Println("I'll be printed!") } if 1 < 2 { fmt.Println("I'll be printed!") } if 2 != 2 { fmt.Println("I won't!") } Если код должен выполняться только в том случае, когда условие дает результат false, используйте ! — оператор логического отрицания. Этот оператор берет значение true и превращает его в false или же берет значение false и превращает его в true. if !true { fmt.Println("I won't be printed!") } if !false { fmt.Println("I will!") } Если код должен выполняться только в том случае, когда истинны оба условия, используйте оператор && («и»). А если он должен выполняться лишь тогда, когда истинно хотя бы одно из двух условий, используйте оператор || («или»). if true && true { fmt.Println("I'll be printed!") } if false || true { fmt.Println("I'll be printed!") } if true && false { fmt.Println("I won't!") } if false || false { fmt.Println("I won't!") } В: Другой язык программирования требует, чтобы условие команды if заключалось в круглые скобки. В Go это не обязательно? О: Нет. Более того, команда go fmt удалит все круглые скобки, добавленные вами, если только они не используются для определения порядка операций. |
26. Условная выдача фатальной ошибки (пакет log) / продолжение
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
Наша программа сообщает об ошибке и аварийно завершается, хотя данные с клавиатуры были успешно прочитаны. Сохраняет возвращаемое значение ошибки в переменной. | input, err := reader.ReadString('\n') log.Fatal(err) - Сообщаем о возвращаемом значении ошибки Напоминаю ошибка выглядела вот так: Enter a grade: 22 2023/07/26 19:53:50 <nil> exit status 1 Мы знаем, что если значение в переменной err равно nil, это говорит о том, что данные с клавиатуры были прочитаны успешно. Теперь, познакомившись с командами if, попробуем обновить код, чтобы сообщение об ошибке и завершение программы происходило только в том случае, если значение err не равно nil. Пример кода с обработкой ошибки: // Выводит строку которую ввели package main import ( "bufio" "fmt" "log" "os" ) func main() { fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } fmt.Println(input) } Вот еще один пример: // сообщает размер файла my1.txt package main import ( "fmt" "log" "os" ) func main() { fileInfo, err := os.Stat("my1.txt") if err != nil { log.Fatal(err) } fmt.Println(fileInfo.Size()) } |
27. Избегайте замещения имен
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
Пример кода: fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } Ранее говорили, что стараетесь избегать сокращений. А здесь переменной присваивается имя err вместо error! Называть переменную error не рекомендуется, потому что это приведет к замещению типа с именем error Объявляя переменную, проследите за тем, чтобы ее имя не совпадало с именами существующих функций, пакетов, типов или других переменных. Если такое имя уже существует во внешней области видимости (вскоре мы поговорим об областях видимости), ваша переменная заместит его, то есть будет перехватывать все обращения к нему. И часто это нежелательно. В следующем фрагменте объявляется переменная с именем int, которая замещает имя типа, переменная с именем append, которая замещает имя встроенной функции (функция append будет представлена в главе 6), а также переменная с именем fmt, которая замещает имя импортированного пакета. Эти имена создают путаницу, но сами по себе не порождают ошибок... Пример ошибок в коде package main import "fmt" func main() { var int int = 12 //Переменная с именем «int» замещает имя встроенного типа «int»! var append string = "minutes of bonus footage" //Переменная с именем «append» замещает имя встроенной функции «append»! var fmt string = "DVD" //Переменная с именем «fmt» замещает имя импортированного пакета «fmt»! var count int //int» теперь относится к переменной, объявленной выше, а не к числовому типу var languages = append([]string{}, "Español") //Имя «append» теперь обозначает переменную, а не функцию! fmt.Println(int, append, "on", fmt, languages) //Имя «fmt» теперь обозначает переменную, а не пакет } # command-line-arguments ./06_varerrorcode.go:3:8: imported and not used: "fmt" ./06_varerrorcode.go:9:6: int is not a type ./06_varerrorcode.go:10:24: cannot call non-function append (type string), declared at ./06_varerrorcode.go:7:6 ./06_varerrorcode.go:11:5: fmt.Println undefined (type string has no field or method Println) Чтобы не путаться самому и не путать коллег, старайтесь по возможности избегать замещения имен. В данном случае проблема решается простым выбором неконфликтующих имен для переменных: Исправили код: package main import "fmt" func main() { var count int = 12 var suffix string = "minutes of bonus footage" var format string = "DVD" var languages = append([]string{}, "Español") fmt.Println(count, suffix, "on", format, languages) } Вот почему при объявлении переменных, предназначенных для хранения ошибок, мы присваивали им имя err вместо error, мы хотели предотвратить замещение имени типа ошибки именем переменной. package main import "fmt" fmt.Print("Enter a grade: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') // err а не error! if err != nil { log.Fatal(err) } Если вы все же назовете свою переменную error, возможно, ваш код будет работать. По крайней мере до того момента, как вы забудете, что имя типа ошибки было замещено, попробуете воспользоваться типом и получите вместо него переменную. Лучше не рисковать; используйте имя err для своих переменных со значениями ошибок. |
28. Преобразование строк в числа
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
Условные команды также могут быть использованы для проверки введенного значения. Давайте воспользуемся командой if/else для определения того, прошел ли пользователь экзамен или нет. Если введенный процент правильных ответов равен 60 и выше, переменной status присваивается строка "passing". В противном случае переменной будет присвоена строка "failing". Пример кода: package main import "fmt" func main() { fmt.Print("Enter a grade:") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } if input >= 60 { status := "passing" } else { status := "failing" } } !!! Код не заработает в текущем варианте этой программы выдается ошибка компиляции. Дело в том, что ввод с клавиатуры читается как строка. Go может сравнивать числа только с другими числами, сравнить число со строкой не удастся. А прямого преобразования типа из строки в число не существует: float64("2.6") - Ошибка cannot convert "2.6" (type string) to type float64 Мы должны решить две задачи: 1. В конце строки input находится символ новой строки, появившийся в результате нажатия клавиши Enter в процессе ввода. Его необходимо удалить. 2. Остальные символы строки необходимо преобразовать в число с плавающей точкой. Удалить символ новой строки из конца входного текста несложно. Пакет strings содержит функцию TrimSpace, которая удаляет все символы-пропуски (символы новой строки, табуляции и обычные пробелы) в начале и в конце строки. s := "\t formerly surrounded by space \n" fmt.Println(strings.TrimSpace(s)) Таким образом, чтобы убрать символ новой строки из входной строки, следует передать ее TrimSpace, а затем снова присвоить возвращенное значение переменной input. input = strings.TrimSpace(input) После этого в строке input должно остаться только число, введенное пользователем. Мы воспользуемся функцией ParseFloat из пакета strconv, чтобы преобразовать его в значение float64. В аргументах передается преобразуемая строка | grade, err := strconv.ParseFloat(input, 64) | | | | Возможная ошибка Количество битов точности для результата Возвращаемые значения - flat64 Функции ParseFloat передается строка, которую необходимо преобразовать в число, а также количество битов точности для результата. Поскольку строка преобразуется в значение float64, мы передаем число 64. (Кроме float64, в Go также поддерживается менее точный тип float32, однако им лучше не пользоваться, если только у вас нет на это веских причин.) Функция ParseFloat преобразует число в строку и возвращает его в форме float64. Как и ReadString, она также имеет второе возвращаемое значение — значение ошибки. Оно должно быть равно nil, если только в ходе преобразования строки не возникли какие-то проблемы. (Например, переданная строка не может быть преобразована в число. Да и какой может быть числовой эквивалент у строки "hello"...) Оффтоп: Все эти «биты точности» сейчас не так уж важны. По сути это просто размер компьютерной памяти, используемой для хранения числа с плавающей точкой. Если вы уверены в том, что вам нужно число float64, всегда передавайте 64 во втором аргументе ParseFloat, и все будет хорошо. package main import ( "bufio" "log" "os" "strconv" "strings" "fmt" ) func main() { fmt.Print("Enter a grage: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } input = strings.TrimSpace(input) grade, err := strconv.ParseFloat(input, 64) if err != nil { log.Fatal(err) } if grade >= 60 { status := "passing" } else { status := "failing" } fmt.Println("A grade of", grade, "is", status) } Сначала нужные пакеты включаются в директиву import. Мы добавляем код удаления символа новой строки из входного текста, после чего передаем ввод функции ParseFloat и сохраняем полученное значение float64 в новой переменной grade. Как и в случае с ReadString, мы проверяем, возвращает ли ParseFloat значение ошибки. В этом случае программа сообщает об ошибке и аварийно завершается. Наконец, мы обновляем условную команду, чтобы она проверяла число в grade, а не строку в input. На этом все ошибки, происходящие от сравнения строки с числом, должны быть исправлены. При запуске обновленной программы мы уже не получаем сообщение о несовпадении типов string и int. Но тут есть и еще ошибки :) Код Go делится на блоки (сегменты). Блоки обычно заключаются в фигурные скобки ({}) и могут существовать как на уровне файлов с исходным кодом, так и на уровне пакетов. Блоки могут вкладываться друг в друга. Тела функций и условных команд тоже являются блоками. Понимание этого — ключ к решению нашей проблемы с переменной status |
29. Блоки и область видимости переменной
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Каждая объявленная переменная обладает областью видимости: частью кода, в которой она «видна». К объявленной переменной можно обращаться в любой точке ее области видимости, однако при попытке обратиться к ней за пределами этой области видимости вы получите сообщение об ошибке. Область видимости переменной состоит из блока, в котором она была объявлена, и всех блоков, вложенных в этот блок. Пример кода: package main import "fmt" var packageVar = "package" func main() { var functionVar = "function" if true { var conditionalVar = "conditional" fmt.Println(packageVar) //все еще в области видимости fmt.Println(functionVar) //все еще в области видимости fmt.Println(conditionalVar) //все еще в области видимости _ область видимости conditionalVar } //Область видимости conditionalVar fmt.Println(packageVar) //Все еще в области видимости fmt.Println(functionVar) //Все еще в области видимости fmt.Println(conditionalVar) //не определенно не в области видимости }//Область видимости functionVar) Ошибка: # command-line-arguments ./02_x123.go:17:14: undefined: conditionalVar Области видимости переменных в приведенном выше коде: - Областью видимости packageVar является весь пакет main. К packageVarможно обращаться в любой точке любой функции, определенной в пакете. - Областью видимости functionVar является вся функция, в которой объявлена переменная, включая блок if, вложенный в эту функцию. - Область видимости conditionalVar ограничивается блоком if. При попытке обратиться к conditionalVar после закрывающей фигурной скобки } блока if вы получите сообщение об ошибке, в котором говорится, что переменная conditionalVar не определена! |
30. Блоки и области видимости переменной:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
Пример кода: package main import ( "bufio" "log" "os" "strconv" "strings" "fmt" ) func main() { fmt.Print("Enter a grage: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } input = strings.TrimSpace(input) grade, err := strconv.ParseFloat(input, 64) if err != nil { log.Fatal(err) } if grade >= 60 { status := "passing" } else { status := "failing" } fmt.Println("A grade of", grade, "is", status) } Ошибка: undefined: status Рассмотрим проблему: func main() { // Начало блока фунции // Omitting code up here... if grade >= 60 { status := "passing" //Блок if } else { status := "failing" // Блок else } fmt.Println("A grade of", grade, "is", status) // В этой области видимости переменная "status" НЕ ОПРЕДЕЛЕНА! //Конец блока функции Проблема решается перемещением объявления переменной status из блоков условных команд в блок функции. После этого переменная status будет находиться в области видимости как во вложенных условных блоках, так и в конце блока функции. Решение: Пример кода: package main import ( "bufio" "log" "os" "strconv" "strings" "fmt" ) func main() { //Функция «main» вызывается при запуске программы. fmt.Print("Enter a grage: ") //Запрашиваем у пользователя значение. reader := bufio.NewReader(os.Stdin) //Создаем bufio.Reader для чтения ввода с клавиатуры. input, err := reader.ReadString('\n') //Читает данные, вводимые пользователем до нажатия клавиши Enter // Если произошла ошибка, вывести сообщение и прервать работу программы. if err != nil { log.Fatal(err) } input = strings.TrimSpace(input) //Удалить символ новой строки из введенных данных. grade, err := strconv.ParseFloat(input, 64) //Преобразовать введенную строку в значение float64 (число). //Если произошла ошибка, вывести сообщение и прервать выполнение программы. if err != nil { log.Fatal(err) } var status string //Переменная «status» объявляется здесь, чтобы она находилась в области видимости в границах функции. //Если значение grade равно 60 и более, переменной status присваивается строка «passing». В противном случае переменной присваивается строка «failing». if grade >= 60 { status = "passing" // так как перемену мы задали выше тут мы уже используем присвоение } else { status = "failing" // так как перемену мы задали выше тут мы уже используем присвоение } fmt.Println("A grade of", grade, "is", status) } | | | и результат сдачи экзамена Выводим введенное значение... Вы можете запускать программу сколько угодно раз. Введите значение меньше 60, и получите сообщение о том, что экзамен не сдан. Введите значение больше 60, и программа сообщит, что экзамен сдан успешно. Кажется, все работает! |
31. Только одна переменная в коротком объявлении должна быть новой?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
Пример: input, err := reader.ReadString('\n') grade, err := strconv.ParseFloat(input, 64) В этом коде есть странность. До этого говорили, что переменную нельзя объявить дважды. Тем не менее переменная err встречается в двух разных коротких объявлениях! Действительно, если дважды объявить одно имя переменной в одной области видимости, компилятор выдаст сообщение об ошибке: a := 1 a := 2 Ошибка компиляции: # command-line-arguments ./05_x1.go:7:11: no new variables on left side of := Но если хотя бы одно имя переменной в коротком объявлении является новым, такая запись допустима. Новые имена переменных интерпретируются как объявление, а существующие — как присваивание. У этого специального подхода есть причина: многие функции Go возвращают несколько значений. Было бы неприятно объявлять отдельно все переменные только потому, что вы захотели повторно использовать одну из них. Вместо этого Go позволяет использовать короткие объявления переменных, даже если для одной из переменных в действительности выполняется присваивание Пример: Вариант с отдельными объявлениями всех переменных работает, но, к счастью, поступать так не обязательно... var a, b float64 var err error a, err = strconv.ParseFloat("1.23", 64) b, err = strconv.ParseFloat("4.56", 64) Можно просто воспользоваться синтаксисом короткого объявления переменных a, err := strconv.ParseFloat("1.23", 64) - Объявляем «a» и «err». b, err := strconv.ParseFloat("4.56", 64) - Объявляем «b» и присваиваем «err». fmt.Println(a, b, err) |
32. Генерация случайного числа / пакет («math/rand»)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
Пакет math/rand содержит функцию Intn, которая сгенерирует случайное число за нас, поэтому в программу следует импортировать math/rand. После этого можно будет вызвать функцию rand.Intn для генерирования случайного числа Пример кода: package main import ( "fmt" "math/rand" ) func main() { target := rand.Intn(100) + 1 fmt.Println(target) } Передайте число функции rand.Intn, и функция вернет случайное число в диапазоне от 0 до переданного числа. Другими словами, если передать аргумент 100, будет получено случайное число в диапазоне 0–99. Так как нам требуется число в диапазоне 1–100, остается прибавить 1 к полученному случайному значению. Результат сохраняется в переменной target. Пока мы ограничимся простым выводом переменной target. Попытавшись запустить программу в таком виде, вы получите случайное число. Однако вы будете получать одно и то же случайное число раз за разом! Чтобы получать разные случайные числа, необходимо передать значение функции rand.Seed. Тем самым вы «инициализируйте» генератор случайных чисел, то есть предоставляете значение, которое будет использоваться для генерирования других случайных чисел. Но если передавать одно и то же значение инициализации, то и случайные значения будут теми же и мы снова вернемся к тому, с чего начинали. Ранее было показано, что функция time.Now выдает значение Time, представляющее текущую дату и время. Его можно использовать для того, чтобы получать разное значение инициализации при каждом запуске программы. Пример кода: package main import ( "fmt" "math/rand" "time" ) func main() { seconds := time.Now().Unix() rand.Seed(seconds) target := rand.Intn(100) + 1 fmt.Println(target) } Функция rand.Seed ожидает получить целое число, поэтому передать ей значение Time напрямую не удастся. Вместо этого для Time следует вызвать метод Unix, который преобразует его в целое число. (А конкретно значение будет преобразовано в формат времени Unix — целое количество секунд, прошедших с 1 января 1970 года. Впрочем, запоминать это не нужно.) Это число передается rand.Seed. |
33. Путь импортирования пакета
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
Пакет math/rand содержит функцию Intn, которая сгенерирует случайное число за нас, поэтому в программу следует импортировать math/rand. После этого можно будет вызвать функцию rand.Intn для генерирования случайного числа Упоминая math/rand, мы имеем в виду путь импортирования пакета, а не его имя. Путь импортирования — всего лишь уникальная строка, которая идентифицирует пакет и используется в директиве import. После того как пакет будет импортирован, к нему можно обращаться по имени пакета. Для всех пакетов, которые использовались до сих пор, путь импортирования совпадал с именем пакета. Несколько примеров: Путь импортирования Имя пакета "fmt" fmt "log" log "strings" strings Однако путь импортирования и имя пакета могут различаться. Многие пакеты Go классифицируются по категориям — например, «сжатие» или «комплексные вычисления». По этой причине они часто группируются по префиксам пути импортирования (например, "archive/" или "math/"). (Их можно рассматривать как аналоги путей каталогов на жестком диске.) Несколько примеров: Путь импортирования Имя пакета "archive" archive "archive/tar" tar "archive/zip" zip "math" math "math/cmplx" cmplx "math/rand" rand Язык Go не требует, чтобы имя пакета было как-то связано с путем импортирования. Но по соглашению последний (или единственный) сегмент пути импортирования также используется в качестве имени пакета. Таким образом, для пути импортирования "archive" именем пакета также будет archive, а для пути импортирования "archive/zip" будет использоваться имя пакета zip. Именно по этой причине в директиве import используется путь "math/rand", а в функции main имя пакета — rand. |
34. Циклы / Цикл for
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
Пример кода: package main import "fmt" func main() { for x := 4; x <= 6; x++ { fmt.Println("x is now", x) } } Разберем: for x := 4; x <= 6; x++ { fmt.Println("x is now", x) } for - ключевое слово x:=4 - команда инициализации x < 6 - Условное выражение x++ - Операция приращения { - начало блока цикла }конец блока цикла fmt.Println("x is now", x) - тело блока цикла Циклы всегда начинаются с ключевого слова for. В одной из стандартных разновидностей циклов за for следуют три сегмента кода, которые управляют циклом: - Команда инициализации, обычно используемая для инициализации переменной. - Условное выражение, которое определяет, когда следует прервать выполнение цикла. - Оператор приращения, который выполняется после каждой итерации цикла. Команда инициализации часто используется для инициализации переменной; условное выражение обеспечивает выполнение цикла до того, как переменная достигнет определенного значения, и оператор приращения обновляет значение этой переменной. Например, в приведенном фрагменте переменная t инициализируется значением 3, условие обеспечивает выполнение цикла, пока t > 0, а оператор приращения уменьшает t на 1 при каждом выполнении цикла. В конечном итоге t уменьшается до 0 и цикл завершается. package main import "fmt" func main() { for t := 3; t > 0; t-- { fmt.Println(t) } fmt.Println("Blastoff!") } Операторы ++ и -- часто встречаются в командах приращения циклов. ++ увеличивает значение переменной на 1, а -- уменьшает его на 1. Примеры кода: x := 0 x++ fmt.Println(x) x++ fmt.Println(x) x-- fmt.Println(x) for x := 1; x <= 3; x++ { fmt.Println(x) } for x := 3; x >= 1; x-- { fmt.Println(x) } В языке Go также поддерживаются операторы присваивания += и -=. Они получают значение в переменной, добавляют или вычитают другое значение, а затем присваивают результат той же переменной. Примеры кода: x := 0 x += 2 fmt.Println(x) x += 5 fmt.Println(x) x -= 3 fmt.Println(x) Операторы += и -= также могут использоваться в циклах для изменения переменной на величину, отличную от 1. for x := 1; x <= 5; x += 2 { fmt.Println(x) } for x := 15; x >= 5; x -= 5 { fmt.Println(x) } Когда цикл завершается, выполнение программы продолжится с команды, следующей за блоком цикла. При этом цикл продолжает выполняться, пока условное выражение остается истинным. Этот факт может иметь нежелательные последствия; ниже приведены примеры циклов, которые выполняются бесконечно или не выполняются ни одного раза: бесконечный цикл: for x := 1; true; x++ { fmt.Println(x) } Цикл не выполнится никогда: for x := 1; false; x++ { fmt.Println(x) } Будьте осторожны! Цикл может выполняться бесконечно. В этом случае ваша программа никогда не остановится сама. Если это случится, в активном окне терминала нажмите клавишу Control одновременно с клавишей C, чтобы прервать выполнение программы. Операторы инициализации и приращения необязательны. При желании операторы инициализации и приращения в заголовке цикла for можно опустить, оставив только условное выражение (хотя вы должны проследить за тем, чтобы условие в какой-то момент становилось ложным, иначе в программе возникнет бесконечный цикл). x := 1 for x <= 3 { fmt.Println(x) x++ } x := 3 for x >= 1 { fmt.Println(x) x-- } |
35. Циклы и области видимости
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
Как и в случае с условными командами, область видимости любых переменных, объявленных в блоке цикла, ограничивается этим блоком (хотя команда инициализации, условное выражение и оператор приращения также могут считаться частью этой области видимости). Пример: for x := 1; x <= 3; x++ { y := x + 1 fmt.Println(y) //Остается в области видимости... } fmt.Println(y) //Ошибка: вне области видимости! (undefined: y) Ошибка: # command-line-arguments ./13_loop.go:11:14: undefined: y Пример: for x := 1; x <= 3; x++ { fmt.Println(x) //Остается в области видимости... } fmt.Println(x) //Ошибка: вне области видимости! (undefined: x) Ошибка: # command-line-arguments ./15_loop.go:9:14: undefined: x Как и в случае с условными командами, любые переменные, объявленные до начала цикла, находятся в области видимости в заголовке и блоке цикла и остаются в области видимости после выхода из цикла. var x int //Объявляется за пределами цикла for x = 1; x <= 3; x++ { //Объявлять x здесь не нужно, просто присвойте значение fmt.Println(x) //Остается в области видимости. } fmt.Println(x) //Остается в области видимости. |
36. Сломай и изучи!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
Пример кода: package main import "fmt" func main() { for x := 1; x <= 3; x++ { fmt.Println(x) } } 0. Добавить круглые скобки после ключевого слова for for (x := 1; x <= 3; x++) Ошибка: # command-line-arguments ./17_dz.go:7:9: syntax error: unexpected :=, expecting ) Что: Другие языки требуют, чтобы управляющая часть цикла for заключалась в круглые скобки. Однако язык Go не только этого не требует, но и запрещает. 1. Удалить : из команды инициализации x = 1 Ошибка: # command-line-arguments ./17_dz.go:9:6: undefined: x ./17_dz.go:10:15: undefined: x Что: Если только вы не присваиваете значение переменной, уже объявленной во внешней области видимости (а это бывает довольно редко), команда инициализации должна быть объявлением, а не присваиванием. 2. Удалить = из условного выражения x < 3 Выражение x<3 становится ложным , когда значение x становится равным 3 (тогда как выражение x<=3 все еще остается истинным). Таким образом, цикл будет вести отсчет только до 2. 3. Использовать противоположный оператор сравнения в условном выражении x >= 3 Так как условие будет ложным уже в самом начале цикла (x инициализируется 1, что меньше 3), цикл не будет выполнен ни одного раза 4. Заменить оператор приращения x++ на x-- Переменная x начинает отсчет с 1 (1, 0, -1, -2 и т. д.). Так как она никогда не станет больше 3, цикл будет выполняться бесконечно 5. Переместить команду fmt.Println(x) за пределы блока цикла Переменные, объявленные в команде инициализации или в блоке цикла, остаются в области видимости только в пределах блока цикла # command-line-arguments ./17_dz.go:15:15: undefined: x |
37. Пропуск частей цикла командами continue и break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
В Go предусмотрены два ключевых слова для управления циклом. Первое — continue — осуществляет немедленный переход к следующей итерации цикла; при этом дальнейший код текущей итерации в блоке цикла пропускается. for x := 1; x <= 3; x++ { fmt.Println("before continue") continue fmt.Println("after continue") } В приведенном выше примере строка "after continue" никогда не выводится, потому что ключевое слово continue всегда выполняет переход к началу цикла — до того, как отработает второй вызов Println. Второе ключевое слово break приводит к немедленному выходу из цикла. Дальнейший код в блоке цикла не отрабатывается, другие итерации цикла не выполняются. Управление передается первой команде, следующей за циклом. for x := 1; x <= 3; x++ { fmt.Println("before break") break fmt.Println("after break") } fmt.Println("after loop") Здесь при первой итерации цикла выводится сообщение "before break", после чего команда break немедленно прерывает цикл; сообщение "after break" не выводится, и цикл не выполняется повторно (хотя без break он бы выполнился еще два раза). Управление передается команде, следующей за циклом. |
38. Комментарии (guess)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
//guess - игра, в которой игрок должен отгадать случайное число. package main import ( "bufio" // пакет отвечает за input keyboard "fmt" // вывод в консоль "log" // работает с ошибками err "math/rand" // отвечает за генерацию чисел "os" // использование возможностей ОС "strconv" // работа со строками, преобразование "strings" // работа со строками "time" // работает со временем, используется ) func main() { seconds := time.Now().Unix() //Получает текущую дату и время в виде целого числа. rand.Seed(seconds) // Инициализируем генератор случайных чисел. target := rand.Intn(100) + 1 // Генерируем целое число 1 от 100 fmt.Println("I've chosen a random number between 1 and 100.") fmt.Println("Can you guess it?") reader := bufio.NewReader(os.Stdin) // Создаем bufio.Reader для чтения ввода с клавиатуры. success := false // Настроить программу, чтобы по умолчанию выводилось сообщение о проигрыше. for guesses := 0; guesses < 10; guesses++ { fmt.Println("You have", 10-guesses, "guesses left.") fmt.Print( |