Ответ 1
Используйте 1-основанные таблицы Lua и похороните + 1
внутри функции getIndex
.
Внутри кода C у меня есть массив и нулевой индекс, используемые для поиска внутри него, например:
char * names[] = {"Apple", "Banana", "Carrot"};
char * name = names[index];
Из встроенного Lua script у меня есть доступ к index
с помощью функции getIndex()
и вы хотите реплицировать поиск массива. Есть ли согласованный "лучший" метод для этого, учитывая Lua на основе одного массива?
Например, я мог бы создать массив Lua с тем же содержимым, что и мой массив C, но для индексации требуется добавить 1:
names = {"Apple", "Banana", "Carrot"}
name = names[getIndex() + 1]
Или я мог бы избежать необходимости добавлять 1, используя более сложную таблицу, но это нарушит такие вещи, как #names
:
names = {[0] = "Apple", "Banana", "Carrot"}
name = names[getIndex()]
Какой подход рекомендуется?
Изменить: Спасибо за ответы. К сожалению, решение добавления 1 к индексу внутри функции getIndex не всегда применимо. Это связано с тем, что в некоторых случаях индексы "хорошо известны", то есть можно документировать, что индекс 0 означает "Apple" и т.д. В этой ситуации следует ли выбрать один или другой из вышеуказанных решений или есть лучшая альтернатива?
Изменить 2: Еще раз спасибо за ответы и комментарии, они действительно помогли мне задуматься над этой проблемой. Я понял, что могут быть два разных сценария, в которых возникает проблема, и идеальное решение может быть различным для каждого.
В первом случае рассмотрим, например, массив, который может время от времени отличаться и индекс, который является просто относительно текущего массива. Индексы не имеют никакого значения вне кода. Doug Currie и RBerteig абсолютно правильны: массив должен быть основан на 1, а getIndex
должен содержать +1
. Как уже упоминалось, это позволяет коду на обеих сторонах C и Lua быть идиоматическим.
Второй случай включает индексы, имеющие смысл, и, вероятно, массив, который всегда один и тот же. Крайним примером может быть где names
содержит "Zero", "One", "Two"
. В этом случае ожидаемое значение для каждого индекса хорошо известно, и я чувствую, что сделать индекс на стороне Lua на основе одного неинтуитивно. Я считаю, что один из других подходов должен быть предпочтительным.
Используйте 1-основанные таблицы Lua и похороните + 1
внутри функции getIndex
.
Я предпочитаю
names = {[0] = "Apple", "Banana", "Carrot"}
name = names[getIndex()]
Некоторые функции обработки таблиц - #
, insert
, remove
, sort
- нарушены.
Другие - concat(t, sep, 0)
, unpack(t, 0)
- требуют, чтобы явный начальный индекс работал правильно:
print(table.concat(names, ',', 0)) --> Apple,Banana,Carrot
print(unpack(names, 0)) --> Apple Banana Carrot
Я ненавижу постоянно помнить об этом +1
, чтобы удовлетворить Lua default 1-based style.
Вы должны отражать индексы вашего домена, чтобы они были более удобочитаемыми.
Если индексы на основе 0 подходят для вашей задачи, вы должны использовать индексы на основе 0 в Lua.
Мне нравится, как индексы массивов реализованы в Pascal: вы абсолютно свободны в выборе любого диапазона, который хотите, например, array[-10..-5]of byte
абсолютно подходит для массива из 6 элементов.
Прежде всего, эта ситуация не уникальна для приложений, которые смешивают Lua и C; вы можете столкнуться с тем же вопросом, даже когда используете только приложения Lua. Например, я использую компонент редактора, который индексирует строки, начинающиеся с 0 (да, это C-based, но я использую только интерфейс Lua), но строки в script, которые я редактирую в редакторе, являются 1 на основе. Итак, если пользователь устанавливает точку останова в строке 3 (начиная с 0 в редакторе), мне нужно отправить команду отладчику, чтобы установить ее в строке 4 в script (и конвертировать назад при ударе точки останова).
Теперь предложения.
(1) Мне лично не нравится использовать [0]
хак для массивов, поскольку он ломает слишком много вещей. Вы и Егор уже перечислили многие из них; самое главное для меня он разбивает #
и ipairs
.
(2) При использовании массивов на основе 1 я стараюсь избегать их индексирования и использовать как можно больше итераторов: for i, v in ipairs(...) do
вместо for i = 1, #array do
).
(3) Я также пытаюсь изолировать свой код, который имеет дело с этими преобразованиями; например, если вы конвертируете строки в редакторе для управления маркерами и строками в script, тогда есть функции marker2script
и script2marker
, которые выполняют преобразование (даже если это простые операции +1 и -1). У вас было бы что-то вроде этого в любом случае даже без корректировок + 1/-1, это было бы просто неявным.
(4) Если вы не можете скрыть преобразование (и я согласен, +1 может выглядеть уродливым), сделайте его еще более заметным: используйте вызовы c2l и l2c, которые делают преобразование. По-моему, это не так уродливо, как + 1/-1, но имеет преимущество передачи намерения, а также дает вам простой способ поиска всех мест, где происходит преобразование. Это очень полезно, если вы ищете ошибки от одного или когда изменения API вызывают обновление этой логики.
В целом, я бы не стал слишком беспокоиться об этих аспектах. Я работаю над довольно сложным приложением Lua, которое обертывает несколько компонентов на основе 0 и не запоминает никаких проблем, вызванных различной индексацией...
Здесь Lua metemethods и metatables пригождаются. Используя прокси-сервер таблицы и несколько метаметодов, вы можете изменить доступ к таблице таким образом, который бы соответствовал вашим потребностям.
local names = {"Apple", "Banana", "Carrot"} -- Original Table
local _names = names -- Keep private access to the table
local names = {} -- Proxy table, used to capture all accesses to the original table
local mt = {
__index = function (t,k)
return _names[k+1] -- Access the original table
end,
__newindex = function (t,k,v)
_names[k+1] = v -- Update original table
end
}
setmetatable(names, mt)
Итак, что происходит здесь, заключается в том, что исходная таблица имеет прокси-сервер для себя, тогда прокси-сервер ловит каждую попытку доступа к таблице. Когда к таблице обращаются, она увеличивает значение, к которому он был обращен, имитируя массив на основе 0. Вот результат печати:
print(names[0]) --> Apple
print(names[1]) --> Banana
print(names[2]) --> Carrot
print(names[3]) --> nil
names[3] = "Orange" --Add a new field to the table
print(names[3]) --> Orange
Все операции с таблицами действуют так, как обычно. С помощью этого метода вам не нужно беспокоиться о беспорядочном доступе к таблице.
EDIT: Я хотел бы указать, что новая таблица имен является просто прокси для доступа к исходной таблице имен. Поэтому, если вы запросили #names
, результат будет равен нулю, потому что сама таблица не имеет значений. Чтобы получить доступ к размеру исходной таблицы, вам нужно запросить #_names
.
EDIT 2: Как отметил Чарльз Стюарт в комментарии ниже, вы можете добавить метатет __len в таблицу mt, чтобы гарантировать, что вызов #names дает правильные результаты.
Почему бы просто не превратить C-массив в массив на основе 1?
char * names[] = {NULL, "Apple", "Banana", "Carrot"};
char * name = names[index];
Откровенно говоря, это приведет к некоторому неинтуитивному коду на стороне С, но если вы настаиваете на том, что должны быть "хорошо известные" индексы, которые работают с обеих сторон, это, пожалуй, лучший вариант.
Чистое решение, конечно же, не должно делать эти "хорошо известные" индексы частью интерфейса. Например, вы можете использовать именованные идентификаторы вместо простых чисел. Enums - это хорошее совпадение для этого на стороне C, тогда как в Lua вы можете даже использовать строки в виде клавиш таблицы.
Другая возможность состоит в том, чтобы инкапсулировать таблицу за интерфейсом, чтобы пользователь никогда не обращался непосредственно к массиву, а только через вызов функции C, который затем может выполнять сколь угодно сложные преобразования индексов. Тогда вам нужно только открыть эту функцию C в Lua, и у вас есть чистое и поддерживаемое решение.
Почему бы не представить свой массив C в Lua как userdata? Этот метод описывается кодом PiL, раздел "Userdata" ; вы можете установить метатетируемые методы __index
, __newindex
и __len
, и вы можете наследовать от класса, чтобы обеспечить другие функции обработки последовательности как обычные методы (например, определить массив с array.remove
, array.sort
, array.pairs
функции, которые могут быть определены как методы объекта путем дополнительной настройки на __index
). Выполнение этого способа означает, что у вас нет проблем с синхронизацией между Lua и C, и это позволяет избежать рисков, которые таблицы "массива" обрабатываются как обычные таблицы, что приводит к ошибкам "один за другим".
Вы можете исправить эту ошибку, используя итератор, который знает разные базы индексов:
function iarray(a)
local n = 0
local s = #a
if a[0] ~= nil then
n = -1
end
return function()
n = n + 1
if n <= s then return n,a[n] end
end
end
Однако вам все равно придется добавить нулевой элемент вручную:
Пример использования:
myArray = {1,2,3,4,5}
myArray[0] = 0
for _,e in iarray(myArray) do
-- do something with element e
end