Язык программирования go: Часть 1. Краткий обзор и основы программирования

В этой статье рассматриваются принципы работы и ключевые особенности синтаксиса языка go, если сравнивать его с языком C++. В статье также представлены примеры приложений, написанных на языке go.

Дмитрий Выкочко, разработчик, EPAM Systems

Дмитрий Выкочко работает JEE-программистом в компании EPAM Systems и активно занимается ОС Linux, с 2004 года принимая участие в разработке GUI и системного ПО для этой платформы. Также увлекается поиском и изучением open-source мульти-платформенных приложений, способных заменить коммерческие продукты.



28.06.2011

Введение

На рынке языков программирования появился новый игрок – язык программирования go от компании Google. Если верить официальным заявлениям Google, то go – это универсальный язык, пригодный для любых задач. Google анонсировал язык go в 2009 году, и с тех пор он немного изменился, но цели и задачи у него остались прежние: этот язык предназначен для системного программирования и по своей сути очень похож на С++.

Язык go — это компилируемый и многопоточный язык общего назначения, имеющий много общих черт с языком С++, поэтому для успешного освоения представленного материала необходимы определенные знания в C++.

Данный цикл статей будет посвящен изучению языка go. В первой статье рассматриваются принципы работы и ключевые особенности синтаксиса go, если сравнивать его с C++. В статье также представлены примеры приложений, написанных на этом языке.

В настоящее время поддержка языка go присутствует на платформах Linux, MacOS и FreeBSD. Удивляет отсутствие платформы Windows в этом списке, но работы ведутся, и скоро пользователи Windows смогут также попробовать go. С ходом работ по переносу go на ОС Windows можно ознакомиться на этой странице. На данный момент язык go может создавать исполняемый код для процессоров i386, amd64 и ARM. Кроме компилятора от компании Google, существует еще компилятор gccgo из коллекции GNU.

Пример использования

В языке go отсутствуют такие понятия, как класс, конструктор и деструктор (вместе с соответствующими зарезервированными словами). Однако в нем существуют структуры, позаимствованные из языка С, к которым можно привязать функции, поэтому в go можно создавать код и в стиле ООП. Наличие «сборщика мусора» упрощает работу с памятью, по сравнению с C или С++. Существуют и указатели, но арифметика для них не предусмотрена. Поэтому, даже зная адрес переменной, перемещаться в памяти относительного него невозможно. Это сделано из соображений безопасности. Сравнивая язык go с С++, стоит упомянуть о невозможности перегрузки функций и об отсутствии определяемых пользователем операций. Нет также заголовочных файлов и неявного преобразования типов. Многопоточность поддерживается на уровне языка, для связи потоков используются каналы. Они будут рассмотрены более подробно ниже.

В листинге 1 представлен пример программы на языке go, который будет понятен любому программисту, знакомому с синтаксисом C.

Листинг 1. Первое знакомство с go
package main

import "fmt"

func main() {
        fmt.Println("Hello, World")
}

Для работы с языком go потребуется установить следующее программное обеспечение:

  • GCC;
  • стандартная библиотека С;
  • генератор bison;
  • make;
  • awk;
  • редактор ed.

На платформе Ubuntu/Debian для этого используется следующая команда:

sudo apt-get install bison ed gawk gcc libc6-dev make.

Затем необходимо загрузить из репозитария дистрибутив языка go. Для этого используется Mercurial – мультиплатформенная распределённая система управления версиями, которую можно загрузить командой:

sudo apt-get install mercurial.Mercurial

Следующая команда загружает уже непосредственно сам язык:

$ hg clone -r release https://go.googlecode.com/hg/ go

Последний параметр — это имя каталога, в который будет установлен go. В данном примере подразумевается, что установка ведется из домашнего каталога пользователя и изначально каталога go нет. В противном случае могут возникнуть проблемы, так как по умолчанию go считает, что он установлен в каталоге $HOME/go. Если установить дистрибутив go в другой каталог, то его надо указать в переменной окружения $GOROOT, как показано ниже.

