One place for hosting & domains

      Импорт пакетов в Go


      Введение

      Иногда для вашего кода могут потребоваться дополнительные функции вне текущей программы. В подобных случаях вы можете использовать пакеты, чтобы сделать программу более функциональной. Пакет представляет собой все файлы в одном каталоге на диске. Пакеты могут определять функции, типы и интерфейсы, на которые вы можете ссылаться в других файлах или пакетах Go.

      В этом обучающем руководстве вы научитесь выполнять установку, импорт и присвоение имен пакетов.

      Пакеты стандартной библиотеки

      Стандартная библиотека, входящая в комплект Go, представляет собой набор пакетов. Эти пакеты содержат множество фундаментальных компонентов для современного программного обеспечения. Например, пакет fmt содержит базовые функции для форматирования и вывода строк. Пакет net/http содержит функции, позволяющие разработчику создавать веб-службы, отправлять и получать данные по протоколу http и т. д.

      Чтобы использовать функции пакета, необходимо получить доступ к нему с помощью выражения import. Выражение import состоит из ключевого слова import и имени пакета.

      Например, в файле программы Go с именем random.go вы можете импортировать пакет math/rand для генерирования случайных чисел с помощью следующей команды:

      random.go

      import "math/rand"
      

      При импорте пакета мы делаем его доступной в текущей программе в качестве отдельного пространства имен. Это означает, что мы будем ссылаться на функцию в точечной нотации, например package.function.

      На практике функция из пакета math/rand может выглядеть, как показано в этих примерах:

      • rand.Int() вызывает функцию, возвращающую случайное целое число.
      • rand.Intn() вызывает функцию, возвращающую случайный элемент от 0 до заданного числа.

      Создадим цикл for, показывающий, как мы вызываем функцию пакета math/rand в нашей программе random.go:

      random.go

      package main
      
      import "math/rand"
      
      func main() {
        for i := 0; i < 10; i++ {
          println(rand.Intn(25))
        }
      }
      

      Вначале программа импортирует пакет math/rand в третьей строке, а затем переходит к циклу for, который выполняется 10 раз. В этом цикле программа выводит случайное целое число в диапазоне от 0 до 25. Целое число 25 передается функции rand.Intn() как параметр.

      При запуске программы с помощью команды go run random.go мы получаем 10 случайных чисел. Поскольку это случайные числа, они скорее всего будут разными при каждом запуске программы. Результат будет выглядеть примерно так:

      Output

      6 12 22 9 6 18 0 15 6 0

      Выводимые целые числа никогда не будут меньше 0 или больше 24.

      При импорте нескольких пакетов вы можете использовать () для создания блока. Используя блоки, вы можете не повторять ключевое слово import на каждой строчке. Так ваш код будет выглядеть лучше:

      random.go

      
      import (
        "fmt"
        "math/rand"
      )
      

      Чтобы использовать дополнительный пакет, мы можем отформатировать результаты и вывести итерацию генерирования каждого случайного числа во время цикла:

      random.go

      package main
      
      import (
        "fmt"
        "math/rand"
      )
      
      func main() {
        for i := 0; i < 10; i++ {
          fmt.Printf("%d) %dn", i, rand.Intn(25))
        }
      }
      

      Теперь при запуске программы мы получим результат, который будет выглядеть примерно так:

      Output

      0) 6 1) 12 2) 22 3) 9 4) 6 5) 18 6) 0 7) 15 8) 6 9) 0

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

      Установка пакетов

      Хотя в стандартной библиотеке содержится много хороших и полезных пакетов, они целенаправленно разработаны как пакеты общего назначения и не имеют конкретного характера. Это позволяет разработчикам создавать собственные пакеты на основе стандартной библиотеки для выполнения конкретных задач.

      Цепочка инструментов Go содержит команду go get. Эта команда позволяет устанавливать сторонние пакеты в локальную среду разработки и использовать их в вашей программе.

      При использовании go get для установки сторонних пакетов часто используется канонический путь пакета. Это может быть путь к публичному проекту, размещенному в таком хранилище кода, как GitHub. Если вы захотите импортировать пакет flect, вы используете полный канонический путь:

      • go get github.com/gobuffalo/flect

      Инструмент go get найдет пакет на GitHub и установит его в каталог $GOPATH.

      В этом примере код будет устанавливаться в следующий каталог:

      $GOPATH/src/github.com/gobuffalo/flect
      

      Авторы часто обновляют пакеты, устраняя ошибки и добавляя новые возможности. В этом случае вам нужно использовать последнюю версию пакета для применения новых функций или устранения ошибок. Для обновления пакета вы можете использовать флаг -u с командой go get:

      • go get -u github.com/gobuffalo/flect

      Эта команда также установит пакет в Go, если он отсутствует в локальной среде. Если пакет уже установлен, Go попытается обновить его до последней версии.

      Команда go get всегда получает последнюю доступную версию пакета. Однако возможны обновления предыдущих версий пакета, которые все равно новее используемых, и поэтому программу будет полезно обновить. Чтобы получить определенную версию пакета, вам потребуется инструмент для управления пакетами, например Go Modules.

      В версии Go 1.11 модули Go используются для определения версии пакета, которую вы хотите импортировать. В настоящей статье мы не рассматриваем тему управления пакетами, но вы можете прочитать об этом на странице Go Modules на GitHub.

      Присвоение имен импортируемым пакетам

      Вы можете изменить имя пакета, если у вас уже есть локальный пакет с тем же именем, что и у используемого стороннего пакета. В этом случае лучше всего присвоить псевдоним импортируемому пакету. Вы можете изменять имена пакетов и их функций Go посредством указания псевдонима перед именем импортируемого пакета.

      Данное выражение выглядит следующим образом:

      import another_name "package"
      

      В этом примере мы изменяем имя пакета fmt в файле программы random.go. Мы изменим имя пакета fmt на f для сокращения. Измененная программа будет выглядеть следующим образом:

      random.go

      package main
      
      import (
       f "fmt"
        "math/rand"
      )
      
      func main() {
        for i := 0; i < 10; i++ {
          f.Printf("%d) %dn", i, rand.Intn(25))
        }
      }
      

      Теперь внутри программы мы ссылаемся на функцию Printf как на f.Printf, а не на fmt.Printf.

      В других языках изменение имен пакетов поощряется для упрощения их использования в программе, в Go дела обстоят иначе. Например, изменение имени пакета fmt на f не будет соответствовать указаниям руководства по стилю.

      При переименовании импортируемых файлов во избежание конфликта имен следует переименовывать наиболее локальные или относящиеся к конкретным проектам импортируемые пакеты. Например, если у вас имеется локальный пакет с именем strings, и вы хотите импортировать системный пакет с именем strings, лучше переименовать локальный пакет, а не системный. По возможности конфликта имен следует избегать.

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

      Форматирование при импорте

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

      В большинстве редакторов импортируемые файлы форматируются автоматически. Также вы можете настроить редактор для использования goimports. Использование goimports в редакторе считается стандартной практикой, поскольку поддерживать порядок сортировки импортируемых пакетов вручную сложно, и при этом возможны ошибки. Кроме того, в случае изменения стиля goimports также обновляется с учетом этих изменений. Благодаря этому все, кто работает над кодом, будут применять единый стиль импортируемых блоков.

      Здесь показано, как может выглядеть блок импорта до форматирования:

      import (
        "fmt"
        "os"
        "github.com/digital/ocean/godo"
        "github.com/sammy/foo"
        "math/rand"
        "github.com/sammy/bar"
      )
      

      Запуск инструмента goimport (который также запускается при сохранении файлов в большинстве редакторов, где есть этот инструмент) изменит формат следующим образом:

      import (
        "fmt"
        "math/rand"
        "os"
      
        "github.com/sammy/foo"
        "github.com/sammy/bar"
      
        "github.com/digital/ocean/godo"
      )
      

      Обратите внимание, что вначале группируются все пакеты стандартной библиотеки, а затем все сторонние пакеты группируются с пустыми строками. Это упрощает чтение и понимание состава используемых пакетов.

      В этом разделе мы узнали, что использование goimports обеспечит правильное форматирование всех импортируемых блоков и поможет предотвратить несогласованность кода в случае работы нескольких разработчиков с одними и теми же файлами.

      Заключение

      Импорт пакетов позволяет нам вызывать функции, которые не встроены в Go. Некоторые пакеты являются частью стандартной библиотеки, входящей в комплектацию Go, а некоторые нужно устанавливать с помощью инструмента go get.

      Использование пакетов делает программу более функциональной и надежной, поскольку мы используем существующий код. Также мы можем создавать собственные пакеты для себя и других программистов и использовать их в будущих программах.



      Source link

      Установка Apache Tomcat 9 в Ubuntu 18.04


      Введение

      Apache Tomcat — это веб-сервер и контейнер сервлетов, используемый для обслуживания приложений Java. Tomcat представляет собой реализацию технологий Java Servlet и JavaServer Pages с открытым исходным кодом, разработанную Apache Software Foundation. В настоящем руководстве описывается базовая установка и определенные моменты конфигурации последней версии Tomcat 9 на сервере Ubuntu 18.04.

      Предварительные требования

      Прежде чем начать изучение настоящего обучающего руководства, необходимо настроить на сервере стандартного пользователя без прав root и с привилегиями sudo. Вы можете узнать, как это сделать, из нашего руководства по начальной настройке сервера Ubuntu 16.04.

      Шаг 1 — Установка Java

      Tomcat требуется наличие на сервере Java, чтобы выполнять любой код веб-приложения Java. Мы можем удовлетворить это требование, установив OpenJDK с помощью apt.

      Во-первых, обновите индекс пакетов apt:

      Затем установите пакет для разработки Java с помощью apt:

      • sudo apt install default-jdk

      Теперь, когда Java установлена, мы можем создать пользователя tomcat, который будет использоваться для запуска службы Tomcat.

      Шаг 2 — Создание пользователя Tomcat

      По соображениям безопасности Tomcat необходимо запускать с помощью пользователя без привилегий (т. е. без прав root). Мы создадим нового пользователя с группой, который будет запускать службу Tomcat.

      Во-первых, создайте новую группу tomcat:

      Далее создайте нового пользователя tomcat. Мы сделаем этого пользователя членом группы tomcat с домашней директорией /opt/tomcat (куда мы будем устанавливать Tomcat) и с оболочкой в /bin/false (чтобы никто не мог войти в учетную запись):

      • sudo useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat

      Теперь, когда наш пользователь tomcat настроен, мы загрузим и установим Tomcat.

      Шаг 3 — Установка Tomcat

      Лучшим способом установки Tomcat 9 является загрузка последней бинарной версии, а затем ее ручная настройка.

      Найдите последнюю версию Tomcat 9 на странице загрузки Tomcat 9. На момент написания номер последней версии — 9.0.10, но вы должны использовать более позднюю стабильную версию, если такая будет доступна. Перейдите в раздел Binary Distributions (Бинарные дистрибутивы), а затем в списке Core скопируйте ссылку на файл “tar.gz”.

      Далее перейдите к директории /tmp на сервере. Эта директория отлично подходит для загрузки кратковременных элементов, таких как tar-архив (тарбол) Tomcat, который нам не потребуется после извлечения содержимого Tomcat:

      Используйте curl для загрузки ссылки, скопированной на веб-сайте Tomcat:

      • curl -O http://mirror.cc.columbia.edu/pub/software/apache/tomcat/tomcat-9/v9.0.10/bin/apache-tomcat-9.0.10.tar.gz

      Мы установим Tomcat в директорию /opt/tomcat. Создайте директорию, а затем извлеките туда архив с помощью этих команд:

      • sudo mkdir /opt/tomcat
      • sudo tar xzvf apache-tomcat-9*tar.gz -C /opt/tomcat --strip-components=1

      Далее мы сможем настроить соответствующие разрешения пользователя для нашей установки.

      Шаг 4 — Обновление разрешений

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

      Перейдите в директорию, куда мы распаковали установку Tomcat:

      Предоставьте группе tomcat права владения для всей директории установки:

      • sudo chgrp -R tomcat /opt/tomcat

      Далее предоставьте группе tomcat доступ для чтения для директории conf и всего ее содержимого, а также доступ execute для самой директории:

      • sudo chmod -R g+r conf
      • sudo chmod g+x conf

      Сделайте пользователя tomcat владельцем директорий webapps, work, temp и logs:

      • sudo chown -R tomcat webapps/ work/ temp/ logs/

      Теперь, когда соответствующие разрешения настроены, мы сможем создать служебный файл systemd для управления процессом Tomcat.

      Шаг 5 — Создание служебного файла systemd

      Мы должны иметь возможность запустить Tomcat в качестве службы, поэтому мы настроим служебный файл systemd.

      Tomcat должен знать, где установлена Java. Обычно этот путь называют “JAVA_HOME”. Самым простым способом поиска расположения является использование этой команды:

      • sudo update-java-alternatives -l

      Output

      java-1.11.0-openjdk-amd64 1081 /usr/lib/jvm/java-1.11.0-openjdk-amd64

      Ваш путь JAVA_HOME — это вывод из последнего столбца (выделено красным). Учитывая приведенный выше пример, для этого сервера правильное значение JAVA_HOME будет выглядеть таким образом:

      JAVA_HOME

      /usr/lib/jvm/java-1.11.0-openjdk-amd64

      Ваш путь JAVA_HOME может отличаться.

      Получив эту информацию, мы сможем создать служебный файл systemd. Откройте файл tomcat.service в директории /etc/systemd/system с помощью следующей команды:

      • sudo nano /etc/systemd/system/tomcat.service

      Добавьте следующее содержимое в служебный файл. Измените значение JAVA_HOME, если потребуется, согласно значению, которое вы используете в вашей системе. Также вы можете изменить параметры распределения памяти, указанные в CATALINA_OPTS:

      /etc/systemd/system/tomcat.service

      [Unit]
      Description=Apache Tomcat Web Application Container
      After=network.target
      
      [Service]
      Type=forking
      
      Environment=JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
      Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
      Environment=CATALINA_HOME=/opt/tomcat
      Environment=CATALINA_BASE=/opt/tomcat
      Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
      Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'
      
      ExecStart=/opt/tomcat/bin/startup.sh
      ExecStop=/opt/tomcat/bin/shutdown.sh
      
      User=tomcat
      Group=tomcat
      UMask=0007
      RestartSec=10
      Restart=always
      
      [Install]
      WantedBy=multi-user.target
      

      После завершения редактирования сохраните и закройте файл.

      Далее перезагрузите демон systemd, чтобы он знал о существовании вашего служебного файла:

      • sudo systemctl daemon-reload

      Запустите службу Tomcat с помощью следующей команды:

      • sudo systemctl start tomcat

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

      • sudo systemctl status tomcat

      Шаг 6 — Настройка брандмауэра и тестирование сервера Tomcat

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

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

      Tomcat использует порт 8080 для принятия обычных запросов. Разрешите прием трафика для этого порта с помощью следующей команды:

      После изменения брандмауэра вы можете получить доступ к начальной странице по умолчанию, перейдя к вашему домену или IP-адресу с :8080 в браузере.

      Open in web browser

      http://server_domain_or_IP:8080

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

      Если вы смогли успешно получить доступ к Tomcat, сейчас лучшее время для активации служебного файла, чтобы Tomcat автоматически запускался при загрузке:

      • sudo systemctl enable tomcat

      Шаг 7 — Настройка веб-интерфейса управления Tomcat

      Чтобы использовать веб-приложение диспетчера, которое поставляется вместе с Tomcat, мы должны добавить вход на ваш сервер Tomcat. Для этого мы отредактируем файл tomcat-users.xml:

      • sudo nano /opt/tomcat/conf/tomcat-users.xml

      Вы должны будете добавить пользователя, который может получить доступ к manager-gui и admin-gui (веб-приложения, которые поставляются вместе с Tomcat). Вы можете сделать это, определив пользователя, аналогичного приведенному ниже примеру, между тегами tomcat-users. Обязательно измените имя пользователя и пароль на более безопасные:

      tomcat-users.xml — Admin User

      <tomcat-users . . .>
          <user username="admin" password="password" roles="manager-gui,admin-gui"/>
      </tomcat-users>
      

      Сохраните файл и закройте его после завершения.

      По умолчанию новые версии Tomcat ограничивают доступ к приложениям диспетчера и диспетчера хостов для подключений с самого сервера. Поскольку мы выполняем установку на удаленном компьютере, вы, вероятно, захотите удалить или изменить это ограничение. Чтобы изменить подобные ограничения IP-адреса, откройте соответствующие файлы context.xml.

      Для приложения диспетчера введите:

      • sudo nano /opt/tomcat/webapps/manager/META-INF/context.xml

      Для приложения диспетчера хостов введите:

      • sudo nano /opt/tomcat/webapps/host-manager/META-INF/context.xml

      Внутри закомментируйте ограничение IP-адреса, чтобы разрешить подключение из любого места. Также, если вы хотите разрешить доступ только для подключений с вашего IP-адреса, вы можете добавить ваш публичный IP-адрес в список:

      context.xml files for Tomcat webapps

      <Context antiResourceLocking="false" privileged="true" >
        <!--<Valve className="org.apache.catalina.valves.RemoteAddrValve"
               allow="127.d+.d+.d+|::1|0:0:0:0:0:0:0:1" />-->
      </Context>
      

      Сохраните файл и закройте после завершения.

      Чтобы изменения вступили в силу, перезапустите службу Tomcat:

      • sudo systemctl restart tomcat

      Шаг 8 — Доступ к веб-интерфейсу

      Теперь, после создания пользователя, мы снова сможем получить доступ к веб-интерфейсу управления в веб-браузере. Вы снова можете перейти к правильному интерфейсу, введя в браузере доменное имя или IP-адрес вашего сервера с портом 8080:

      Open in web browser

      http://server_domain_or_IP:8080

      Страница, которую вы увидите, должна выглядеть аналогично странице, которую вы видели при тестировании ранее:

      root Tomcat

      Давайте изучим приложение диспетчера, доступ к которому можно получить по ссылке или по адресу http://server_domain_or_IP:8080/manager/html. Вам нужно будет ввести учетные данные аккаунта, которые вы добавили в файл tomcat-users.xml​​​. После этого вы должны увидеть страницу, которая выглядит следующим образом:

      Веб-приложение диспетчера Tomcat

      Веб-приложение диспетчера используется для управления приложениями Java. Здесь вы можете запустить, остановить, перезагрузить, развернуть и отменить развертывание. Также вы можете запустить инструменты диагностики для ваших приложений (например, найти утечки памяти). Информация о вашем сервере доступна в самом низу страницы.

      Теперь давайте рассмотрим диспетчер хостов, доступный по ссылке или по адресу http://server_domain_or_IP:8080/host-manager/html/​​​:

      Виртуальный диспетчер хостов Tomcat

      На странице диспетчера виртуальных хостов вы можете добавить виртуальные хосты для обслуживания ваших приложений.

      Заключение

      Ваша установка Tomcat завершена! Теперь вы можете развернуть ваши собственные веб-приложения Java!

      В настоящее время ваша установка Tomcat работает, но абсолютно не имеет шифрования. Это означает, что все данные, включая чувствительную информацию, например пароли, отправляются в текстовой форме и могут быть кем-либо перехвачены и прочитаны в Интернете. Чтобы предотвратить это, мы настоятельно рекомендуем шифровать ваши соединения с помощью SSL. Вы можете узнать, как реализовать шифрование ваших подключений в Tomcat в этом руководстве (примечание: в настоящем руководстве описано шифрование Tomcat 8 в Ubuntu 16.04).



      Source link

      Массивы и срезы в Go


      Введение

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

      Хотя и массивы, и срезы в Go представляют собой упорядоченные последовательности элементов, между ними имеются существенные отличия. Массив в Go представляет собой структуру данных, состоящую из упорядоченной последовательности элементов, емкость которой определяется в момент создания. После определения размера массива его нельзя изменить. Срез — это версия массива с переменной длиной, дающая разработчикам дополнительную гибкость использования этих структур данных. Срезы — это то, что обычно называют массивами в других языках.

      С учетом этих отличий массивы и срезы предпочтительнее использовать в определенных ситуациях. Если вы только начинаете работать с Go, вам может быть сложно определить, что использовать в каком-либо конкретном случае. Благодаря универсальному характеру срезов, они будут полезнее в большинстве случаев, однако в некоторых ситуациях именно массивы могут помочь оптимизировать производительность программы.

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

      Массивы

      Массивы представляют собой структурированные наборы данных с заданным количеством элементов. Поскольку массивы имеют фиксированный размер, память для структуры данных нужно выделить только один раз, в то время как для структур данных переменной длины требуется динамическое выделение памяти в большем или меньшем объеме. Хотя из-за фиксированной длины массивов они не отличаются гибкостью в использовании, одноразовое выделение памяти позволяет повысить скорость и производительность вашей программы. В связи с этим, разработчики обычно используют массивы при оптимизации программ, в том числе, когда для структур данных не требуется переменное количество элементов.

      Определение массива

      Массивы определяются посредством декларирования размера массива в квадратных скобках [ ], после которых указывается тип данных элементов. Все элементы массива в Go должны относиться к одному и тому же типу данных. После типа данных вы можете декларировать отдельные значения элементов массива в фигурных скобках { }.

      Ниже показана общая схема декларирования массива:

      [capacity]data_type{element_values}
      

      Примечание: важно помнить, что в каждом случае декларирования нового массива создается отдельный тип. Поэтому, хотя [2]int и [3]int содержат целочисленные элементы, из-за разницы длины типы данных этих массивов несовместимы друг с другом.

      Если вы не декларируете значения элементов массива, по умолчанию используются нулевые значения, т. е. по умолчанию элементы массива будут пустыми. Это означает, что целочисленные элементы будут иметь значение 0, а строки будут пустыми.

      Например, следующий массив numbers имеет три целочисленных элемента, у которых еще нет значения:

      var numbers [3]int
      

      Если вы выведете массив numbers, результат будет выглядеть следующим образом:

      Output

      [0 0 0]

      Если вы хотите назначить значения элементов при создании массива, эти значения следует поместить в фигурные скобки. Массив строк с заданными значениями будет выглядеть следующим образом:

      [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
      

      Вы можете сохранить массив в переменной и вывести его:

      coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
      fmt.Println(coral)
      

      Запуск программы с вышеуказанными строчками даст следующий результат:

      Output

      [blue coral staghorn coral pillar coral elkhorn coral]

      Обратите внимание, что при печати элементы массива не разделяются, и поэтому сложно сказать, где заканчивается один элемент и начинается другой. Поэтому иногда бывает полезно использовать функцию fmt.Printf вместо простой печати, поскольку данная функция форматирует строки перед их выводом на экран. Используйте с этой командой оператор %q, чтобы функция ставила кавычки вокруг значений:

      fmt.Printf("%qn", coral)
      

      Результат будет выглядеть следующим образом:

      Output

      ["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

      Теперь все элементы заключены в кавычки. Оператор n предписывает добавить в конце символ возврата строки.

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

      Индексация массивов (и срезов)

      Каждый элемент массива (и среза) можно вызвать отдельно, используя индексацию. Каждый элемент соответствует номеру индекса, который представляет собой значение int, начиная с номера индекса 0 с увеличением в восходящем порядке.

      В следующих примерах мы будем использовать массив, однако данные правила верны и для срезов, поскольку индексация массивов и срезов выполняется одинаково.

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

      “blue coral” “staghorn coral” “pillar coral” “elkhorn coral”
      0 1 2 3

      Первый элемент, строка "blue coral", начинается с индекса 0, а заканчивается срез индексом 3 с элементом "elkhorn coral".

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

      Теперь мы можем вызвать дискретный элемент среза по его номеру индекса:

      fmt.Println(coral[1])
      

      Output

      staghorn coral

      Номера индекса для этого среза входят в диапазон 0-3, как показано в предыдущей таблице. Поэтому для вызова любого отдельного элемента мы будем ссылаться на номера индекса, как показано здесь:

      coral[0] = "blue coral"
      coral[1] = "staghorn coral"
      coral[2] = "pillar coral"
      coral[3] = "elkhorn coral"
      

      Если мы вызовем массив coral с любым номером индекса больше 3, результат будет за пределами диапазона и запрос будет недействителен:

      fmt.Println(coral[18])
      

      Output

      panic: runtime error: index out of range

      При индексации массива или среза всегда следует использовать положительные числа. В отличие от некоторых языков, поддерживающих обратную индексацию с использованием отрицательных чисел, в Go такая индексация приводит к ошибке:

      fmt.Println(coral[-1])
      

      Output

      invalid array index -1 (index must be non-negative)

      Мы можем объединять элементы строк массива или среза с другими строками, используя оператор +:

      fmt.Println("Sammy loves " + coral[0])
      

      Output

      Sammy loves blue coral

      Например, мы можем объединить элемент строки с номером индекса 0 со строкой "Sammy loves ".

      Поскольку номера индекса соответствуют элементам массива или среза, мы можем получать отдельный доступ к каждому элементу и работать с этими элементами. Чтобы продемонстрировать это, мы покажем, как можно изменить элемент с определенным индексом.

      Изменение элементов

      Мы можем использовать индексацию для изменения элементов массива или среза, задав для индексированного элемента другое значение. Это дает нам дополнительные возможности контроля данных в срезах и массивах и позволяет программно изменять отдельные элементы.

      Если мы хотим изменить значение строки элемента с индексом 1 в массиве coral с "staghorn coral" на "foliose coral", мы можем сделать это так:

      coral[1] = "foliose coral"
      

      Теперь, когда мы будем распечатывать массив coral, он будет выглядеть по другому:

      fmt.Printf("%qn", coral)
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

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

      Подсчет элементов с помощью len()

      В Go имеется встроенная функция len(), помогающая работать с массивами и срезами. Как и в случае со строками, вы можете рассчитать длину массива или среза, используя команду len() с указанием массива или среза в качестве параметра.

      Например, чтобы определить количество элементов внутри массива coral, мы используем следующую команду:

      len(coral)
      

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

      Output

      4

      Это дает длину массива 4 в типе данных int, что соответствует действительности, поскольку массив coral содержит четыре элемента:

      coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}
      

      Если вы создадите массив целых чисел с большим количеством элементов, вы можете использовать функцию len() и в этом случае:

      numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
      fmt.Println(len(numbers))
      

      Результат будет выглядеть следующим образом:

      Output

      13

      Хотя примеры массивов содержат относительно немного элементов, функция len() особенно полезна при определении количества элементов внутри очень больших массивов.

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

      Добавление элементов с помощью append()

      append() — это встроенный метод Go для добавления элементов в тип данных коллекции. Но данный метод не будет работать с помощью массива. Как уже отмечалось, основное отличие массивов от срезов заключается в том, что размер массива нельзя изменить. Это означает, что хотя вы можете изменять значения элементов в массиве, вы не можете сделать массив больше или меньше после его определения.

      Рассмотрим наш массив coral:

      coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}
      

      Допустим, вы хотите добавить в массив элемент "black coral". Если вы попробуете использовать функцию append() в массиве с помощью следующей команды:

      coral = append(coral, "black coral")
      

      В результате вы получите сообщение об ошибке:

      Output

      first argument to append must be slice; have [4]string

      Для решения подобных проблем нам нужно больше узнать о типе данных среза, определении среза и процедуре конвертации массива в срез.

      Срезы

      Срез *— это тип данных Go, представляющий собой *мутируемую или изменяемую упорядоченную последовательность элементов. Поскольку размер срезов не постоянный, а переменный, его использование сопряжено с дополнительной гибкостью. При работе с наборами данных, которые в будущем могут увеличиваться или уменьшаться, использование среза обеспечит отсутствие ошибок при попытке изменения размера набора. В большинстве случаев возможность изменения стоит издержек перераспределения памяти, которое иногда требуется для срезов, в отличие от массивов. Если вам требуется сохранить большое количество элементов или провести итерацию большого количества элементов, и при этом вам нужна возможность быстрого изменения этих элементов, вам подойдет тип данных среза.

      Определение среза

      Срезы определяются посредством декларирования типа данных, перед которым идут пустые квадратные скобки ([]) и список элементов в фигурных скобках ({}). Вы видите, что в отличие от массивов, для которых требуется поставить в скобки значения int для декларирования определенной длины, в срезе скобки пустые, что означает переменную длину.

      Создадим срез, содержащий элементы строкового типа данных:

      seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}
      

      При выводе среза мы видим содержащиеся в срезе элементы:

      fmt.Printf("%qn", seaCreatures)
      

      Результат будет выглядеть следующим образом:

      Output

      ["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

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

      oceans := make([]string, 3)
      

      При печати этого среза вы получите следующий результат:

      Output

      ["" "" ""]

      Если вы хотите заранее выделить определенный объем памяти, вы можете использовать в команде make() третий аргумент:

      oceans := make([]string, 3, 5)
      

      При этом будет создан обнуленный срез с длиной 3 и заранее выделенной емкостью в 5 элементов.

      Теперь вы знаете, как декларировать срез. Однако это не решает проблему с массивом coral, которая возникала у нас ранее. Чтобы использовать функцию append() с coral, нужно вначале научиться преобразовывать разделы массива в срезы.

      Разделение массивов на срезы

      Используя числовые индексы для определения начальных и конечных точек, вы можете вызывать подразделы значений внутри массива. Эта операция называется разделением массива на слайсы, и вы можете сделать это посредством создания диапазона числовых индексов, разделенных двоеточием, в форме:[first_index:second_index]. Важно отметить, что при разделении массива на срезы в результате получается срез, а не массив.

      Допустим, вы хотите вывести средние элементы массива coral, не включая первый и последний элемент. Для этого вы можете создать срез, начинающийся с индекса 1 и заканчивающийся перед индексом 3:

      fmt.Println(coral[1:3])
      

      Запуск программы с этой строкой даст следующий результат:

      Output

      [foliose coral pillar coral]

      При создании среза (например, [1:3]), первое число означает начало среза (включительно), а второе число — это сумма первого числа и общего количества элементов, которое вы хотите получить:

      array[starting_index : (starting_index + length_of_slice)]
      

      В этом случае вы вызываете второй элемент (или индекс 1) в качестве начальной точки, а всего вызываете два элемента. Результат будет выглядеть следующим образом:

      array[1 : (1 + 2)]
      

      Вот как это было получено:

      coral[1:3]
      

      Если вы хотите задать начало или конец массива в качестве начальной или конечной точки среза, вы можете пропустить одно из чисел в синтаксисе array[first_index:second_index]. Например, если вы хотите вывести первые три элемента массива coral, а именно "blue coral", "foliose coral" и "pillar coral", вы можете использовать следующий синтаксис:

      fmt.Println(coral[:3])
      

      В результате будет выведено следующее:

      Output

      [blue coral foliose coral pillar coral]

      Команда распечатала начало массива, остановившись непосредственно перед индексом 3.

      Чтобы включить все элементы до конца массива, нужно использовать обратный синтаксис:

      fmt.Println(coral[1:])
      

      Получившийся срез будет выглядеть следующим образом:

      Output

      [foliose coral pillar coral elkhorn coral]

      В этом разделе мы рассказали о вызове отдельных частей массива посредством разделения массива на срезы. Далее мы расскажем, как преобразовывать полные массивы в срезы.

      Преобразование массива в срез

      Если вы создали массив и считаете, что для него требуется переменная длина, вы можете преобразовать этот массив в срез. Чтобы преобразовать массив в срез, используйте процесс разделения на срезы, описанный в разделе Разделение массивов на срезы настоящего документа, но пропустите указание обоих числовых индексов, определяющих конечные точки:

      coral[:]
      

      Учтите, что вы не сможете конвертировать саму переменную coral в срез, поскольку после определения переменной в Go ее тип нельзя изменить. Чтобы обойти эту проблему, вы можете скопировать полное содержание массива в новую переменную в качестве среза:

      coralSlice := coral[:]
      

      Если вы выводите coralSlice, результат будет выглядеть следующим образом:

      Output

      [blue coral foliose coral pillar coral elkhorn coral]

      Теперь попробуйте добавить элемент black coral как в разделе массива, используя функцию append() в новом конвертированном срезе:

      coralSlice = append(coralSlice, "black coral")
      fmt.Printf("%qn", coralSlice)
      

      В результате будет выведен срез с добавленным элементом:

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

      В одном выражении append() можно добавить несколько элементов:

      coralSlice = append(coralSlice, "antipathes", "leptopsammia")
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

      Чтобы объединить два среза также можно использовать выражение append(), но при этом необходимо раскрыть аргумент второго элемента, используя синтаксис расширения ...:

      moreCoral := []string{"massive coral", "soft coral"}
      coralSlice = append(coralSlice, moreCoral...)
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

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

      Удаление элемента из среза

      В отличие от других языков, в Go отсутствуют встроенные функции для удаления элементов из среза. Для удаления элементов из среза их нужно вырезать.

      Чтобы удалить элемент, нужно выделить в срез элементы до него, затем элементы после него, а затем объединить два новых среза в один срез, не содержащий удаленного элемента.

      Если i — индекс удаляемого элемента, формат этого процесса будет выглядеть следующим образом:

      slice = append(slice[:i], slice[i+1:]...)
      

      Удалим из среза coralSlice элемент "elkhorn coral". Этот элемент располагается на позиции индекса 3.

      coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}
      
      coralSlice = append(coralSlice[:3], coralSlice[4:]...)
      
      fmt.Printf("%qn", coralSlice)
      

      Output

      ["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

      Теперь элемент на позиции индекса 3, строка "elkhorn coral", больше не находится в срезе coralSlice.

      Такой же подход можно применить и для удаления диапазона элементов. Допустим, мы хотим удалить не только элемент "elkhorn coral", но и элементы "black coral" и "antipathes". Для этого мы можем использовать в выражении диапазон:

      coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}
      
      coralSlice = append(coralSlice[:3], coralSlice[6:]...)
      
      fmt.Printf("%qn", coralSlice)
      

      Этот код убирает из среза индексы 3, 4 и 5:

      Output

      ["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

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

      Измерение емкости среза с помощью cap()

      Поскольку срезы имеют переменную длину, для определения размера этого типа данных метод len() подходит не очень хорошо. Вместо него лучше использовать функцию cap() для определения емкости слайса. Данная функция показывает, сколько элементов может содержать срез. Емкость определяется объемом памяти, который уже выделен для этого среза.

      Примечание: поскольку длина и емкость массива всегда совпадают, функция cap() не работает с массивами.

      Функция cap() обычно используется для создания среза с заданным числом элементов и заполнения этих элементов с помощью программных методов. Это позволяет предотвратить выделение лишнего объема памяти при использовании команды append() для добавления элементов сверх выделенной емкости.

      Допустим, мы хотим составить список чисел от 0 до 3. Мы можем использовать для этого функцию append() в цикле или мы можем заранее выделить срез и использовать функцию cap() в цикле для заполнения значений.

      Вначале рассмотрим использование append():

      numbers := []int{}
      for i := 0; i < 4; i++ {
          numbers = append(numbers, i)
      }
      fmt.Println(numbers)
      

      Output

      [0 1 2 3]

      В этом примере мы создали срез, а затем создали цикл for с четырьмя итерациями. Каждая итерация добавляла текущее значение переменной цикла i к индексу среза numbers. Однако это могло повлечь выделение ненужного объема памяти, что могло бы замедлить реализацию программы. При добавлении к пустому срезу при каждом вызове функции append программа проверяет емкость среза. Если при добавлении элемента емкость среза превышает это значение, программа выделяет дополнительную память с учетом этого. При этом возникают дополнительные издержки программы, которые могут замедлить выполнение.

      Теперь заполним срез без использования append() посредством выделения определенной длины / емкости:

      numbers := make([]int, 4)
      for i := 0; i < cap(numbers); i++ {
          numbers[i] = i
      }
      
      fmt.Println(numbers)
      
      

      Output

      [0 1 2 3]

      В этом примере мы использовали make() для создания среза и предварительно выделили 4 элемента. Затем мы использовали функцию cap() в цикле для итерации по всем обнуленным элементам, заполняя каждый до достижения выделенной емкости. В каждом цикле мы поместили текущее значение переменной цикла i в индекс среза numbers.

      Хотя с функциональной точки зрения использование append() и cap() эквивалентно, в примере с cap() не выделяется лишняя память, которая потребовалась бы при использовании функции append().

      Построение многомерных срезов

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

      Рассмотрим следующий многомерный срез:

      seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}
      

      Чтобы получить доступ к элементу этого среза, нам нужно использовать несколько индексов, каждый из которых соответствует одному измерению конструкции:

      fmt.Println(seaNames[1][0])
      fmt.Println(seaNames[0][0])
      

      В приведенном выше коде мы вначале определяем элемент с индексом 0 среза с индексом 1, а затем указываем элемент с индексом 0 среза с индексом 0. Результат будет выглядеть так:

      Output

      Sammy shark

      Далее идут значения индекса для остальных отдельных элементов:

      seaNames[0][0] = "shark"
      seaNames[0][1] = "octopus"
      seaNames[0][2] = "squid"
      seaNames[0][3] = "mantis shrimp"
      
      seaNames[1][0] = "Sammy"
      seaNames[1][1] = "Jesse"
      seaNames[1][2] = "Drew"
      seaNames[1][3] = "Jamie"
      

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

      Заключение

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

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



      Source link