0. Создаем директорию для проекта mkdir ИМЯ_ПРОЕКТА cd ИМЯ_ПРОЕКТА touch main.go 1. Первичная иницилизация go mod init ИМЯ_ПРОЕКТА 2. Докача модулей из интернета go get путь_до_модуля go get путь_до_модуля go get путь_до_модуля go get путь_до_модуля Сборка: go build -o ИМЯ_ПРОЕКТА_ЖЕЛАЕМОЕ_ИМЯ_БИНАРНИКА_Х86 GOOS=linux GOARCH=arm64 go build -o ИМЯ_ПРОЕКТА_АРМ64 P.S. ИМЯ_ПРОЕКТА/ │── go.mod # Файл с информацией о модулях │── go.sum # Контрольные суммы зависимостей │── main.go # Исходный код └── ИМЯ_ПРОЕКТА # Скомпилированный бинарник (после go build)
Рубрика: go
Сегменты
Сегменты — разновидность коллекций, которые могут расширяться для хранения дополнительных элементов; а это как раз то, что нужно! Оказывается, в Go существует структура данных, в которую можно добавлять новые значения, — она называется сегментом. Как и массив, сегмент состоит из нескольких элементов, относящихся к одному типу. В отличие от массивов, существуют функции, позволяющие добавлять новые элементы в конец сегмента. Отличие сегмента от массива: Фактически это уже знакомый синтаксис объявления массива, только без указания размера. var myArray [5]int // Массив — обратите внимание на размер. var mySlice []int // Сегмент — размер не задан В отличие от переменных для массивов, объявление переменной для сегмента не приводит к автоматическому созданию сегмента. Для этого следует вызвать встроенную функцию make. Функции передается тип создаваемого сегмента (он должен соответствовать типу переменной, которой вы собираетесь присвоить сегмент) и длина сегмента при создании. Пример: var notes []string notes = make([]string, 7) notes[0] = "do" notes[1] = "re" notes[2] = "mi" fmt.Println(notes[2]) fmt.Println(notes[0]) Пример: pr := make([]int, 5) pr[0] = 99 pr[4] = 200 fmt.Println("pr[4] - pr[0] = ", pr[4]-pr[0]) Встроенная функция len для сегментов работает так же, как и для массивов. Передайте len сегмент, и функция вернет его длину в виде целого числа. Пример: notes := make([]string, 7) primers := make([]int, 5) fmt.Println(len(notes)) fmt.Println(len(primers)) Циклы "for" и "for...range" работают с сегментами точно так же, как и с массивами: Пример: letters := []string{"a", "b", "c"} for i := 0; i < len(letters); i++ { fmt.Println(letters[i]) } fmt.Println(" ") for _, letter := range letters { fmt.Println(letter) }
Литералы сегментов
Как и с массивами, если вы заранее знаете, какими значениями должен быть заполнен сегмент в исходном состоянии, то можете инициализировать сегмент этими значениями при помощи литерала сегмента. Литерал сегмента очень похож на литерал массива, но если литерал массива содержит длину массива в квадратных скобках, у литерала сегмента квадратные скобки пусты. За пустыми скобками следует тип элементов, которые будут храниться в сегменте, и список исходных значений всех элементов, заключенный в фигурные скобки. Вызывать функцию make необязательно, при использовании литерала сегмента ваш код создаст сегмент и заполнит его. Сегмент: []int{9, 18, 27} | | | | | Список значений, разделенных запятыми. | Тип элементов в сегменте Пустая пара квадратных скобок Пример: notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //Значения присваиваются с помощью литерала сегмента. fmt.Println(notes[3], notes[6], notes[0]) primes := []int{ // Многострочный литерал сегмента. 2, 3, 5, } fmt.Println(primes[0], primes[1], primes[2]) Погодите! Похоже, что сегменты могут делать все, что делают массивы, и в них можно добавлять элементы. Тогда почему бы не ограничиться сегментами и не забыть про эту ерунду с массивами? !!! Потому что сегменты построены на основе массивов. !!! И вы не сможете понять, как работают сегменты, не понимая массивы. Каждый массив существует на основе базового массива. Данные сегмента на самом деле хранятся в базовом массиве, а сегмент всего лишь предоставляет «окно» для работы с некоторыми (или всеми) элементами массива. Когда вы используете функцию make или литерал сегмента для создания сегмента, базовый массив при этом создается автоматически (и вы не можете обратиться к нему иначе как через сегмент). Но вы также можете создать массив самостоятельно, а затем создать сегмент на основе этого массива при помощи оператора сегмента. Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} slice1 := underlyingArray[0:3] fmt.Println(slice1) Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} i, j := 1, 4 slice2 := underlyingArray[i:j] fmt.Println(slice2) У оператора сегмента предусмотрены значения по умолчанию как для начального, так и для конечного индексов. Если начальный индекс не указан, будет использовано значение 0 позиции. Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} slice4 := underlyingArray[:3] fmt.Println(slice4) А если не указан конечный индекс, то в сегмент включаются элементы от начального индекса и до конца базового завершается. Пример: underlyingArray := [5]string{"a", "b", "c", "d", "e"} slice5 := underlyingArray[1:] fmt.Println(slice5)
Базовые массивы
Как упоминалось ранее, сам сегмент не содержит данных, это всего лишь «окно» для просмотра элементов базового массива. Сегмент можно представить себе как микроскоп, направленный на определенную часть предметного стекла (базовый массив). Когда вы берете сегмент базового массива, то «видите» только ту часть элементов массива, которая видна через этот сегмент. Несколько сегментов могут существовать на основе одного базового массива. В этом случае каждый сегмент становится «окном» для отдельного подмножества элементов массива. Сегменты даже могут перекрываться! Присваивание нового значения элементу сегмента приводит к изменению соответствующего элемента в базовом массиве. Если на один и тот же базовый массив указывают несколько сегментов, то и изменения элементов массива будут видны во всех сегментах. Из-за этих потенциальных проблем обычно рекомендуется создавать сегменты с использованием make или литерала сегмента (вместо того, чтобы создать массив и применять к нему оператор сегмента). С make и литералами сегментов вам никогда не приходится иметь дела с базовым массивом.
Расширение сегментов функцией «append»
В Go существует встроенная функция append, которая получает сегмент и одно или несколько значений, которые присоединяются в конец сегмента. Функция возвращает но вый, расширенный сегмент со всеми элементами исходного сегмента и новыми элементами, добавленными в его конец. Пример: slice := []string{"a", "b"} fmt.Println(slice, len(slice)) slice = append(slice, "c") fmt.Println(slice, len(slice)) slice = append(slice, "d", "e") fmt.Println(slice, len(slice)) Вам не нужно следить за тем, по какому индексу присваиваются новые значения, или за чем-нибудь еще! Просто вызовите функцию append и передайте ей сегмент со значениями, которые добавляются в конец сегмента, и вы получите новый расширенный сегмент. Да, так просто! Обратите внимание: возвращаемое значение append во всех случаях присваивается той же переменной сегмента, которая передается append. Таким образом предотвращается возможность непоследовательного поведения сегментов, возвращаемых append. Базовый массив сегмента не может увеличиваться в размерах. Если в массиве не остается места для добавления элементов, все элементы копируются в новый, больший массив, а сегмент обновляется, чтобы он базировался на новом массиве. Но поскольку все это происходит где-то за кулисами внутри функции append, невозможно простым способом определить, имеет ли возвращенный сегмент тот же базовый массив, как и переданный сегмент, или другой. Если в программе будут оставаться оба сегмента, это может привести к непредсказуемому поведению. Пример: s1 := []string{"s1", "s1"} s2 := append(s1, "s2", "s2") s3 := append(s2, "s3", "s3") s4 := append(s3, "s4", "s4") fmt.Println(s1, s2, s3, s4) s4[0] = "XX" fmt.Println(s1, s2, s3, s4) По этой причине при вызове append возвращаемое значение обычно присваивается той же переменной сегмента, которая была передана append. Если в программе хранится только один сегмент, то вам не придется беспокоиться о том, используют ли два сегмента один базовый массив! Пример: s1 := []string{"s1", "s1"} s1 = append(s1, "s2", "s2") s1 = append(s1, "s3", "s3") s1 = append(s1, "s4", "s4") fmt.Println(s1)
Сегменты и нулевые значения
Как и в случае с массивами, при обращении к элементу сегмента, которому не было присвоено значение, вы получите нулевое значение для этого типа. Пример: floatSlice := make([]float64, 10) boolSlice := make([]bool, 10) fmt.Println(floatSlice[9], boolSlice[5]) Пример: var intSlice []int var stringSlice []string fmt.Printf("intSlice: %#v, stringSlice: %#vn", intSlice, stringSlice)
016 / go работа с файлами
Чтение текстового файла
Ранее мы использовали пакеты os и bufio стандартной библиотеки для чтения данных по строкам с клавиатуры. Те же пакеты могут использоваться и для построчного чтения данных из текстовых файлов. В своем любимом текстовом редакторе создайте новый файл с именем data.txt. Запишите в файле три наших значения с плавающей точкой, по одному числу в строке. cat > data.txt << "EOF" 71.8 56.2 89.5 EOF Пример программы: package main import ( "bufio" "fmt" "log" "os" ) func main() { file, err := os.Open("data.txt") //Файл данных открыва if err != nil { log.Fatal(err) } scanner := bufio.NewScanner(file) //Цикл выполняется до того, как будет достигнут конец файла, а scanner.Scan вернет false for scanner.Scan() { //Читает строку из файла fmt.Println(scanner.Text()) //Выводит строку } // Если при закрытии файла произошла ошибка то сообщить о ней и завершить работу err = file.Close() //Закрывает файл для освобождения ресурсов. if err != nil { log.Fatal(err) } if scanner.Err() != nil { log.Fatal(scanner.Err()) } } Наша тестовая программа readfile.go успешно читает данные из файла data.txt и выводит их. А теперь разберемся, как же она работает. Сначала строка с именем открываемого файла передается функции os.Open. Эта функция возвращает два значения: указатель os.File, представляющий открытый файл, и значение ошибки. Как и в случае с другими функциями, если значение ошибки равно nil, это означает, что файл был открыт успешно, но любое другое значение указывает на то, что произошла ошибка (например, если файл отсутствует или не читается). В таком случае программа выводит сообщение об ошибке и завершается.
Чтение текстового файла в массив
Пример кода: // Пакет datafile предназначен для чтения данных из файлов. //package datafile package main import ( "fmt" "bufio" "os" "strconv" ) // GetFloats читает значение float64 из каждой строки файла. ошибку. func GetFloats(fileName string) ([3]float64, error) { var numbers [3]float64 // Объявление возвращаемого массива. file, err := os.Open(fileName) //Открывает файл с переданным именем. if err != nil { return numbers, err } i := 0 //Переменная для хранения индекса, по которому должно выполняться присваивание scanner := bufio.NewScanner(file) for scanner.Scan() { numbers[i], err = strconv.ParseFloat(scanner.Text(), 64) if err != nil { return numbers, err } i++ //Переход к следующему индексу массива. } err = file.Close() if err != nil { return numbers, err } if scanner.Err() != nil { return numbers, scanner.Err() } return numbers, nil //Если выполнение дошло до этой точки, значит, ошибок не было, поэтому программа возвращает массив чисел и значение ошибки «nil ». } func main() { fileName := "data.txt" fmt.Println("Hello readfile") fmt.Println(GetFloats(fileName)) }
Чтение файла и выполнение программы average. Еще один пример:
//average вычисляет среднее значение package main import ( "bufio" "fmt" "os" "strconv" "log" ) // GetFloats читает значение float64 из каждой строки файла. ошибку. func GetFloats(fileName string) ([3]float64, error) { var numbers [3]float64 // Объявление возвращаемого массива. file, err := os.Open(fileName) //Открывает файл с переданным именем. if err != nil { return numbers, err } i := 0 //Переменная для хранения индекса, по которому должно выполняться присваивание scanner := bufio.NewScanner(file) for scanner.Scan() { numbers[i], err = strconv.ParseFloat(scanner.Text(), 64) if err != nil { return numbers, err } i++ //Переход к следующему индексу массива. } err = file.Close() if err != nil { return numbers, err } if scanner.Err() != nil { return numbers, scanner.Err() } return numbers, nil //Если выполнение дошло до этой точки, значит, ошибок не было, поэтому программа возвращает массив чисел и значение ошибки «nil». } func main() { filename := "data.txt" numbers, err := GetFloats(filename) if err != nil { log.Fatal(err) } var sum float64 = 0 for _, number := range numbers { sum += number } sampleCount := float64(len(numbers)) fmt.Printf("Average: %0.2fn", sum/sampleCount) } Наша программа может обрабатывать только три значения! cat > data1.txt << "EOF" 71.8 56.2 89.5 99.3 EOF Пример ошибки: panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.GetFloats(0xd5676, 0x9, 0x0, 0x0, 0x0, 0x4d458, 0x162060) /home/pi/githabmegafolder/c-test/02_lesson_golang/25_file/02_averange.go:25 +0x2e4 main.main() /home/pi/githabmegafolder/c-test/02_lesson_golang/25_file/02_averange.go:46 +0x34 exit status 2
014 / go doc
Введите команду "go doc", чтобы вывести документацию по любому пакету или функции. Чтобы вывести документацию по пакету, передайте его путь импорта команде "go doc". Например, информация о пакете "strconv" выводится командой "go doc strconv". go doc -h go doc -u http go doc strconv go doc strconv ParseFloat go doc github.com/headfirstgo/keyboard Команда go doc старается получить полезную информацию на основании анализа кода. Имена пакетов и пути импорта включаются автоматически, как и имена функций, параметры и возвращаемые типы. И все же команда go doc не умеет творить чудеса. Если вы хотите, чтобы пользователи смогли узнать из документации о предназначении пакета или функции, придется добавить эту информацию самостоятельно. К счастью, это делается просто: нужно добавить в код документирующие комментарии. Обычные комментарии Go, размещенные непосредственно перед директивой package или объявлением функции, считаются документирующими комментариями и включаются в вывод go doc. При добавлении документирующих комментариев следует соблюдать ряд правил: - Комментарии должны состоять из полноценных предложений. - Комментарии для пакетов должны начинаться со слова "Package", за которым следует имя пакета: // Package mypackage enables widget management. - Комментарии для функций должны начинаться с имени функции, которую они описывают: // MyFunction converts widgets to gizmos. - В комментарии также можно включать примеры кода, которые должны снабжаться отступами. - Не включайте дополнительные символы для выразительности или форматирования (кроме отступов в примерах кода). Документирующие комментарии будут выводиться в виде простого текста и должны форматироваться соответствующим образом. go help go help gopath go help list
015 / go arrays / Массивы
Массивы
Многие программы работают со списками. Списки адресов. Списки телефонных номеров. Списки товаров. Массив представляет собой набор значений, относящихся к одному типу. Представьте себе таблетницу — вы можете класть и доставать таблетки в любое отделение, но при этом ее удобно переносить как единое целое. Значения, хранящиеся в массиве, называются элементами. Вы можете создать массив строк, массив логических значений или массив любого другого типа Go (даже массив массивов). Весь массив можно сохранить в одной переменной, а затем обратиться к любому нужному элементу. В массивах хранятся наборы значений. Массив содержит заранее заданное количество элементов, а его размер не может увеличиваться или уменьшаться. Чтобы объявить переменную для хранения массива, следует указать количество хранящихся в нем элементов в квадратных скобках ([]), а затем тип элементов в массиве. Чтобы присвоить значения элементам массива или прочитать их позднее, необходимо каким-то образом указать, какой элемент вам нужен. Элементы в массиве нумеруются, начиная с 0. Номер массива называется его индексом. Пример массив нот: var notes [7]string notes[0] = "do" notes[1] = "re" notes[2] = "mi" fmt.Println(notes[0]) fmt.Println(notes[1]) Массив целых чисел: var primes [5]int primes[0] = 2 primes[1] = 3 fmt.Println(primes[0]) Массив значений time.Time: var dates [3]time.Time dates[0] = time.Unix(1257894000, 0) dates[1] = time.Unix(1447920000, 0) dates[2] = time.Unix(1508632200, 0) fmt.Println(dates[1]) Нулевые значения в массивах Как и в случае с переменными, при создании массивов все содержащиеся в них значения инициализируются нулевым значением для типа, содержащегося в массиве. Так массив значений int по умолчанию заполняется нулями. С другой стороны, нулевым значением для строк является пустая строка, так что массив строковых значений по умолчанию заполняется пустыми строками. Нулевые значения позволяют безопасно выполнять операции с элементами массивов, даже если им не были присвоены значения. Например, в следующем массиве хранятся целочисленные счетчики. Любой элемент можно увеличить на 1 даже без предварительного присваивания значения, потому что мы знаем, что все значения счетчиков начинаются с 0. var counters [3]int counters[0]++ // Первый элемент увеличивается с 0 до 1. counters[0]++ // Первый элемент увеличивается с 1 до 2. counters[2]++ // Третий элемент увеличивается с 0 до 1. fmt.Println(counters[0], counters[1], counters[2]) Вывод: 2 0 1 При создании массива все содержащиеся в нем элементы инициализируются нулевым значением для типа, хранящегося в массиве.
Литералы массивов
Если вам заранее известны значения, которые должны храниться в массиве, вы можете инициализировать массив этими значениями в форме литерала массива. Литерал массива начинается как тип массива — с количества элементов в квадратных скобках, за которым следует тип элементов. Далее в фигурных скобках идет список исходных значений элементов массива. Значения элементов должны разделяться запятыми. [3]int{9, 18, 27} | | | | | Список значений, разделенных запятыми. | Тип элементов в массиве. | Количество элементов в массиве. var notes [7]string = [7]string{"do", "re", "mi", "fa", "so", "la", "ti"} fmt.Println(notes[3], notes[6], notes[0]) var primes [5]int = [5]int{2, 3, 5, 7, 11} fmt.Println(primes[0], primes[2], primes[4]) Литералы массивов также позволяют использовать короткие объявления переменных с помощью :=. Короткое объявление переменной. notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"} primes := [5]int{2, 3, 5, 7, 11} Литералы массивов могут распространяться на несколько строк, но перед каждым переносом строки в коде должна стоять запятая. Запятая даже должна стоять после последнего элемента в литерале массива, если за ним следует перенос строки. (На первый взгляд этот синтаксис выглядит неуклюже, но он упрощает последующее добавление новых элементов в коде.) text := [3]string{ // Все это один массив. "This is a series of long strings", "which would be awkward to place", "together on a single line", // Запятая в конце обязательна. } Когда вы занимаетесь отладкой кода, вам не нужно передавать элементы массивов Println и другим функциям пакета fmt один за одним. Просто передайте весь массив. Пакет fmt содержит логику форматирования и вывода массивов. (Пакет fmt также умеет работать с сегментами, картами и другими структурами данных, которые будут описаны позднее.) var notes [3]string = [3]string{"do", "re", "mi"} var primes [5]int = [5]int{2, 3, 5, 7, 11} fmt.Println(notes) fmt.Println(primes) Возможно, вы также помните глагол "%#v", используемый функциями Printf и Sprintf, — он форматирует значения так, как они отображаются в коде Go. При форматировании с "%#v" массивы отображаются в форме литералов массивов Go. fmt.Printf("%#vn", notes) fmt.Printf("%#vn", primes)
Обращение к элементам массива в цикле
Вы не обязаны явно записывать целочисленные индексы элементов массивов, к которым обращаетесь в своем коде. В качестве индекса также можно использовать значение целочисленной переменной. notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"} index := 1 fmt.Println(index, notes[index]) // Выводит элемент массива с индексом 1. index = 3 fmt.Println(index, notes[index]) // Выводит элемент массива с индексом 3. for i := 0; i <= 2; i++ { fmt.Println(i, notes[i]) } При обращении к элементам массивов через переменную необходимо действовать внимательно и следить за тем, какие значения индексов используются в программе. Как упоминалось ранее, массивы содержат конкретное число элементов. Попытка обратиться к индексу за пределами массива приводит к панике — ошибке, происходящей во время выполнения программы (а не на стадии компиляции).
Проверка длины массива функцией «len»
Написание циклов, которые ограничиваются только правильными индексами, сопряжено с определенным риском ошибок. К счастью, есть пара приемов, которые упрощают этот процесс. Во-первых, вы можете проверить фактическое количество элементов в массиве перед обращением к элементу. Для этого можно воспользоваться встроенной функцией len, которая возвращает длину массива (количество содержащихся в нем элементов). notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"} for i := 0; i < len(notes); i++ { fmt.Println(i, notes[i]) } Впрочем, и здесь существует некоторый риск ошибок. Хотя len(notes) возвращает наибольший индекс, к которому вы можете обращаться, равен 6 (потому что индексирование массивов начинается с 0, а не с 1). При попытке обратиться по индексу 7 возникнет ситуация паники.
Безопасный перебор массивов в цикле «for…range»
В другом, еще более безопасном способе обработки всех элементов массива используется специальный цикл for...range. В форме с range указывается переменная для хранения целочисленного индекса каждого элемента, другая переменная для хранения значения самого элемента и перебираемый массив. Цикл выполняется по одному разу для каждого элемента в массиве; индекс элемента присваивается первой переменной, а значение элемента — второй переменной. В блок цикла включается код для обработки этих значений. for index, value := range myArray { // Блок цикла. } Эта форма цикла for не содержит запутанных выражений инициализации, условия и завершения. А поскольку значение элемента автоматически присваивается переменной, риск обращения к недействительному индексу массива исключен. Форма цикла for с range читается безопаснее и проще, поэтому именно она чаще всего встречается при работе с массивами и другими коллекциями. notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"} for index, note := range notes { fmt.Println(index, note) } Цикл выполняется семь раз, по одному разу для каждого элемента в массиве notes. Для каждого элемента переменной index присваивается индекс элемента, а переменной note присваивается значение элемента. После этого мы выводим индекс и значение. Помните, как при вызове функции с несколькими возвращаемыми значениями мы хотели проигнорировать одно из них? Это значение присваивалось пустому идентификатору ( _ ), чтобы компилятор Go просто отбросил это значение без выдачи сообщения об ошибке... То же самое можно проделать со значениями из циклов "for...range". Если вам не нужен индекс каждого элемента массива, присвойте его пустому идентификатору: notes := [7]string{"do", "re", "mi", "fa", "so", "la", "ti"} for index, note := range notes { fmt.Println(index, note) } А если вам не нужна переменная для значения, замените ее пустым идентификатором
013 / go Указатели
Указатели
Оператор & (амперсанд) используется в Go для получения адреса переменной. Например, следующий код инициализирует переменную, сначала выводит ее значение, а затем адрес переменной... Пример кода: package main import "fmt" func main() { amount := 6 fmt.Println(amount) //Выводит значение переменной fmt.Println(&amount) //Выводит адрес переменной } вывод кода: 6 //Значение переменной. 0x400011c010 //Адрес переменной. Адрес можно получить для переменной любого типа. Обратите внимание, все переменные имеют разные адреса. Еще пример кода: package main import "fmt" func main() { var myInt int fmt.Println(&myInt) var myFloat float64 fmt.Println(&myFloat) var myBool bool fmt.Println(&myBool) } Вывод кода: 0x40000ba010 0x40000ba018 0x40000ba030 И что же собой представляют эти «адреса»? Чтобы найти конкретный дом в плотно застроенном городе, вам нужно знать его адрес... Память, выделяемая компьютером программе, так же переполнена, как и городские улицы. Она забита значениями переменных: логическими значениями, целыми числами, строками и т. д. Зная адрес переменной, вы сможете воспользоваться им для получения значения, хранящегося в переменной. Адрес | 0x1040a100 0x1040a108 0x1040a110 0x1040a118 0x1040a120 0x1040a128 true 6 ... ... ... 3.1415 | значение, хранящееся по этому адресу! !!! Значения, представляющие адреса переменных, называются указателями, потому что они указывают на область памяти, в которой хранится переменная.
Типы указателей
Тип указателя состоит из знака * и типа переменной, на которую ссылается указатель. Например, тип указателя на переменную int записывается в виде *int (читается «указатель на int»). С помощью функции reflect.TypeOf можно вывести типы указателей из приведенной ранее программы: package main import ( "fmt" "reflect" ) func main() { var myInt int fmt.Println(reflect.TypeOf(&myInt)) //Получает указатель на myInt и выводит тип указателя. var myFloat float64 fmt.Println(reflect.TypeOf(&myFloat)) //Получает указатель на myFloat и выводит тип указателя. var myBool bool fmt.Println(reflect.TypeOf(&myBool)) //Получает указатель на myBool и выводит тип указателя.* } В программе можно объявлять переменные, содержащие указатели. В таких переменных могут храниться только указатели на один конкретный тип переменной, так что переменная может содержать только указатели *int, только указатели *float64 и т. д. package main import "fmt" func main() { myInt := 4 //Значение myIntPointer := &myInt //Указатель fmt.Println(myIntPointer) //Выводится сам указатель. fmt.Println(*myIntPointer) //Выводится значение, на которое ссылается указатель. myFloat := 98.6 //Значение myFloatPointer := &myFloat //Указатель fmt.Println(myFloatPointer) //Выводит сам указатель fmt.Println(*myFloatPointer) //Выводится значение, на которое ссылается указатель. myBool := true //Значение myBoolPointer := &myBool ////Указатель fmt.Println(myBoolPointer) //Выводит сам указатель fmt.Println(*myBoolPointer) //Выводится значение, на которое ссылается указатель. Как и с другими типами, при немедленном присваивании исходного значения переменной-указателю можно воспользоваться коротким объявлением переменной. package main import "fmt" func main() { var myBool bool myBoolPointer := &myBool //Короткое объявление переменной-указателя. fmt.Println(myBoolPointer) }
Чтение или изменение значения по указателю
!!! Оператор * может использоваться для обновления значения по указателю. package main import "fmt" func main() { myInt := 4 //Значение fmt.Println(myInt) //Вывести значение myIntPointer := &myInt //Создаем указатель *myIntPointer = 8 //Новое значение присваивается переменной, на которую ссылается указатель (myInt). fmt.Println(*myIntPointer) //Выводится значение переменной, на которую ссылается указатель. fmt.Println(myInt) //Значение переменной выводится напрямую. } /* 4 Исходное значение myInt. 8 Результат обновления *myIntPointer. 8 Обновление значения myInt (то же, что *myIntPointer). */ В приведенном коде команда *myIntPointer = 8 обращается к переменной, на которую ссылается указатель myIntPointer (то есть переменной myInt) и присваивает ей новое значение. Таким образом, обновляется не только значение *myIntPointer, но и myInt
Использование указателей с функциями
Указатели также можно возвращать из функций; просто объявите возвращаемый тип функции как тип указателя. Пример кода: package main import "fmt" func createInt() *int { var myInt = 100 return &myInt } func createPointer() *float64 { var myFloat = 98.5 return &myFloat } func crS() *string { var xX = "hello all" return &xX } func main() { var myFloatPointer *float64 = createPointer() fmt.Println(*myFloatPointer) var x *int = createInt() fmt.Println(*x) var x1 *string = crS() fmt.Println(*x1) } Кстати говоря, в отличие от некоторых других языков, в Go можно вернуть указатель на переменную, локальную по отношению к функции. И хотя эта переменная уже не находится в области видимости, пока у вас есть указатель, Go предоставит вам доступ к значению этой переменной. package main import "fmt" func printPointer(myBoolPointer *bool) { fmt.Println(*myBoolPointer) } func main() { var myBool bool = true printPointer(&myBool) }
Пакет 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.
Выражение вычисляется, и если полученный результат равен 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 удалит все круглые скобки, добавленные вами, если только они не используются для определения порядка операций.
Циклы / Цикл for
Пример кода: 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-- }
Пропуск частей цикла командами continue и break
В 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 он бы выполнился еще два раза). Управление передается команде, следующей за циклом.
010 / go Комментарии
Самая распространенная форма комментариев обозначается двумя слешами (//). Все символы от // до конца строки рассматриваются как часть комментария. Комментарий // может занимать всю строку или следовать после кода. // Общее количество виджетов в системе. var TotalCount int // Должно быть целым числом. Более редкая форма комментариев занимает несколько строк. Блочные комментарии начинаются с /* и заканчиваются */, а весь текст между этими маркерами (включая символы новой строки) является частью комментария. /* Пакет widget включает все функции, используемые для работы с виджетами. */
009 / go strings / строки
Пакет 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() | | Значение. Имя метода.
В языке 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) }
Нулевые значения
Если переменная объявляется без присваивания значения, то она будет содержать нулевое значение для этого типа. Для числовых типов нулевое значение равно 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) }
Короткие объявления переменных
Вместо того чтобы явно объявлять тип переменной и позднее присваивать ей значение оператором =, вы совмещаете эти две операции с помощью синтаксиса :=. Обычное объявление переменной выглядит вот так: 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") } Явно объявлять тип переменной не обязательно: тип значения, присвоенного переменной, становится типом этой переменной. Поскольку короткие объявления переменных очень удобны и компактны, они используются чаще обычных объявлений. Впрочем, время от времени вам будут встречаться обе формы, поэтому важно знать их.
Правила выбора имен для переменных
В Go существует один простой набор правил, применяемых к именам переменных, функций и типов: !!! Эти правила должны обязательно выполняться на уровне языка 00. Имя должно начинаться с буквы и может содержать любое количество дополнительных букв и цифр. 01. Если имя переменной, функции или типа начинается с буквы верхнего регистра, оно считается экспортируемым и может использоваться в других пакетах, кроме текущего. (Именно поэтому буква P в fmt.Println имеет верхний регистр: это нужно для того, чтобы его можно было использовать в main или любом другом пакете.) Если имя переменной/функции/типа начинается с буквы нижнего регистра, оно считается не экспортируемым. Такие имена доступны только в текущем пакете. !!! Но сообщество Go также соблюдает ряд дополнительных соглашений: 02. Если имя состоит из нескольких слов, каждое слово после первого должно начинаться с буквы верхнего регистра, и они должны следовать друг за другом без разделения пробелами: topPrice, RetryConnection и т. д. (Первая буква имени имеет верхний регистр только в том случае, если оно должно экспортироваться из пакета.) Этот стиль записи часто называется верблюжьим регистром, потому что буквы верхнего регистра напоминают горбы у верблюда. 03. Если смысл имени очевиден по контексту, в сообществе Go принято сокращать его: использовать i вместо index, max вместо maximum и т. д. Примеры: Нормально: sheetLength TotalUnits i нарушают соглашения: sheetlength - Остальные слова должны начинаться с буквы верхнего регистра! Total_Units - Допустимо, но слова должны записываться подряд! index - Хорошо бы заменить сокращением! !!! Только переменные, функции и типы, имена которых начинаются с буквы верхнего регистра, считаются экспортируемыми, то есть доступными за пределами текущего пакета.
Преобразование переменных
Пример не рабочего кода: 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)
Числа
Числа тоже можно определять прямо в программном коде. Это еще проще, чем определять строковые литералы: просто введите нужное число. Как вы вскоре увидите, в языке Go целые числа и числа с плавающей точкой интерпретируются как разные типы. Помните, что целое число можно отличить от числа с плавающей точкой по разделителю дробной части — точке. Пример: 42 Целое число. int 3.1415 Число с плавающей точкой float Пример в коде: package main import "fmt" func main() { fmt.Println(0, 3.1415) }
Математические операции и сравнения
Основные математические операторы Go работают так же, как и в большинстве других языков. оператор + выполняет сложение оператор - выполняет вычитание оператор * — умножение оператор / — деление Пример кода: package main import "fmt" func main() { fmt.Println("t1 + 2 =", 1+2, "nt5.4 - 2.2 =", 5.4-2.2, "nt3 * 4 =", 3*4, "nt7.5 / 5 =", 7.5/5) } При помощи операторов < и > можно сравнить два значения и проверить, какое из них больше другого. оператор == (два знака равенства) проверяет, что два значения равны оператор != проверяет, что два значения не равны оператор <= проверяет, что второе значение меньше или равно первому оператор >= проверяет, что второе значение больше или равно первому Результатом сравнения является логическое значение (true или false) package main import "fmt" func main() { fmt.Println("t4 < 6 ttt", 4 < 6, "nt4 > 6 ttt", 4 > 6, "nt2 + 2 == 5 tt", 2+2 == 5, "nt2 + 2 !=5 tt", 2+2 != 5, "nt4 < = 6 tt", 4 <= 6, "nt4 >= 4ttt", 4 >= 4) }
Узнаем типы значений
Чтобы узнать тип любого значения, передайте его функции 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 Строка— последовательность данных, которые обычно представляют символы текста
004 / go Display image
package main import ( "bytes" "encoding/base64" "fmt" "image" "image/png" ) var favicon = []byte{ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0f, 0x04, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x5d, 0x52, 0x1c, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x4c, 0x54, 0x45, 0x7a, 0xdf, 0xfd, 0xfd, 0xff, 0xfc, 0x39, 0x4d, 0x52, 0x19, 0x16, 0x15, 0xc3, 0x8d, 0x76, 0xc7, 0x36, 0x2c, 0xf5, 0x00, 0x00, 0x00, 0x40, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x95, 0xc9, 0xd1, 0x0d, 0xc0, 0x20, 0x0c, 0x03, 0xd1, 0x23, 0x5d, 0xa0, 0x49, 0x17, 0x20, 0x4c, 0xc0, 0x10, 0xec, 0x3f, 0x53, 0x8d, 0xc2, 0x02, 0x9c, 0xfc, 0xf1, 0x24, 0xe3, 0x31, 0x54, 0x3a, 0xd1, 0x51, 0x96, 0x74, 0x1c, 0xcd, 0x18, 0xed, 0x9b, 0x9a, 0x11, 0x85, 0x24, 0xea, 0xda, 0xe0, 0x99, 0x14, 0xd6, 0x3a, 0x68, 0x6f, 0x41, 0xdd, 0xe2, 0x07, 0xdb, 0xb5, 0x05, 0xca, 0xdb, 0xb2, 0x9a, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, } // displayImage renders an image to the playground's console by // base64-encoding the encoded image and printing it to stdout // with the prefix "IMAGE:". func displayImage(m image.Image) { var buf bytes.Buffer err := png.Encode(&buf, m) if err != nil { panic(err) } fmt.Println("IMAGE:" + base64.StdEncoding.EncodeToString(buf.Bytes())) } func main() { m, err := png.Decode(bytes.NewReader(favicon)) if err != nil { panic(err) } displayImage(m) }
003 / go clear screen
package main import ( "fmt" "strings" "time" ) func main() { const col = 30 // Clear the screen by printing x0c. bar := fmt.Sprintf("x0c[%%-%vs]", col) for i := 0; i < col; i++ { fmt.Printf(bar, strings.Repeat("=", i)+">") time.Sleep(100 * time.Millisecond) } fmt.Printf(bar+" Done!", strings.Repeat("=", col)) }
[==============================] Done!
006 / go rune / Руны
Если строки обычно используются для представления последовательностей символов, то руны в языке 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', '') }
005 / go hello
Структура типичного файла Go 1. Директива package. package main 2. Директива import. import "fmt" 3. Собственно код программы. function main{} Пример: package main import "fmt" func main () { fmt.Println("Hello, Go") } 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 Проверяем еще раз работоспособность
002 / go fibonacci
package main import "fmt" // fib returns a function that returns // successive Fibonacci numbers. func fib() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } } func main() { f := fib() // Function calls are evaluated left-to-right. fmt.Println(f(), f(), f(), f(), f(), f(), f(), f()) }
Printf
Основные спецификаторы формата (%) Вот наиболее часто используемые значения для % в fmt.Printf: Общие спецификаторы: %v — Выводит значение в стандартном формате. %+v — Добавляет имена полей структуры. %#v — Отображает значение в виде синтаксиса Go (например, для отладки). %T — Тип значения. %% — Выводит %. Числа: %b — Двоичное представление числа. %c — Unicode-символ, соответствующий числу. %d — Десятичное целое число. %o — Восьмеричное представление. %x, %X — Шестнадцатеричное представление (строчные и заглавные буквы соответственно). %e, %E — Научный формат. %f, %F — Десятичное представление числа с плавающей запятой. %g, %G — Наиболее компактное представление числа (выбирает между %f и %e). Строки и символы: %s — Строка. %q — Строка в кавычках. %x — Строка в виде шестнадцатеричного кода. %p — Указатель (адрес в памяти). Логические значения: %t — Выводит true или false. Пример использования: package main import "fmt" func main() { n := 42 s := "Hello, Go!" f := 3.14159 b := true fmt.Printf("Число: %dn", n) // Число: 42 fmt.Printf("Строка: %sn", s) // Строка: Hello, Go! fmt.Printf("Плавающая точка: %fn", f) // Плавающая точка: 3.141590 fmt.Printf("Логическое: %tn", b) // Логическое: true fmt.Printf("Тип переменной n: %Tn", n) // Тип переменной n: int fmt.Printf("n в двоичном формате: %bn", n) // n в двоичном формате: 101010 fmt.Printf("Now you have %g problems.n", math.Sqrt(7555)) }
Println
В аргументах Println передавались строки. Строка представляет собой последовательность байтов, которые обычно представляют символы текста. Строки можно определять прямо в программе в виде строковых литералов: компилятор Go интерпретирует текст, заключенный в двойные кавычки, как строку Открывающая двойная кавычка "Hello, Go!" Закрывающая двойная кавычка Результат: Hello, Go! Некоторые управляющие символы, которые неудобно вводить с клавиатуры (символы новой строки, табуляции и т. д.), внутри строк могут представляться в виде служебных последовательностей: символа «обратный слеш», за которым следует другой символ (или символы). n Символ новой строки t Символ табуляции " Двойная кавычка Обратный слеш Пример 0: "Hello,nGo!" Результат: Hello, Go! Пример 1: "Hello, tGo!" Результат: Hello, Go! Пример 2: ""Hello, Go!"" "Hello, Go!"
Установка golang на linux
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=amd64 go build -o hello_amd64 # Файл для linux с архитектурой 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 / рабочее пространство
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"
visual code / VS / Code
Плагины:
Beetter Comments Code Spell Checker Error Lens GitLens - Git supercharged go indent-rainbow Markdown All in One markdownllint Russian - Code Spell Checker - В настройках нужно включить ShellCheck Todo Tree
conten markdown
Ctrl + Shift + P content
Плагины для golang
создайте пустую папку и в ней файл main.go и vs предложит установить
config
cat .vscode/settings.json ------------------------- { // - // cursor "editor.cursorBlinking": "expand", "editor.cursorStyle": "line", "editor.cursorWidth": 2, "editor.cursorSmoothCaretAnimation": "explicit", // - // scrol bar "editor.smoothScrolling": true, "editor.scrollbar.horizontal": "hidden", "editor.scrollbar.vertical": "hidden", "editor.minimap.enabled": true, "editor.minimap.maxColumn": 200, "editor.minimap.size": "proportional", "editor.scrollBeyondLastLine": true, // - // TAB "editor.tabSize": 4, "editor.detectIndentation": true, // - // font "editor.fontFamily": "'Droid Sans Mono', 'monospace', monospace", "editor.fontSize": 14, "editor.fontLigatures": false, "editor.lineHeight": 0, "window.zoomLevel": 0, // - // wrap "editor.rulers": [ 90 ], "editor.wordWrap": "bounded", "editor.wordWrapColumn": 90, //"editor.rulers": [], //"editor.wordWrap": "off", // - // auto closing tag "javascript.autoClosingTags": true, "html.autoClosingTags": true, "typescript.autoClosingTags": true, // - // hint "editor.quickSuggestionsDelay": 0, // - // terminal "terminal.integrated.fontFamily": "", "terminal.integrated.fontSize": 14, "terminal.integrated.tabs.enabled": false, // - // folder "explorer.autoOpenDroppedFile": false, "explorer.confirmDelete": true, "explorer.compactFolders": false, "workbench.editor.tabSizing": "shrink", // - // debug "debug.toolBarLocation": "hidden", "debug.focusWindowOnBreak": false, "debug.showInlineBreakpointCandidates": false, "debug.showBreakpointsInOverviewRuler": false, // - // over "editor.formatOnSave": true, "editor.renderWhitespace": "boundary", "editor.insertSpaces": true, "editor.trimAutoWhitespace": true, "files.encoding": "utf8", "files.eol": "auto", "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "workbench.list.smoothScrolling": true, "workbench.editor.closeOnFileDelete": true, "workbench.editor.highlightModifiedTabs": true, "editor.quickSuggestions": { "strings": true }, "commentTranslate.targetLanguage": "ru", "[json]": { "editor.defaultFormatter": "vscode.json-language-features", }, "[go]": { "editor.insertSpaces": false, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "always" }, "editor.defaultFormatter": "golang.go" }, "[markdown]": { "editor.defaultFormatter": "DavidAnson.vscode-markdownlint", "editor.formatOnSave": true }, "todo-tree.general.tagGroups": { "BUG": [ "BUG", "ERROR", "ERR", "ISSUE", "PROBLEM" ], "HACK": [ "HACK", "HACKY", "HACKED", "HACKING", "HACKS" ], "FIX": [ "FIXME", "FIXIT", "FIX IT", "FIX" ], "TODO": [ "TODO", "TO DO", "TO-DO", "TASK", "TASKS", "DO IT", "DOIT" ], "IDEA": [ "IDEA", "IDEAS" ] }, "todo-tree.filtering.excludeGlobs": [ "**/node_modules/*/**", "**/dist/*/**", "**/.vscode/**", "**/.git/**", "**/.github/**", ".gitignore", "package-lock.json" ], "todo-tree.tree.hideTreeWhenEmpty": true, "todo-tree.regex.regex": "(//|#|