export GOROOT=$HOME/goX.

После получения дистрибутива можно переходить к сборке go:

$ cd go/src
$ ./all.bash

На этом этапе будут собраны соответствующие библиотеки и исполняемые файлы. Если в конце в консоли будет напечатано сообщение ALL TESTS PASSED, то сборка прошла успешно, и можно приступать к программированию на go. Если возникают ошибки, это может быть связано с отсутствием инструментов, перечисленных выше. Также при отсутствии подключения к Интернет могут возникнуть ошибки при прохождении автоматизированных тестов, но это не является критичным для дальнейшей работы.

После этих действий код из листинга 1 можно сохранить в файле с именем hello.go и добавить значение $HOME/go/bin в переменную $PATH. В листинге 2 показано, как выполнить компиляцию и запуск go-программы на 32-битной версии Linux.

Листинг 2. Компиляция и запуск go-программы
//компиляция исходного кода
$ 8g hello.go
//компоновка
$ 8l hello.8
//запуск исполняемого файла
$ ./8.out
//результат работы программы
Hello, World

Как видно, процесс компиляции и запуска в языке go совпадает с процессом, использующемся в языках С/С++. Для других платформ будут использоваться другие версии компилятора и компоновщика: 6g/6l для amd64 и 5g/5l для arm.


Типы данных, доступные в языке go

Язык go поддерживает исходный код в кодировке UTF-8, поэтому никаких специальных действий для использования символов, не входящих в английский алфавит, не потребуется. Даже в имени переменной могут содержаться китайские иероглифы.

Также в этом языке существует большое количество различных типов данных. Например, существует пять вариантов целочисленного типа int: int, int8, int16, int32, int64. Такие же типы данных, но с префиксом u, представляют беззнаковые значения. Числа с плавающей точкой представлены тремя типами: float, float32 и float64. В языке go нет неявного приведения типов, поэтому при компиляции могут возникать ошибки. Имеется даже два типа данных для комплексных переменных: complex64 и complex128. Встроенный тип string представляет собой неизменяемый массив байт. В листинге 3 показан пример объявления констант.

Листинг 3. Объявление констант
const name = 0xAAAA
const {
	a, b = iota, iota;
	x, y = 1, 2;
}

Константы a и b получат значения 0 и 1 соответственно. Это обеспечивается специальным счетчиком iota, значение которого увеличивается при каждом последующем упоминании. Новые типы определяются с помощью ключевого слова type, как показано в листинге 4.

Листинг 4. Определение нового типа данных
type Point struct{
	x, y int
}

Вся память в программе инициализируется, так что если объявить переменную, не присваивая ей значения, то она в любом случае получит значение по умолчанию. Значение по умолчанию равно 0 для числовых типов, false для булевых переменных и пустая строка для строк. В листинге 4 также можно увидеть пример объявления структуры в go, который ничем не отличается от объявления структуры в языке C. В языке go к структурам можно привязывать функции и делать из них, по сути, классы, как будет показано позже.

Говоря о типах, необходимо упомянуть сложные структуры, поддерживаемые на уровне языка, - массивы, срезы (slices) и хэш-таблицы. Главное отличие массивов в go от большинства популярных языков — это то, что они являются значениями, т.е. имя массива не является ссылкой. Ниже приведен пример объявления массива:

var arr [3] int;

Получить адрес массива можно через &arr. Но из-за отсутствия адресной арифметики, никакой пользы из знания адреса извлечь нельзя, разве что передавать в функцию адрес массива вместо его копии, что положительно влияет на производительность. Инициализировать массив можно следующим образом:

var arr [10]int { 2:1, 3:1, 5:1, 7:1 }

Для инициализации определенных элементов необходимо указать индекс элемента и через запятую его значение. Если просто указать n значений через запятую, то будут инициализированы только первые n элементов. Так как в go вся память инициализируется, то все элементы, значения для которых не были заданы явно, получат значения по умолчанию.

