|
||||||||
Назначение и основные возможности языка языка Go
Время создания: 29.09.2016 13:49
Текстовые метки: язык, go, документация
Раздел: Компьютер - Программирование - Язык Go
Запись: xintrea/mytetra_syncro/master/base/1475146148y5cfs9veos/text.html на raw.github.com
|
||||||||
|
||||||||
Назначение и основные возможности языка Язык Go разрабатывался как язык системного программирования для создания высокоэффективных программ, работающих на современных распределённых системах и многоядерных процессорах. Он может рассматриваться как попытка создать замену языку Си. При разработке уделялось особое внимание обеспечению высокоэффективной компиляции. Программы на Go компилируются в объектный код (хотя доступен и интерпретатор) и не требуют для исполнения виртуальной машины. Основные возможности языка Go:
При этом из языка сознательно исключены:
Язык продолжает развиваться, и разработчики рассматривают возможность включения в язык средств обобщённого программирования. В «Часто задаваемых вопросах»[ 4] по языку приводятся аргументы против использования утверждений, а наследование без указания типа, наоборот, отстаивается. Синтаксис языка Go схож с синтаксисом языка Си , с отдельными элементами, заимствованными из Оберона и скриптовых языков . Go — регистро-зависимый язык с полной поддержкой Юникода в строках и идентификаторах Идентификатор традиционно может быть любой непустой последовательностью, включающей буквы, цифры и знак подчёркивания, начинающийся с буквы и не совпадающий ни с одним из ключевых слов языка Go. При этом под «буквами» понимаются все символы Юникода, относящиеся к категориям «Lu» (буквы верхнего регистра), «Ll» (буквы нижнего регистра), «Lt» (заглавные буквы), «Lm» (буквы-модификаторы) или «Lo» (прочие буквы), под «цифрами» — все символы из категории «Nd» (числа, десятичные цифры). Таким образом, ничто не мешает использовать в идентификаторах, например, русские буквы. Идентификаторы, различающиеся только регистром букв, являются различными. В языке существует ряд соглашений об использовании заглавных и строчных букв. В частности, в именах пакетов используются только строчные буквы. Все ключевые слова Go пишутся в нижнем регистре. Естественно, в строковых литералах могут использоваться все символы Юникода без ограничений. Комментарии и точки с запятой Go использует оба типа комментариев в стиле Си: строчные (начинающиеся с // …) и блочные (/* … */). Строчный комментарий рассматривается компилятором как перевод строки. Блочный, располагающийся на одной строке — как пробел, на нескольких строках — как перевод строки. Точка с запятой в Go используется в качестве обязательного разделителя в некоторых операциях (if, for, switch). Формально также она должна завершать каждую команду, но практически ставить такую точку с запятой в конце строки нет необходимости, так как компилятор в процессе обработки кода сам добавляет точки с запятой в конец каждой строки, которая, без учёта пустых символов, завершается идентификатором, числом, символьным литералом, строкой, ключевыми словами break, continue, fallthrough, return, командой инкремента или декремента (++ или —) или закрывающей круглой, квадратной или фигурной скобкой (важное исключение — запятая в приведённый список не входит). Из этого следует две особенности:
func g() // ! { // НЕВЕРНО } if x { } // ! else { // НЕВЕРНО } func g(){ // ВЕРНО } if x { } else { // ВЕРНО } Здесь в двух первых случаях компилятор вставит точку с запятой в строке, помеченной комментарием с восклицательным знаком, так как строка заканчивается (без учёта пробелов и комментария), соответственно, на круглую и фигурную закрывающиеся скобки. В результате будет нарушен синтаксис объявления функции в первом случае и условного оператора — во втором. Аналогично нельзя в списке элементов, разделённых запятыми, переносить запятую на следующую строку: func f(i // ! , k int // ! , s // ! , t string) string { // НЕВЕРНО } func f(i, k int, s, t string) string { // ВЕРНО } При переносе запятой на следующую строку текущая строка заканчивается идентификатором и в её конце автоматически ставится точка с запятой, что нарушает синтаксис списка (запятая, как уже говорилось выше — исключение из правила, после неё точка с запятой компилятором не добавляется). Таким образом, язык диктует определённый стиль записи кода. В комплект компилятора Go входит утилита gofmt, обеспечивающая правильное и единообразное форматирование исходных текстов. Все тексты стандартной библиотеки Go отформатированы этой утилитой. Объявление типа Синтаксис объявления типа , в основном, решён в духе Паскаля.
При объявлении переменные инициализируются на нулевое значение для данного типа (0 для int , пустая строка для string , nil для указателей ). Объявления можно группировать: var ( i int m float ) Автоматический вывод типов Язык Go поддерживает также автоматический вывод типов . Переменная может быть инициализирована при объявлении, её тип при этом можно не указывать — типом переменной становится тип присваиваемого ей выражения. Для литералов (чисел, символов, строк) стандарт языка определяет конкретные встроенные типы, к которым относится каждое такое значение. Чтобы инициализировать переменную другого типа, к литералу необходимо применить явное преобразование типа. var v = *p Присваивания Внутри функции короткий синтаксис присваивания переменным значения с автоматическим выводом типов напоминает обычное присваивание в Паскале : v1 := v2 // аналог var v1 = v2 Go допускает множественные присваивания, выполняемые параллельно: i, j = j, i // Поменять местами значения i и j. Аргументы функций и методов Аргументы объявляются таким образом: func f(i, j, k int, s, t string) string { } Функции могут возвращать несколько значений типы таких значений заключаются в скобки: func f(a, b int) (int, string) { return a+b, "сложение" } Результаты функций также могут быть именованы: func incTwo(a, b int) (c, d int) { c = a+1 d = b+1 return } Несколько значений, возвращаемых функциями, присваиваются переменным их перечислением через запятую, при этом количество переменных, которым присваивается результат вызова функции, должно точно совпадать с количеством возвращаемых функцией значений: first, second := incTwo(1, 2) // first = 2, second = 3 first := incTwo(1, 2) // НЕВЕРНО - нет переменной, которой присваивается второй результат Обязательное использование локальных переменных и псевдопеременная «_» Любая локальная переменная обязательно должна быть использована, то есть её значение должно участвовать в какой-либо операции в пределах функции, где она объявлена. В отличие от Паскаля и Си, где объявление локальной переменной и последующее её неиспользование или потеря значения, присвоенного локальной переменной (когда переменной присваивается значение, которое затем нигде не читается), может лишь вызывать предупреждение (warning) компилятора, в Go такая ситуация считается языковой ошибкой и приводит к невозможности компиляции программы. Это означает, в частности, что программист не может проигнорировать значение (или одно из значений), возвращаемое вызываемой функцией, просто присвоив его какой-нибудь переменной и отказавшись от его дальнейшего использования. Если возникает необходимость игнорировать одно из значений, возвращаемых вызовом функции, используется предопределённая псевдопеременная с именем «_» (один знак подчёркивания). Она может быть указана в любом месте, где должна быть переменная, принимающая значение. Соответствующее значение не будет присвоено никакой переменной и просто потеряется. Смысл такого архитектурного решения — выявление на стадии компиляции возможной потери результатов вычислений: случайный пропуск обработки значения будет обнаружен компилятором, а использование псевдопеременной «_» укажет на то, что программист сознательно проигнорировал результаты. Так, в примере выше, если из двух возвращаемых функцией incTwo значений нужно только одно, вместо второй переменной нужно указать «_»: first := incTwo(1, 2) // НЕВЕРНО first, _ := incTwo(1, 2) // ВЕРНО, второй результат не используется Переменная «_» может указываться в списке присваивания любое число раз. Все результаты функции, которым соответствует «_», будут проигнорированы. Механизм отложенного вызова defer Отложенный вызов заменяет сразу несколько синтаксических средств, в частности, обработчики исключений и блоки с гарантированным завершением. Вызов функции, которому предшествует ключевое слово defer, параметризуется в той точке программы, где размещён, а выполняется непосредственно перед выходом программы из области видимости, где он был объявлен, независимо от того, как и по какой причине происходит этот выход. Ниже пример использования defer в качестве блока гарантированного завершения[ 7] // Функция, копирующая файл func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) // Открытие файла-источника if err != nil { // Проверка return // Если неудача, возврат с ошибкой } // Если пришли сюда, то файл-источник был успешно открыт defer src.Close() // Отложенный вызов: src.Close() будет вызван по завершении CopyFile dst, err := os.Create(dstName) // Открытие файла-приёмника if err != nil { // Проверка и возврат при ошибке return } defer dst.Close() // Отложенный вызов: dst.Close() будет вызван по завершении CopyFile return io.Copy(dst, src) // Копирование данных и возврат из функции // После всех операций будут вызваны: сначала dst.Close(), затем src.Close() } Прочие синтаксические различия Отсутствуют круглые скобки для условных конструкций for и if : func print(arr []int) { n := len(arr) for i := 0; i < n; i++ { println(arr[i]) } } Любые циклы в Go описываются с помощью конструкции for, синтаксических аналогов while, do-while и других вариантов циклической конструкции в языке нет. Но конструкция for позволяет организовать цикл любого желаемого вида. for { // бесконечный цикл // Выход из цикла должен быть организован вручную, // обычно это делается с помощью конструкций return или break } for i < 10 { // цикл выполняется, пока условие истинно (аналог while в Си) } for i := 0; i < 10; i++ { // точно то же самое, что цикл for в Си } var arr []int for i, v := range arr { // цикл по элементам массива или среза arr // i - индекс текущего элемента // v - сам текущий элемент (аналог arr[i]) } for i := range arr { // используется только индекс } for _, v := range arr { // используется только элемент массива } for range arr { //цикл по коллекции без переменных - поддерживается с версии 1.4 // Может использоваться, когда коллекция используется только в качестве счётчика итераций, // а само текущее значение не требуется. } Обработка ошибок и исключительных ситуаций Язык Go не поддерживает типичный для большинства современных языков синтаксис структурной обработки исключений (блоки try-catch); авторы сочли, что его применение провоцирует программиста на игнорирование ошибок. Для обработки ошибок создатели языка рекомендуют использовать возврат ошибки как одного из результатов вызова функции и проверку его на месте вызова. Типичный порядок, выдерживаемый в стандартных библиотеках Go, выглядит следующим образом:
func ReadFile(srcName string)(result string, err error) { file, err := os.Open("file.txt") if err != nil { return nil, err } ... // Дальнейшее исполнение функции, если ошибки не было return result, nil // Возврат результата и пустой ошибки, если выполнение успешно }
Начинающие программисты на Go нередко критикуют его идеологию обработки ошибок, заявляя, что многочисленные проверки ошибок засоряют код и затрудняют его восприятие, тогда как механизм исключений позволяет сосредоточить всю обработку ошибок в блоках catch. В действительности идеология Go вполне позволяет обрабатывать ошибки элегантно и экономно, в литературе по языку описан ряд паттернов для этого (см., например, статью Роба Пайка в официальном блоге Go (русский перевод ). При возникновении фатальных ошибок, делающих невозможным дальнейшее исполнение программы, возникает состояние «паники» (panic). Типичный пример возникновения паники — деление на нуль в процессе вычислений либо обращение за границы массива. В отсутствие обработки паника приводит к аварийному завершению программы с выдачей сообщения об ошибке и трассировки стека вызовов. Для обеспечения отказоустойчивости программы паника тоже может быть перехвачена и обработана. Для это используется механизм отложенного исполнения defer. Инструкция defer, как говорилось выше, получает в качестве параметра вызов функции (то есть фактически создаёт замыкание ), который производится тогда, когда исполнение программы покидает текущую область видимости. Это происходит даже в случае паники. Для её перехвата в функции, вызываемой в defer, необходимо вызвать стандартную функцию recover() — она прекращает системную обработку паники и возвращает её причину в виде объекта error. Далее программист может обработать полученную ошибку любым желаемым образом, в том числе и возобновить панику, вызвав стандартную функцию panic(err error). Модель многопоточности Go была создана на основе CSP (англ. ) Тони Хоара по типу предыдущих распараллеливаемых языков программирования Occam и Limbo , но также присутствуют такие особенности Пи-исчисления , как канальная передача. Go дает возможность создать новый поток выполнения программы (go-процедуру) с помощью ключевого слова go, которое запускает анонимную или именованную функцию в заново созданной go-процедуре (аналог сопрограмм ). Все go-процедуры в рамках одного процесса используют общее адресное пространство, выполняясь над ОС-потоками , но без жёсткой привязки к последним, что позволяет выполняющейся go-процедуре покидать поток с заблокированной go-процедурой (ждущей, например, отправки или приема сообщения из канала) и продолжать работу далее. func server(i int) { for { print(i) time.Sleep(10) } } go server(1) go server(2) В выражении go можно использовать замыкания . var g int go func(i int) { s := 0 for j := 0; j < i; j++ { s += j } g = s }(1000) Для связи между go-процедурами используются каналы (встроенный тип chan), через которые можно передавать любые значения. Для передачи значения в канал используется <- в качестве бинарного оператора, для получения сообщения из канала — <- в качестве унарного оператора. Объектно-ориентированное программирование Специальное ключевое слово для объявления класса в Go отсутствует, но для любого именованного типа, включая структуры и базовые типы вроде int, можно определить методы , так что в смысле ООП все такие типы являются классами. type newInt int Синтаксис определения метода заимствован из языка Оберон-2 и отличается от обычного определения функции тем, что получатель англ. receiver, то есть объект, для которого вызывается метод, явно указывается в описании после ключевого слова func. Если в C++ или Java получатель всегда имеет стандартное имя (this), то в Go он указывается явно и его имя может быть любым. type myType struct { i int } func (p *myType) get() int { return p.i } func (p *myType) set(i int) { p.i = i } Наследование классов (структур) в Go формально отсутствует, но имеется технически эквивалентный ему механизм встраивания (англ. embedding). В описании структуры можно использовать так называемое анонимное поле — поле, для которого не указывается имя, а только тип. Там где в классических объектно-ориентированных языках используются классы, в Go задействованы интерфейсы (похожи на абстрактные классы C++ ). В Go каждый тип, предоставляющий методы, обозначенные в интерфейсе, может трактоваться как реализация интерфейса, явного объявления не требуется. type myInterface interface { get() int set(i int) } Объявленный выше тип myType реализует интерфейс myInterface, хотя это нигде не указано явно. Такой подход к наследованию соответствует некоторым практическим тенденциям современного программирования. Так в знаменитой книге «банды четырёх» (Эрих Гамма и др.) о паттернах проектирования , в частности, написано:
В Go нет понятия виртуальной функции . Полиморфизм обеспечивается за счёт интерфейсов. Если для вызова метода используется переменная обычного типа, то такой вызов связывается статически, то есть всегда вызывается метод, определённый для данного конкретного типа. Если же метод вызывается для переменной типа «интерфейс», то такой вызов связывается динамически, и в момент исполнения для запуска выбирается тот вариант метода, который определён для типа объекта, фактически присвоенного в момент вызова этой переменной. Динамическая поддержка объектно-ориентированного программирования для Go осуществлена с помощью проекта GOOP . На текущий момент существует только одна основная версия самого языка Go — версия 1. Версии среды разработки (компилятора, инструментария и стандартных библиотек) Go нумеруются по двухзначной («<версия языка>.<основной релиз>») либо трёхзначной («<версия языка>.<основной релиз>.<дополнительный релиз>») системе. Выпуск новой «двузначной» версии автоматически означает прекращение поддержки предыдущей «двузначной» версии. «Трёхзначные» версии выпускаются для исправления обнаруженных ошибок и проблем с безопасностью; исправления безопасности в таких версиях могут затрагивать две последние «двузначные» версии[ 8] . На апрель 2016 года последней является версия 1.6.2, вышедшая 20 апреля 2016 года. С марта 2012 года, когда была представлена версия Go 1, вышли следующие основные версии:
На данный момент существуют два основных компилятора Go:
А так же перспективные разработки:
Среда разработки Go содержит всего три инструмента командной строки: компилятор go, обеспечивающий построение объектных файлов, и вспомогательные утилиты godoc и gofmt, предназначенные, соответственно, для документирования программ и для форматирования исходного кода по стандартным правилам. Для отладки программ может использоваться отладчик gdb. Единственная на текущий момент IDE, изначально ориентированная на язык Go, это LiteIDE[1] (ранее проект назывался GoLangIDE). Это небольшая по объёму оболочка, написанная с использованием qt4, с помощью которой можно выполнять весь базовый набор действий по разработке ПО на Go — создание кода (редактор поддерживает подсветку синтаксиса и автодополнение), компиляцию, отладку, форматирование кода, запуск инструментов. Также Go поддерживается плагинами в универсальных IDE Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus и других. Автоподсветка, автодополнение кода на Go и запуск утилит компиляции и обработки кода реализованы в виде плагинов к более чем двум десяткам распространённых текстовых редакторов под различные платформы, в том числе Emacs, Vim, Notepad++, jEdit. Ниже представлен пример программы «Hello, World!» на языке Go. package main import "fmt" func main() { fmt.Println("Hello, World!") } Пример реализации команды Unix echo : package main import ( "os" "flag" // парсер параметров командной строки ) var omitNewLine = flag.Bool("n", false, "не печатать знак новой строки") const ( Space = " " NewLine = "\n" ) func main() { flag.Parse() // Сканирование списка аргументов и установка флагов var s string for i := 0; i < flag.NArg(); i++ { if i > 0 { s += Space } s += flag.Arg(i) } if !*omitNewLine { s += NewLine } os.Stdout.WriteString(s) }
|
||||||||
Так же в этом разделе:
|
||||||||
|
||||||||
|