3.6 - Конструкторы таблиц

Конструкторы - это выражения, которые создают и инициализируют таблицы. Они являются отличительной особенностью Lua и одним из наиболее удобных и универсальных механизмов.

Простейшей формой конструктора является пустой конструктор,{}, создающий пустую таблицу; мы уже встречали их ранее. Также, конструкторы могут инициализировать массивы (called also sequences or lists). Например, the statement

    days = {"Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday"}
инициализирует days[1] строкой "Sunday" (первый элемент всегда имеет индекс 1, а не 0), days[2] строкой "Monday" и т.д.:
    print(days[4])  --> Wednesday

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

    tab = {sin(1), sin(2), sin(3), sin(4),
           sin(5), sin(6), sin(7), sin(8)}

Для инициализации таблицы как записи, Lua предоставляет следующий синтаксис (т.н. record-style форма конструктора, прим. переводчика):

    a = {x=0, y=0}
что эквивалентно
    a = {}; a.x=0; a.y=0

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

    w = {x=0, y=0, label="console"}
    x = {sin(0), sin(1), sin(2)}
    w[1] = "another field"
    x.f = w
    print(w["x"])   --> 0
    print(w[1])     --> another field
    print(x.f[1])   --> another field
    w.x = nil       -- удаление поля "x"
То есть, все таблицы создаются одинаковыми; конструкторы влияют только на инициализацию.

Every time Lua evaluates a constructor, it creates and initializes a new table. Следовательно, можно использовать таблицы для реализации связных списков (list-style форма конструктора, прим. переводчика):

    list = nil
    for line in io.lines() do
      list = {next=list, value=line}
    end
Данный код считывает строки со стандартного входного потока и сохраняет их в связном списке в обратном порядке. Каждый узел списка представляет собой таблицу с двумя полями: value, которое содержит собственно считанную строку, и next, являющееся указателем на следующий узел. Следующий код выводит содержимое списка:
    l = list
    while l do
      print(l.value)
      l = l.next
    end
(Т.к. список организован по принципу стека (LIFO), строки будут выведены в обратном порядке.) Несмотря на поучительность примера, данная реализация практически не используется в реальных программах на Lua; списки лучше реализовывать как массивы, как можно будет увидеть в Главе 11.

Стили инициализации (record- и list-style) можно применять одновременно в одном конструкторе:

    polyline = {color="blue", thickness=2, npoints=4,
                 {x=0,   y=0},
                 {x=-10, y=0},
                 {x=-10, y=1},
                 {x=0,   y=1}
               }
Приведенный пример также иллюстрирует способ создания вложенных конструкторов для представления более сложных структур данных. Каждый элемент polyline[1], ..., polyline[4] является таблицей, представляющей запись:
    print(polyline[2].x)    --> -10

Тем не менее, обе формы конструктора имеют свои ограничения. Например, нельзя инициализировать поля с отрицательными индексами, или со строковыми индексами that are not proper identifiers. Для этих целей существует другой, более общий, формат. В этом формате инициализируемый индекс явно указывается в квадратных скобках, являясь одним из операндов выражения:

    opnames = {["+"] = "add", ["-"] = "sub",
               ["*"] = "mul", ["/"] = "div"}

    i = 20; s = "-"
    a = {[i+0] = s, [i+1] = s..s, [i+2] = s..s..s}

    print(opnames[s])    --> sub
    print(a[22])         --> ---
Этот синтаксис более громоздкий, но и более гибкий: и list-style и record-style являются вариантами этой общей формы. Конструктор
    {x=0, y=0}
эквивалентен
    {["x"]=0, ["y"]=0}
а конструктор
    {"red", "green", "blue"}
эквивалентен
    {[1]="red", [2]="green", [3]="blue"}

Если есть реальная необходимость, чтобы массивы начинались с индекса 0, несложно написать следующее:

    days = {[0]="Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday"}
Теперь первое значение, "Sunday", имеет индекс 0. Этот ноль не влияет на другие поля, но "Monday", естественно, имеет индекс 1, т.к. следует первым в списке значений конструктора; следующие значения располагаются за ним. Тем не менее, я не рекомендую использовать в Lua массивы, начинающиеся с 0. Помните, что большинство функций считают, что массивы начинаются с индекса 1, и следовательно обрабатывают такие массивы некорректно.

Допускается после последнего элемента помещать запятую. Эта завершающая запятая не является обязательной, но всегда корректна:

    a = {[1]="red", [2]="green", [3]="blue",}
Такая гибкость позволяет легко создавать программы, формирующие таблицы Lua, т.к. нет необходимости обрабатывать последний элемент особым образом.

Наконец, в конструкторе в качестве разделителя всегда можно использовать точку с запятой (;) вместо запятой (,). Мы обычно используем ';' для разделения различных секций конструктора, например для разделения его list-style части от record-style части:

    {x=10, y=45; "one", "two", "three"}


Hosted by uCoz