На «настоящие» массивы больше похожи срезы, объявляемые как безразмерные массивы:

var sl [] int;

Задать значение среза можно адресом или фрагментом («срезом» – отсюда и название) массива, как показано ниже:

sl = arr[7:9];
sl = &arr

Существует также встроенная функция make(), которая выделяет память и возвращает адрес этого фрагмента памяти, который можно сохранить в переменной:

sl = make([]int, 10)

Ниже приведен пример создания хеш-таблицы:

var mp = map[string]float { "first":1, "second":2.0001 }

В квадратных скобках задается тип ключа, а после него — тип хранимого значения. Хэш-таблицы можно и не инициализировать. Обращаться к элементам можно как в ассоциативном массиве в PHP: mp[“second”]. При обращении к несуществующему элементу генерируется ошибка. В go существует удобное средство for - range для итерации по значениям массива или хеш-таблицы, как показано ниже:

for key, value := range mp {
	fmt.Printf("key %s, value %g\n", key, value)
}

Объектно-ориентированным язык go назвать нельзя, но некоторые возможности ООП в нем имеются. Например, можно объявить функцию, привязанную к определенному типу. Тогда класс можно определить следующим образом: структура отвечает за поля, а привязанные методы реализуют поведение класса, как показано в листинге 5. Но все же основные возможности ООП, например, наследование, оказываются недоступными.

Листинг 5. Объявление класса в go
type Point struct { x, y float }
func (p Point) Abs() float {
	return math.Sqrt(p.x*p.x + p.y*p.y)
}

Также можно выполнить привязку к массиву и другим типам. Отдельно следует упомянуть интерфейсы. Они по своей сути похожи на интерфейсы в языке Java. При объявлении интерфейса в нем объявляется набор методов, и все типы, реализующие этот интерфейс, совместимы с переменной этого интерфейса, как показано в листинге 6.

Листинг 6. Использование интерфейсов
type AnInterface interface { foo() float }
type Point struct { x, y float }

func (p Point) foo() float {...}

var iv AnInterface;

pp := new(Point);
iv = pp; // ошибки не произойдет: *Point имеет метод foo()

Работа с памятью в go организована по аналогии с языком С. При объявлении указателя для него необходимо получить память, за что отвечает оператор new().

var p *Point = new(Point)

Наиболее интересным типом являются каналы, используемые при многопоточном программировании, которые будут обсуждаться позже.


Функциональное программирование в go

Набор конструкций для контроля хода выполнения программы в языке go скуднее по сравнению с языком C. В go доступны только конструкции if, for и switch. Первое, что бросается в глаза, это отсутствие круглых скобок.

Листинг 7. Конструкции для управления ходом выполнения программы
Loop: for i := 0; i < 10; i++ {
	switch f(i) {
		case 0, 1, 2: break Loop
	}
	g(i)
}

При этом конструкция goto сохранилась, а операции инкремента и декремента более не являются выражениями, и их нельзя подставлять в вычисления. Префиксная форма совсем отсутствует.

Функции объявляются при помощи ключевого слова func. Особенностью является то, что они могут возвращать сразу несколько значений, по аналогии с множественным присваиванием, как показано в листинге 8.

Листинг 8. Множественное присваивание и создание функций
var v1,v2 uint32 = 10, 20

func MySqrt(f float) (v float, ok bool) {
	if f >= 0 { v,ok = math.Sqrt(f),true }
	else { v,ok = 0,false }
	return v,ok
}

После параметров в скобках указываются типы возвращаемых значений. В случае c одним значением скобки не используются. Еще одна интересная возможность - это выражение defer, пример использования которого приведен ниже.

defer foo();

Конструкция defer представляет собой некий триггер, и функция foo() будет вызвана по достижении return. Если defer используется в функции несколько раз, то соответствующие методы выстраиваются в стек. Польза от подобного нововведения неочевидна, особенно при большом объеме кода. Так программисту, незнакомому с кодом, придется держать в памяти весь этот стек вызовов со всеми параметрами. При использовании defer структурное программирование не обеспечивается.


