← Назад к вопросам

Что такое список?

2.3 Middle🔥 161 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое список в контексте программирования?

В программировании список (list) — это абстрактный тип данных (АТД), представляющий собой упорядоченную коллекцию элементов. Ключевые характеристики списка включают упорядоченность (элементы имеют определённую позицию), возможность дублирования (одинаковые элементы могут встречаться несколько раз) и динамический размер (список обычно может расти или уменьшаться в процессе выполнения программы). Списки являются фундаментальной структурой данных и реализованы практически во всех языках программирования, часто в виде встроенных типов или библиотечных классов.

Список в языке Go

В Go список как таковой не является встроенным типом данных, как, например, list в Python или ArrayList в Java. Вместо этого Go предлагает несколько способов работы с упорядоченными коллекциями, наиболее близкими к классическому списку являются срезы (slices) и двусвязные списки из пакета container/list.

1. Срез (Slice) — основная "списочная" структура

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

  • Динамический размер: можно добавлять и удалять элементы.
  • Индексированный доступ: доступ к элементам по индексу за O(1).
  • Упорядоченность: порядок элементов сохраняется.

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

package main

import "fmt"

func main() {
    // Создание среза (пустого списка)
    var tasks []string
    // Или с инициализацией:
    // tasks := []string{}
    // tasks := make([]string, 0)

    // Добавление элементов в конец (аналог append() в Python)
    tasks = append(tasks, "Написать код")
    tasks = append(tasks, "Протестировать", "Отправить на ревью")
    fmt.Println("Список задач:", tasks) // [Написать код Протестировать Отправить на ревью]

    // Доступ по индексу
    firstTask := tasks[0]
    fmt.Println("Первая задача:", firstTask)

    // Итерация по списку
    for index, task := range tasks {
        fmt.Printf("Задача #%d: %s\n", index+1, task)
    }

    // Длина списка
    fmt.Println("Всего задач:", len(tasks))

    // Удаление элемента (со сдвигом)
    tasks = append(tasks[:1], tasks[2:]...) // Удаляем второй элемент
    fmt.Println("После удаления:", tasks) // [Написать код Отправить на ревью]
}

2. Двусвязный список из пакета container/list

Для случаев, когда требуются частые вставки и удаления в середине коллекции, в стандартной библиотеке Go существует пакет container/list, реализующий двусвязный список.

Особенности list.List:

  • Элементы хранятся как узлы (тип *list.Element).
  • Эффективные вставки и удаления в любом месте списка за O(1), если известен элемент.
  • Нет индексированного доступа — для доступа к элементу по индексу требуется последовательный обход (O(n)).
  • Тип interface{} (до Go 1.18): может хранить элементы любого типа (с приведением типов).

Пример работы с container/list:

package main

import (
    "container/list"
    "fmt"
)

func main() {
    // Создание нового двусвязного списка
    l := list.New()

    // Добавление элементов в конец
    l.PushBack("Первый")
    l.PushBack("Третий")

    // Добавление элемента перед другим
    secondElement := l.PushFront("Нулевой")
    l.InsertAfter("Второй", secondElement)

    // Итерация по списку
    fmt.Print("Список: ")
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Printf("%s ", e.Value)
    }
    // Вывод: Список: Нулевой Первый Второй Третий

    // Удаление элемента
    l.Remove(secondElement)

    // Длина списка
    fmt.Println("\nДлина списка:", l.Len())
}

Сравнение срезов и двусвязных списков в Go

КритерийСрез (Slice)Двусвязный список (list.List)
Индексированный доступДа, за O(1)Нет, требуется обход O(n)
Вставка/удаление в серединеМедленно O(n) из-за сдвигаБыстро O(1), если известен элемент
Использование памятиБолее эффективно (последовательное хранение)Менее эффективно (доп. память на указатели)
ТипизацияСтрогая (определяется при объявлении)Слабая (interface{} / any), требует утверждения типа
Частота использованияОчень высокая (основной инструмент)Низкая (для специфических задач)

Ключевые выводы для Go-разработчика

  1. Срез — это "список" по умолчанию в Go. В 95% случаев, когда вам нужна упорядоченная, изменяемая коллекция, вы будете использовать срез. Его эффективность, типобезопасность и удобство встроенных операций (срезинг, append, copy, len, cap) делают его идеальным выбором.
  2. Двусвязный список — специализированный инструмент. Его стоит применять только в специфических сценариях: реализации LRU-кэшей, очередей, стеков, когда операции вставки/удаления происходят в произвольных местах и критичны по производительности, а доступ по индексу не требуется.
  3. Понимание внутреннего устройства. Для написания эффективного кода важно понимать, что срез — это "вид" (view) на массив, и операции append могут приводить к реаллокации базового массива. Двусвязный список, в свою очередь, не имеет этой проблемы, но "разбрасывает" данные по памяти, что плохо для кэширования процессора.

Таким образом, в Go под термином "список" чаще всего подразумевается срез (slice) — высокоуровневая, типобезопасная и невероятно эффективная структура данных, которая стала идиоматическим способом работы с динамическими последовательностями. Классические связные списки доступны как специализированный инструмент в стандартной библиотеке для решения узкого круга задач.

Что такое список? | PrepBro