воскресенье, 27 января 2008 г.
Серые будни.
Несколько месяцев назад я устроился на новую, престижную работу, в настоящую IT-компанию. В принципе все здорово, новый офис, хорошие люди, вид на Бештау из окна... и перспектива зарабатывать столько-же сколько зарабатывают люди моей профессии в Москве и Санкт-Петербурге $_$.
Но как оказалось, у медали есть и обратная сторона. Теперь у меня пропало ощущение причастности к результату, так как то-что я делаю — всего-лишь малая часть целого. Все-бы ничего, вот только раньше у меня это ощущение было (я точно помню;), и именно оно давало стимул приходить на работу, и стараться что то делать... Сейчас-же чуствую себя винтиком. А это очень неприятное чувство, к нему конечно можно привыкнуть но страшно не хочется.
Масла в огонь подлил один тип в ЖЖ. Товарисч что-то там админит за бугром, и проводит на работе по 14 часов в сутки, только потому что в его жизни почти ничего не осталось, кроме работы. Видимо так и становятся трудоголиками, время-то надо куда-то девать.
И естественно возникает вопрос, на кой черт вообще работать, если это не нравится? Что-бы заработать денег, потом их просрать (превед пятниЦЦо!!!) , и потом опять на работу, что-бы... что-то в этом лишнее, видимо работа :))
Она ведь нужна только для того, что-бы было на что вести нормальный образ жизни, а то получится как в филме Fight Club :-D.
В общем, превращаться в офисный планктон и быть хозяином своей жизни это разные вещи...
пятница, 25 января 2008 г.
Метапрограммирование на С++.
Сегодня я закончил проект в котором очень плотно использовал технику программирования, в народе известную как Generic Metaprogramming %)
Особенность данной методики состоит в том, что программист не пишет алгоритм программы, а создает его обобщенную реализаецию. Потом по обобщенной реализации компилятор генерирует код...
Приемуществ у этого подхода масса, главное состоит в том что большинство ошибок отсеивается на этапе компиляции. Плюс сам процесс разработки становится нелинейным, сначала работа продвигается как обычно, потом, когда написан обобщенный код, продуктивность резко возрастает, в частности я за сегодня написал то - что планировал писать еще одну неделю минимум :). Причина этого в том что мне не пришлось дублировать похожие участки кода в разных местах... вот такой вот бонус))
четверг, 24 января 2008 г.
Долой switch!!!
Незнаю у кого как, но у меня, при виде оператора switch на 2 страницы, появляются подозрения, что, что-то здесь не так... По сути программист не должен в ручную прописывать все возможные варианты работы кода, за него это должен делать компилятор.
В правильно написанной программе (на C++) роль switch чаще всего выполняет таблица виртуальных функций. Но ведь не будешь на каждую мелочь создавать иерархию объектов. В этом случае могут помочь шаблоны...
Мне раньше часто приходилось писать уродливый код вроде этого:
int _tmain(int argc, _TCHAR* argv[]) { unsigned long value = 123465; unsigned char buffer[0x100]; if ( value <= numeric_limits<unsigned char>::max() ) { unsigned char* ptr = reinterpret_cast<unsigned char*>(buffer); *ptr = (unsigned char)value; } else if ( value <= numeric_limits<unsigned short>::max() ) { unsigned short* ptr = reinterpret_cast<unsigned short*>(buffer); *ptr = (unsigned short)value; } else if ( value <= numeric_limits<unsigned int>::max() ) { unsigned int* ptr = reinterpret_cast<unsigned int*>(buffer); *ptr = (unsigned int)value; } return 0; }здесь переменная value записывается в массив, причем в зависимости от ее значения, под ее хранение отводится 1, 2, или 4 байта... код для разных value отличается только типами значений, в остальном он идентичен. Может показаться что проблема притянута за уши, но это не так - это очень серьезная проблема)) Что если вариантов не 3, а скажем 10, а если потребуется добавить еще один? А что если потом потребуется изменить алгоритм ;) Я нашел решение этой проблемы в использовании списков типов. Вот мой вариант такого спска:
class null_t; template <typename Head, typename Tail> struct type_list_node_t { typedef Head Type; typedef Tail Next; };а вот пример использования:
typedef type_list_node_t<unsigned char, type_list_node_t<unsigned short, type_list_node_t<unsigned int, null_t> > > my_typelist;в принципе все очень просто, type_list_node_t - это элемент списка, который может содержать еще один type_list_node_t и т.д. класс null_t служит маркером окончания списка... подробнее можно почитать у Александресску... В общем, не вдаваясь в подробности - следующий код занимается генерацией всех возможных ветвлений, для заданного набора типов, используя для этого рекурсию. Компилятор может это оптимизировать так, что по скорости выполнения, разницы, по сравнению с первым вариантом, не будет.
template<typename T> struct limits_helper_t; template<typename Head, typename Tail> struct limits_helper_t< type_list_node_t<Head, Tail> > { bool operator () (unsigned long value, unsigned char* buffer) { if ( value <= numeric_limits<Head>::max() ) { Head* ptr = reinterpret_cast<Head*>(buffer); *ptr = (Head)value; return true; } limits_helper_t <Tail> next; return next(value, buffer); } }; template<> struct limits_helper_t< null_t > { bool operator () (unsigned long value, unsigned char* buffer) { return false; } }; int _tmain(int argc, _TCHAR* argv[]) { unsigned long value = 123465; unsigned char buffer[0x100]; typedef type_list_node_t<unsigned char, type_list_node_t<unsigned short, type_list_node_t<unsigned int, null_t> > > my_typelist; limits_helper_t<my_typelist> helper; helper(value, buffer); return 0; }однако чем-то смахивает на функциональное программирование :-)
вторник, 22 января 2008 г.
Черный властелин
Сегодня прийдя на работу и открыв google reader был "приятно" удивлен, хабрахабр был под властью черного властелина :-D
понедельник, 21 января 2008 г.
Люблю препроцессор
Недавно наткнулся на забавную фишку в дебрях windows.h, я конечноже специально туда не лазил, просто захотел добавить к своему классу метод Yeild. Так вот, оказалось что гдето там в глубинах windows sdk объявлен макрос с таким-же именем ;). Непомню точно, но объявлен он вроде-бы так - #define Yeild, в общем жесть))
воскресенье, 20 января 2008 г.
Lua и C++, объекты.
Сейчас практически почти каждый язык программирования имеет встроенную поддержку ООП, С++ имеет, а Lua к сожалению нет :'(
Точнее имеет, но несколько странную...
Основная структура данных в языке lua - таблицы. В качестве ключей и значений таблицы, используются переменные, значения которых, типизируются динамически. Это означает что таблица может содержать пары, ключ - значение, разных типов. Таблица - единственная структура данных в lua. С помощью таблиц реализуются все остальные структуры данных, а так-же объекты. Например таблица, в качестве ключей которой используются численные значения, является аналогом одномерного массива. Таблица так-же может содержать функции.
--пример таблицы ... а это комментарий
local table = {}
table["pi"] = 3.14159
table[1] = "some string"
table[3.14159] = "pi"
Язык lua позволяет перегружать операции для таблиц и userdata, это делается с помощью мета-таблиц. Мета-таблица связывает события и их обработчики и представляет из себя обычную таблицу, в качестве ключей которой используются строки - имена событий, а в качестве значений - функции - обработчики событий. В свою очередь с каждой мета-таблицей, может быть связана еще одна мета-таблица, если событие не определено в первой мета-таблице, то оно ищется во второй, и так далее...
События (ключи таблицы) - могут иметь как произвольные значения, так и предопределенные (всегда начинаются с "__"), например, "__add" - перегружает операцию сложения, "__gc" - вызывается перед удалением сборщиком мусора, ключ "__metatable" может содержать другую мета-таблицу и тд...
Синтаксис языка предполагает использование мета-таблиц, для создания объектов, для этого предусмотрен оператор : (двоеточие). При вызове функции из таблицы, с помощью этого оператора, в качестве первого параметра будет неявно передана эта таблица.
Теперь о том как это все работает. В языках программирования поддерживающих классы, объекты всегда создаются на основе информации о типах.
В lua классов нет, но объекты все-таки можно создавать. Скрипт должен содержать код, для создания экземпляра объекта. Для этого он должен, создать новую таблицу и мета-таблицу, содержащую все методы объекта.
Ниже приведен код, котрый позволяет использовать объекты класса PersonInfo, в lua скрипте. Для взаимодействия скрипта с объектами PersonInfo, предназначен класс PersonInfoBinder. extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } #include <stdlib.h> #include <stdio.h> #include <string> #include <conio.h> class PersonInfo { friend class PersonInfoBinder; protected: std::string name; std::string sname; int age; public: static int ObjCount; PersonInfo(): name(), sname(), age(0) { } //конструктор PersonInfo(const char* n, const char* sn, int a): name(n), sname(sn), age(a) { ObjCount++; } //деструктор virtual ~PersonInfo() { ObjCount--; } //метод который мы будем вызывать с помощью Lua virtual void ShowInfo() { printf("PersonInfo %s %s, age %d\n",name.c_str(), sname.c_str(), age); } }; int PersonInfo::ObjCount = 0; //размещающий оператор new, для создания объекта внутри userdata void* operator new(size_t size, lua_State* L, const char* metatableName) { //создаем userdata на вершине стэка void* ptr = lua_newuserdata(L, size); /* состояние стэка * [-1] - объект userdata */ //кладем на вершину стэка metatable с именем metatableName luaL_getmetatable(L, metatableName); /* состояние стэка * [-1] - metatable * [-2] - объект userdata */ //привязываем metatable к объекту userdata lua_setmetatable(L, -2); return ptr; } //Класс через который интерпретатор Lua может работать с объектами PersonInfo class PersonInfoBinder { public: typedef PersonInfo* pPersonInfo; //метод возвращает указатель на PersonInfo или NULL static pPersonInfo checkPersonInfo(lua_State* L, int ix) { //если на вершине стэка объект userdata if (lua_isuserdata(L,ix)) { //возвращаем указатель на объект void* udata = luaL_checkudata(L,ix,"PersonInfo"); if (udata != NULL) //если получится :-) return (pPersonInfo)udata; } return NULL; } //Ф-я вызывает метод ShowInfo объекта находящегося на вершине стэка static int Print(lua_State* L) { //получаем указатель на объект pPersonInfo p = checkPersonInfo(L, 1); //и надеемся на удачу p->ShowInfo(); return 0; } //метод создает объект PersonInfo static int New(lua_State* L) { //получаем черезстэк параметры для создания const char* name = lua_tostring(L,1); const char* sname= lua_tostring(L,2); int age = lua_tointeger(L,3); //создаем объект PersonInfo на вершине стэка pPersonInfo p = new (L, "PersonInfo") PersonInfo(name, sname, age); return 1;//сообщаем интерпретатору, что ф-я вернула один параметр } //Деструктор для объекта - функция вызываетс для объекта во время сборки мусора static int GC(lua_State* L) { //единственный параметр - указатель на уничтожаемый объект pPersonInfo p = checkPersonInfo(L, 1); //вызываем деструктор явно, Lua самостоятельно освободит память занимаемую userdata p->~PersonInfo(); return 0; } //Ф-я делает объект типа PersonInfo доступным интерпретатору static int Bind(lua_State* L) { //методы которые содержит метатаблица объекта static const luaL_Reg somebody_meta[] = { {"__gc", &GC}, {0, 0} }; //методы самого объекта static const luaL_Reg somebody_methods[] = { {"New", &New}, {"Print", &Print}, {0, 0} }; //Ф-я создает глобальную переменную (таблицу) PersonInfo, и записывает в нее все методы из somebody_methods //данная таблица будет общей для всех объектов этого типа luaL_register(L,"PersonInfo",somebody_methods); //используем таблицу PersonInfo, в качестве мета-таблицы luaL_newmetatable(L, "PersonInfo"); //создаем еще одну таблицу, на этот раз в стеке, содержащую методы из массива somebody_meta luaL_register(L,NULL,somebody_meta); //metatable.__index = methods lua_pushliteral(L, "__index"); //копируем таблицу методов lua_pushvalue(L, -3); lua_rawset(L, -3); /* metatable.__index = methods */ lua_pushliteral(L, "__metatable"); //копируем таблицу методов lua_pushvalue(L, -3); lua_rawset(L, -3); lua_pop(L, 1); return 1; } }; int main(void) { int status, result; double sum; lua_State *L; L = lua_open(); luaL_openlibs(L); PersonInfoBinder::Bind(L); status = luaL_loadfile(L, "script.lua"); if (status) { (void)fprintf(stderr, "Syntax error\n"); getch(); exit(1); } result = lua_pcall(L, 0, LUA_MULTRET, 0); if (result) { fprintf(stdout, "runtime error\n"); getch(); exit(1); } lua_close(L); printf("Objects: %d", PersonInfo::ObjCount); getch(); return 0; }Сценарий, который этот код выполняет:
-- script.lua
-- ввод значений пользователем
function read_record(table)
io.write("first name: ")
name = io.read()
io.write("second name: ")
sname = io.read()
io.write("age: ")
agestr = io.read()
age = tonumber(agestr)
local person = PersonInfo.New(name, sname, age)
table[name..' '..sname] = person
end
-- вывод значений таблицы
function find_record(table)
name = io.read()
if table[name] ~= nil then
table[name]:Print()
else
print("\a\r")
end
end
-- Удаление значений из таблицы
function remove_record(table)
name = io.read()
if table[name] ~= nil then
table[name] = nil
else
print("\a\r")
end
end
-- пустая таблица
table = {}
command = ""
while command ~= "quit" do
io.write(">> ")
command = io.read()
if command == "quit" then
break
elseif command == "find" then
find_record(table)
elseif command == "add" then
read_record(table)
elseif command == "remove" then
remove_record(table)
else
print("\a\r")
end
end
return 0
Lua и C++, функции.
Этот пост о том, как заставить интерпретатор lua вызывать функции написанные на С++.
Чтобы интерпретатор мог вызвать функцию, программа, сперва, должна эту функцию зарегистрировать.
lua_register(lua_State*, Имя функции, Адрес функции)
Функция должна возвращать значение типа int и получать в качестве параметра указатель на lua_State:
int glue_function(lua_State* L);
Склеивающая функция должна следовать определенному протоколу для нормальной работы. В первую очередь она должна получить через lua_State количество переданных ей аргументов, это значение находится на вершине стека во время вызова.
int glue_function(lua_State* L);
Затем функция может обращаться к элементам в стеке ( lua_to...(lua_State*, index) [вместо многоточия следует поставить нужный тип, например bool string и тд] ), имеющим индексы i >= 1 i <= count. Эти элементы могут иметь разные типы, функция должна проверять перед использованием типы переданных ей аргументов (функциями lua_is...(lua_State*, index) ), так как интерпретатор lua в момент вызова функции из кода lua не выполняет таких проверок (он не проверяет даже количество переданных в функцию аргументов, не то что их тип ). Чтобы возвратить значение, функция должна поместить в стэк возвращаемые значения (функции lua_push...) и возвратить их количество. Функция может вернуть множество значений (это называется кортеж), причем для функции lua абсолютно естественно, возвращать переменное число параметров.
Следующий пример иллюстрирует сказанное, программа регистрирует функцию print_arg, которая печатает количество переданных ей параметров, сами параметры и возвращает количество параметров. Скрипт использует эту функцию по назначению.
#include "stdafx.h" extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } #include <stdlib.h> #include <stdio.h> #include <string> //Эта функция может принимать переменное число параметров через L int stack_print (lua_State* L) { int n = lua_gettop(L);//получаем количество параметров переданных функции printf("args: %d\n", lua_gettop(L) ); for (int i = 1; i <= n; ++i) { if (lua_isnumber(L, i))//проверяем тип i-го параметра //если это вещественное число выводим его на экран printf("result[%d] = %f\n", i, lua_tonumber(L, i)); else if (lua_isstring(L, i))//проверка на строку printf("result[%d] = %s\n", i, lua_tostring(L, i)); else if (lua_istable(L, i))//если таблица { lua_pushnil(L);//кладем на вершину стека NULL while ( lua_next(L, i) )//эта функция берет значение из стека (по индексу -1), //и использует его в качестве ключа для поиска по таблице, которая //находится в стеке по индексу i, в случае если находит, добавляет в //стек пару ключ - значение, следующие в таблице после ключа находившегося в стеке //если больше нет значений, ф-я возвращает 0 //lua_next используется для перебора элементов таблицы { /* Состояние стека: [-1] value <--Вершина стека [-2] key .... [i] table <-- текущий элемент */ //print key value if (lua_type(L, -2) == LUA_TNUMBER) { printf ( "key = %f", lua_tonumber(L, -2) ); } else if ( lua_type(L, -2) ) { printf ("key = %s", lua_tostring(L, -2)); } //print value if (lua_type(L, -1) == LUA_TNUMBER) { printf ( "\tvalue = %f\n", lua_tonumber(L, -1)); } else if ( lua_type(L, -1) ) { printf ("\tvalue = %s\n", lua_tostring(L, -1)); } //удаляем значение value из стэка, что-бы на следующей итерации //ключ использовался для нахождения следующей пары значений из таблицы lua_pop(L, 1); /* Состояние стека: [-1] key <--Вершина стека .... [i] table <-- текущий элемент */ } } else if (lua_isuserdata(L, i)) //пользовательский тип данных { void* udata = lua_touserdata(L, i); printf("lua userdata %06", udata); } } //возвпащаем один параметр - количество переданных значений lua_pushinteger(L, (ptrdiff_t)n); return 1;//сие означает что возвращается одно значение } int main(void) { int status, result; lua_State *L; L = lua_open();//Новая виртуальная машина луа luaL_openlibs(L);//Открываем библиотеки base table string io math lua_register(L,"print_arg", &stack_print);//Регистрируем функцию status = luaL_loadfile(L, "script.lua");//загружаем файл if (status) { printf("file not found or syntax error\n"); return 1; } result = lua_pcall(L, 0, LUA_MULTRET, 0);//выполняем программу, //точка входа в которую, расположена на вершине стека if (result) { printf("runtime error\n"); return 1; } lua_close(L); //Done return 0; }код скрипта:
-- script.lua x = 8 x = print_arg(5, x, 'lua') io.write('x = '..x..'\n') local table = {} table['pi'] = 3.14159 table[3.14159] = 'pi' print_arg(table)Этот пример, уже намного более практичен. С помощю склеивающих функций вы можете изменять часть логики, не перекомпилируя проект, просто изменив скрипт. В следующей статье я покажу как реализовать в lua возможность, которая не поддерживается этим языком напрямую – объектно ориентированное программирование.
пятница, 18 января 2008 г.
Обработка ошибок
Для себя я уяснил 2 простых правила обработки ошибок в программе.
Любая необработанная ошибка должна иметь последствия.
Неважно какие, главное, что-бы была возможность узнать, что ошибка произошла. Как следствие - второе правило:
Должна существовать возможность узнать причину возникновения ошибки.
Неважно как, но должна быть возможность установить точное место возникновения ошибки. Варианты - от сообщения в лог, с именем функции и номером строки, до создания дампа, содержащего состояние стека, и регистров в момент обнаружения ошибки.
Ну а как-же быть с ошибками, не приводящим к завершению приложения? Для этого есть первое правило %)
Разумеется следует пользоваться исключениями и писать безопасный с точки зрения исключений код. Возвращаемый какой нибудь функцией HRESULT, клиентский код может и не проверить, исключение он конечно то-же может и не перехватить, но по крайней мере будет ясно где ошибка.
Так-же не стоит увлекаться разработкой разветвленных иерархий классов исключений, вполне хватит и одного (благодаря второму правилу и бритве Оккама :-D)
четверг, 17 января 2008 г.
Lua и C++
Я постараюсь рассказать о том как использовать интерпретатор lua в программе написанной на C++. Это может понадобиться в тех случаях, когда в программу нужно встроить поддержку скриптов, либо написать библиотеку для интерпретатора Lua.
Луа – это язык предназначенный для написания расширений. Основа Луа – это библиотека написанная на ANSI С, поэтому она собирается на любой платформе. Библиотеку можно собрать в разных конфигурациях, например исключить парсер, auxiliary library или что нибудь еще. Парсер луа не использует универсальные инструменты для генерации кода, вроде yacc или lex, а написан разработчиками с нуля. Так как библиотека работает очень быстро, и использует очень мало памяти для этого (да и сама имеет очень скромные размеры), Луа может работать на самом разнообразном железе.
Приступим.
В первую очередь нужно скачать исходники lua с оф сайта www.lua.org. Затем вам не составит труда их собрать.
Для того чтобы использовать Lua API, нужно подключить несколько заголовочных файлов:
extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" }lua.h – Lua API lauxlib.h – дополнительный API lualib.h – стандартные библиотеки. Луа использует следующее соглашение об именах функций: lua_### - функция(или макрос, или определение типа) из базового интерфейса, luaL_### - функция из auxiliary библиотеки. Чтобы иметь возможность использовать API, нужно, впервую очередь, создать lua_State. Эта структура содержит состояние виртуальной машины луа, помимо, это стек, через который lua общается с вашей программой.
lua_State *L;
L = lua_open();//создать стек
lua_close(L); //закрыть стек
Весь обмен информацией между программой и скриптом происходит через стэк. Например если ваша программа попросит lua создать какой нибудь объект, то он в результате будет находится на вершине стека. Если программа должна что-нибудь передать скрипту, то это что-то то-же должно быть передано через стэк (примерно так: lua_pushnumber(L, 0) ). Ваша программа может создать несколько экземпляров lua_State. К элементам стека можно обращаться по индексу, положительные значения соответствуют абсолютным индексам – элемент помещенный в стэк первым имеет индекс равный 1, следующий 2 и тд, абсолютные индексы элементов стека не изменяются при добавлении новых элементов в стэк. Отрицательные значения используются для индексирования относительно вершины стека, верхний элемент имеет индекс -1, следующий за ним -2 и тд. При добавлении нового элемента в стэк относительные(отрицательные) индексы всех его элементов изменяются. В общем стек луа, из 3х элементов, выглядит примерно так:
[abs][rel]
[-1][3] <--Top
[-2][2]
[-3][1]<—Bottom
Ок. В первом примере показывается как создать экземпляр виртуальной машины lua, загрузить скрипт, передать в него значения (через глобальную переменную), выполнить скрипт и получить результат его работы.
#include "stdafx.h" extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } #include <stdlib.h> #include <stdio.h> int main(void) { int status, result, i; double sum; lua_State *L; L = lua_open();//Создаем стэк luaL_openlibs(L);//подключить стандартные библиотеки status = luaL_loadfile(L, "script.lua");//загрузить файл //содержимое файла обрабатывается, создается функция, которая помещается на вершину стэка /* Состояние стэка: [-1]main <--top of the stack */ if (status) { (void)fprintf(stderr, "file not found\n"); exit(1); } lua_newtable(L);//создать таблицу, поместить ее на вершину стэка /* Состояние стэка: [-1]table <--top of the stack [-2]main */ //В этом цикле таблица заполняется значениями for (i = 1; i <= 5; i++) { /* Состояние стэка: [-1]table <--top of the stack [-2]main */ lua_pushnumber(L, i);//кладем в стэк число (key) /* Состояние стэка: [-1]key <--top of the stack [-2]table [-3]main */ lua_pushnumber(L, i*2);//добавляем значение ключа (value) /* Состояние стэка: [-1]value <--top of the stack [-2]key [-3]table [-4]main */ lua_rawset(L, -3);//добавить к таблице пару ключ-значение: table[key] = value //таблица (table) должна распологаться по индексу -3, value - на вершине стэка //key - после value /* Состояние стэка: [-1]table <--top of the stack [-2]main */ } lua_setglobal(L, "foo");//добавить глобальную переменную "foo" в скрипт //значение переменной (в данном случае это будет таблица) //должно находится на вершине стэка /* Состояние стэка: [-1]main <--top of the stack */ result = lua_pcall(L, 0, LUA_MULTRET, 0);//Выполняет функцию, функция должна находится //на вершине стэка (функция находящаяся на вершине стэка была создана через luaL_loadfile). //В данном случае ф-я не получает аргументов. if (result) { fprintf(stdout, "runtime error\n"); exit(1); } sum = lua_tonumber(L, lua_gettop(L));//Получаем через стэк значение которое вернул скрипт if (!sum) { fprintf(stdout, "lua_tonumber() failed!\n"); exit(1); } fprintf(stdout, "script exec result: %.0f\n", sum); lua_pop(L, 1);//удаляем число которое вернул скрипт из стэка lua_close(L);//удаляем стэк и интерпретатор return 0; }Файл script.lua должен выглядеть так:
-- script.lua x = 0 for i = 1, #foo do print(i, foo[i]) x = x + foo[i] end return xСкрипт суммирует значения всех элементов таблицы в переменной х, и возвращает результат.
воскресенье, 13 января 2008 г.
Подписаться на:
Сообщения (Atom)