Многопоточное программирование

Многопоточное программирование в go обеспечивается ключевым словом go. Переданная в качестве параметра функция запускается в отдельном потоке. Функции могут общаться друг с другом через каналы. Все достаточно просто и эффективно, как можно увидеть в листинге 9.

Листинг 9. Многопоточная программа
package main
import ("fmt"; "time")

var ch1 chan int = make(chan int);

func foo(ch chan int) {
	for { fmt.Println(<-ch) }
}

func bar(ch chan int) {
for i := 0; ; i++ { 
		time.Sleep(1000000000);
		ch < i 
	}
}

func main(){
	go bar(ch1)
	go foo(ch1)
	time.Sleep(10000000000)
}

В главной функции main() видно, что в разных потоках запускаются две функции. В качестве параметра им передается канал, созданный встроенной функцией make(). При создании канала можно указать его размер. Функция time.Sleep() отвечает за задержку при выполнении потока. В функции bar() в канал отправляются целые числа (ch <- i), а в функции foo() они, по мере поступления, печатаются. Ничего сложного, но на этом простейшем примере наглядно показано, как организовать взаимодействие потоков в программе.


Пакеты

Представленной информации вполне достаточно для создания простейших программ, поэтому можно перейти к обсуждению архитектуры go-программы. Каждая программа – это пакет (см. package main в листинге 1), а точнее набор пакетов. Программы собираются из пакетов, и то, что обычно называется библиотеками, в go называется набором пакетов.

Исполняемая программа должна иметь пакет main и метод main, с которого начинается выполнение программы, и по завершении которого программа закрывается. Выражение import “package” дает доступ к методам, переменным и константам пакета package. Обращаться к ним следует через оператор . (точка), например, package.Foo().

Область видимости в go несколько отличается от таковой в языке С. Внутри пакета все переменные, функции, константы и типы имеют глобальную видимость. Но для обращения к ним из клиента, импортировавшего этот пакет, имена последних должны начинаться с большой буквы.

Например, клиент импортировал пакет package, в котором объявлены следующие переменные:

const hello = “Im not visible in client, just in package” //видна только в пакете
const Hello = “I can say hello to client” //видна также при импорте

Вторая константа будет доступна клиенту по ссылке package.Hello.

Хотя язык go достаточно молод, он уже имеет обширную библиотеку пакетов. Выше использовался пакет fmt, предназначенный для форматированного ввода/вывода. Пакет os предоставляет доступ к файлам, аргументам командной строки, позволяет запускать другие программы и т.д. Есть пакеты для шифрования, сжатия, кодирования и журналирования. Отдельного внимания заслуживают пакеты net, http, websocket для работы с сетью, позволяющие без особых усилий создавать Web-приложения. В стандартной поставке есть даже пакет для работы с изображениями. После установки в каталоге $HOME/go появляется каталог pkg, в котором есть каталог, соответствующей архитектуре. В этом каталоге выполняется хранение и поиск пакетов.


Заключение

В 2009 go был признан языком года по версии организации TIOBE. Сложно сказать, насколько этот язык «созрел» для серьезных проектов, но его потенциал очевиден. Простота создания многопоточных и сетевых приложений позволяет говорить о жизнеспособности и востребованности проекта в целом. «Идеологическое родство» с языками С и С++, возможно, поможет языку go, как в свое время родство с С помогло развитию C++, а родство с C++ способствовало становлению Java. Огромную роль играет и поддержка со стороны разработчика языка – компании Google, возможно, самой влиятельной IT-компании на данный момент. В следующих статьях будут рассматриваться более сложные аспекты программирования на языке go.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=696192
ArticleTitle=Язык программирования go: Часть 1. Краткий обзор и основы программирования
publish-date=06282011