Меня не раз спрашивали, каким образом я рассчитываю размер строк в своих работах (многие из вас могли видеть всякие на первый взгляд сложные формулы типа "sizeof(fmt_str)-2+11-2+MAX_PLAYER_NAME-2+etc."). Поэтому вместо того, чтобы расписывать это всё для каждого отдельно, будет намного лучше составить для всех один урок.
Ниже приведены преимущества и недостатки описываемого в данном уроке метода подсчёта в сравнении с ручным подсчётом (измеритель длины строки + калькулятор), а также особенности, которые нельзя отнести ни к преимуществам, ни к недостаткам данного метода.
Преимущества:- Прозрачность.
Исключаются ситуации, в которых непонятно, каким образом рассчитывается размер форматного массива. Формула подсчёта всегда находится прямо в коде, и никуда не исчезает, в отличие от результатов, выдаваемых калькулятором.
Поняв принципы, по которым составляется формула, можно легко прочесть саму формулу и убедиться в её корректности.
В принципе это преимущество можно отнести и к ручному подсчёту, но только если тоже записывать размер в виде формулы, а не одним числом (например "31 + (-2 + 4) + (-2 + 11)" или "27 + 4 + 11" вместо "42").
- Устойчивость к ошибкам при расчётах.
При измерении длины строки вручную и использовании калькулятора легко допустить ошибку в расчётах. В данном методе такие ошибки исключены, т.к. вместо ручных подсчётов требуется лишь составить формулу - вся "грязная работа" с расчётами перекладывается на компилятор.
Также при ручном подсчёте даже с использованием формулы можно изменить форматную строку, но забыть пересчитать её длину - такой тип ошибок исключается только при объявлении форматной строки в отдельном массиве и использовании оператора sizeof.
С другой стороны, в описываемом в данном уроке методе появляется вероятность ошибки при составлении самой формулы, но такие ошибки отследить и предотвратить куда проще: нужно лишь проверить формулу на соответствие форматной строке - благо, и то, и другое всегда "под рукой", прямо в коде (см. выше).
- Готовность к модификации.
При изменении форматного текста вносить изменения в формулу нужно только при добавлении новых спецификаторов или изменении уже имеющихся.
Для сравнения, при ручном подсчёте размер массива придётся пересчитывать при любом редактировании форматной строки.
Недостатки:- Обязательно наличие прямых рук, растущих из нужного места.
- Требуется объявлять форматную строку в виде отдельного массива.
На производительность или расход памяти это не влияет, но может плохо сказаться на читаемости кода.
Особенности:- Грамотное использование стека.
Этот фактор нельзя отнести к преимуществам только данного метода, поскольку при ручном подсчёте также можно сократить использование стекового пространства вплоть до необходимого минимума.
- Плохая читаемость кода.
Например, такой код:
PHP код:
new string[8 + 4];
format(string, sizeof(string), "Ваш ID: %d", playerid);
... занимает меньше места и может быть проще для восприятия, чем
PHP код:
static const fmt_str[] = "Ваш ID: %d";
new string[sizeof(fmt_str) + (-2 + 4)];
format(string, sizeof(string), fmt_str, playerid);
На самом деле "читаемость" кода - вещь субъективная. При достаточном опыте использования описываемого в данном уроке метода код из второго примера может перестать казаться сложным.
- Трудоёмкость.
И ежу понятно, что бездумно написать "new string[145];" куда проще, чем составлять формулы.
В принципе то же самое относится и к ручному подсчёту с помощью калькулятора, да и к любому другому методу подсчёта вообще. Зачем что-то рассчитывать, если можно обойтись максимальной длиной строки ("145" или "MAX_CHATBUBBLE_LENGTH + 1" для SendClientMessage) или ещё чем-то подобным?
Из всего вышеописанного можно сделать (довольно очевидный) вывод: не стоит оптимизировать всё подряд.
Для личных работ я рекомендую использовать подсчёт размера форматного массива только в критических случаях - например, когда требуется отформатировать сразу много текста и нужно осуществить контроль использования стекового пространства, дабы избежать переполнения стека.
Примеры: команды /stats (отображение диалогового окна с подробной информацией об аккаунте), /admins, /members (диалог со списком админов или членов банды/мафии/организации онлайн).
Также могут быть команды, из которых вызываются функции, требовательные к стеку ("классика жанра": вызов OnDialogResponse из команды на ZCMD) - тогда следует подсчитывать размеры массивов, если не в самой команде, то в вызываемых функциях.
В остальных случаях подсчёты вряд ли будут стоить временных затрат. Не пытайтесь оптимизировать всё подряд.
Для работ, выкладываемых на форуме, я всё же советую использовать подсчёт, будь то даже простая команда, дабы выставляемый на всеобщее обозрение код был как можно более опрятным.
Ок, теперь, когда со "сферой применения" разобрались, перейдём к примерам. Заодно рассмотрим самые частые быдлокодерские ошибки.
Итак, поехали.
Пример 1:
Допустим, у нас есть команда для вывода ID игрока и его никнейма.
Исходный вид команды:
PHP код:
CMD:myinfo(playerid, params[])
{
new string[128];
new name[24];
GetPlayerName(playerid, name, sizeof(name));
format(string, sizeof(string), "Ваш ID: %d, никнейм: %s.", playerid, name);
return SendClientMessage(playerid, -1, string);
}
Теперь разберём этот код.
Сразу бросается в глаза массив name, вернее его размер "24". Так обычно пишут только быдлокодеры.
Во-первых, размер ника игрока может измениться в будущих версиях SA:MP. В то же время названия констант в новых версиях не меняют. Поэтому, чтобы с выходом новой версии не пришлось по-быдлокодерски заменять все "24" на новые числа, лучше использовать константу.
Во-вторых, с помощью SetPlayerName возможна установка никнейма длиной до 24 (MAX_PLAYER_NAME) символов (см. здесь), а в строке нужна ещё одна позиция для завершающего нуль-символа ('\0'). Отсюда получаем максимум "MAX_PLAYER_NAME + 1".
Пэтому следует переделать объявление массива name следующим образом:
PHP код:
new name[MAX_PLAYER_NAME + 1];
Едем дальше.
Пока мы получаем никнейм игрока в name, массив string никак не используется.
Можно сэкономить место в стеке, получив никнейм сразу в string и затем сформатировать содержимое массива в тот же самый массив:
PHP код:
new string[128];
GetPlayerName(playerid, string, sizeof(string));
format(string, sizeof(string), "Ваш ID: %d, никнейм: %s.", playerid, string);
А теперь самое главное: рассчитаем оптимальный размер массива string.
Начнём с того, что вынесем форматную строку из format в отдельный массив, объявив его с помощью ключевых слов static и const.
PHP код:
static const fmt_str[] = "Ваш ID: %d, никнейм: %s.";
new string[128];
GetPlayerName(playerid, string, sizeof(string));
format(string, sizeof(string), fmt_str, playerid, string);
Теперь составим формулу для расчёта размера массива string.
Сначала возьмём размер массива с форматной строкой: sizeof(fmt_str).
Функция format убирает форматные спецификаторы ("%d", "%i", "%s", etc.) и ставит на их место форматируемые значения.
Поэтому мы в формуле тоже будем убирать длину спецификаторов и прибавлять максимальную длину значений.
В общем виде формула будет выглядеть так:
PHP код:
sizeof(fmt_str)-<длина спецификатора 1>+<макс. длина значения 1>-<длина специф. 2>+<макс. длина знач. 2>-...-<длина специф. N>+<макс. длина знач. N>
Также для удобства можно отделить друг от друга пары "спецификатор-значение", взяв их в скобки:
PHP код:
sizeof(fmt_str) + (-<длина специф. 1>+<макс. длина знач. 1>) + (-<длина специф. 2>+<макс. длина знач. 2>) + ... + (-<длина специф. N>+<макс. длина знач. N>)
Рассчитаем формулу размера string для нашего случая. Сначала идёт спецификатор "%d". Его длина 2 символа, но нужно ещё узнать максимальную длину для целых чисел.
Максимальное целочисленное значение: 2147483647 (10 символов), минимальное: -2147483648 (11 символов), следовательно макс. длина будет 11.
Но следует иметь в виду, что это не просто число, а ID игрока.
На данный момент в SA-MP установлен лимит в 1000 игроков. Максимальный ID - 999 - в текстовом виде занимает 3 символа, однако в будущих версиях это число может перевалить за 1000 из-за увеличения лимита (кто знает?), поэтому я бы советовал не упираться вплотную в текущее ограничение и на всякий случай отводить под ID игрока 4 позиции в строке.
Итак, для подсчёта длины строки, в которой форматируется ID игрока, нужно из sizeof(fmt_str) отнять 2 позиции (длина спецификатора "%d") и прибавить 4.
Дальше идёт "%s", на место этого спецификатора ставится никнейм игрока, максимальная длина которого - MAX_PLAYER_NAME символов (не путать с "MAX_PLAYER_NAME + 1" - в форматированную строку завершающий нуль-символ не попадает, поэтому лишняя ячейка не нужна).
В итоге получаем формулу:
PHP код:
sizeof(fmt_str) + (-2 + 4) + (-2 + MAX_PLAYER_NAME)
Настоятельно рекомендую оформлять формулу расчёта длины именно таким образом, заключая подсчёт для каждого форматного спецификатора в отдельную пару скобок для большей наглядности.
Наконец, подставим полученную формулу в код:
PHP код:
CMD:myinfo(playerid, params[])
{
static const fmt_str[] = "Ваш ID: %d, никнейм: %s.";
new string[sizeof(fmt_str) + (-2 + 4) + (-2 + MAX_PLAYER_NAME)];
GetPlayerName(playerid, string, sizeof(string));
format(string, sizeof(string), fmt_str, playerid, string);
return SendClientMessage(playerid, -1, string);
}
Пример 2:
Немного усложним задачу: кроме ID и никнейма игрока будем выводить его здоровье.
Рассчитаем размер буфера для форматирования. Данный пример похож на предыдущий, просто был добавлен вывод вещ. числа, поэтому можно просто взять размер этого числа, вычесть из предыдущей формулы длину спецификатора "%.0f" и прибавить максимальную длину числа.
Новая форматная строка будет выглядеть так:
PHP код:
static const fmt_str[] = "Ваш ID: %d, никнейм: %s, здоровье: %.0f.";
Функция GetPlayerHealth возвращает вещественные числа от 0 до 255. Если здоровье игрока не находится в этом диапазоне, то функция вычисляет остаток от целочисленного деления здоровья на 256.
При этом дробная часть у возвращаемого здоровья отсутствует, а значит её вместе с запятой можно не выводить.
Исходя из этого, получаем максимальную длину выводимого числа в 3 символа.
PHP код:
CMD:myinfo2(playerid, params[])
{
static const fmt_str[] = "Ваш ID: %d, никнейм: %s, здоровье: %.0f.";
// Обратите внимание: размер спецификатора "%.0f" не 2, а 4 символа.
new string[sizeof(fmt_str) + (-2 + 4) + (-2 + MAX_PLAYER_NAME) + (-4 + 3)];
new Float:health;
GetPlayerName(playerid, string, sizeof(string));
GetPlayerHealth(playerid, health);
format(string, sizeof(string), fmt_str, playerid, string, health);
return SendClientMessage(playerid, -1, string);
}
Пример 3:
Ещё один пример: выведем координаты игрока.
Числа будут выводиться с точностью в 2 знака после запятой.
Длина "%.4f" - 4 символа. Но нужно как-то узнать макс. длину вещественного числа.
Минимальное значение координаты Z: -100 - такое значение возможно, если игрок провалится под текстуру. При высоте меньше -100 его телепортирует обратно на поверхность, так устроен движок GTA:SA.
Максимум Z ограничен максимальным значением, возможным для типа Float, но нам вряд ли понадобятся числа больше или равные 1 000 000, т.к. на больших координатах графика в GTA:SA становится глючной и замедляется процесс отрисовки.
Получаем для координаты Z максимум в 6 символов. При этом спецификатор числа "%.4f" будет занимать 4 символа.
Примерно то же самое с координатами X и Y, только там возможны отрицательные числа меньше -100, а значит наравне с положительными числами могут выводиться отрицательные и следует добавить ещё 1 символ под знак минуса.
В итоге для Z получаем 6 символов, для X и Y - 7 символов. Добавим к этому два знака после запятой и саму запятую и получим 9 и 10 для Z и X/Y соответственно.
PHP код:
CMD:getpos(playerid, params[])
{
static const fmt_str[] = "Ваши координаты: X = %.4f, Y = %.4f, Z = %.4f.";
new string[sizeof(fmt_str) + (-4 + 10) * 2 + (-4 + 9)];
new Float:x, Float:y, Float:z;
GetPlayerPos(playerid, x, y, z);
format(string, sizeof(string), fmt_str, x, y, z);
return SendClientMessage(playerid, -1, string);
}
Пример 4:
И последний пример: выведем ID игрока, никнейм и название машины.
Стандартными средствами SA:MP нельзя узнать название модели транспорта. Тем не менее, можно самостоятельно составить массив названий и брать нужное название по номеру модели транспорта.
PHP код:
// В размере "- 399" означает пропущенные ID от 1 до 399,
// а "+ 1" - самая первая строка, отведённая под 0-ю (несуществующую) модель транспорта.
new const vehicle_model_names[611 - 399 + 1][] =
{
/* 0 */ {!"Нет машины"}, // Для случаев, когда (model == 0), т.е. когда игрок не в машине.
/* 400 */ {!"Landstalker"}, // Также обратите внимание, что строки с названиями упакованные.
/* 401 */ {!"Bravura"},
/* ... */
/* 611 */ {!"Utility Trailer"}
}
stock GetVehicleModelName(model, name, size = sizeof(name))
{
// Если номер модели неизвестен (в т.ч. нулевой)...
if (model < 400 || 611 < model)
return strunpack(name, vehicle_model_names[0], size);
// Модели #400 соответствует название в vehicle_model_names[1], а не [0],
// поэтому следует прибавить 1 к индексу.
return strunpack(name, vehicle_model_names[model - 400 + 1], size);
}
Теперь приступим к составлению формулы длины форматной строки.
Что имеем: ID (в предыдущих примерах обусловились выделять под него 4 позиции), ник (MAX_PLAYER_NAME символов) и название модели транспорта.
Максимальную длину названия транспорта можно определить с помощью выражения "sizeof(vehicle_model_names[]) - 1".
Фигурные скобки после названия массива означают, что мы берём размер его второго измерения, а "- 1" добавлено в конец формулы потому, что для формулы нужно определить длину, а не размер её составляющих.
По той же причине в формуле записывается "MAX_PLAYER_NAME", а не "MAX_PLAYER_NAME + 1". Завершающий нуль-символ уже учтён в "sizeof(fmt_str)".
Получаем следующую формулу:
PHP код:
new fmt_str[] = "Ваш ID: %d, ник: %s, транспорт: %s.";
const string_size = sizeof(fmt_str) + (-2 + 4) +
(-2 + MAX_PLAYER_NAME) + (-2 + sizeof(vehicle_model_names[]);
new string[string_size];
Теперь запишем всю команду целиком:
PHP код:
CMD:myinfo3(playerid, params[])
{
new fmt_str[] = "Ваш ID: %d, ник: %s, транспорт: %s.";
const string_size = sizeof(fmt_str) + (-2 + 4) + (-2 + MAX_PLAYER_NAME) + (-2 + sizeof(vehicle_model_names[]);
new string[string_size];
new name[MAX_PLAYER_NAME + 1], vehicle_name[sizeof(vehicle_model_names[])];
GetPlayerName(playerid, name, sizeof(name));
GetVehicleModelName(GetPlayerVehicleID(playerid), vehicle_name);
format(string, sizeof(string), fmt_str, playerid, name, vehicle_name);
return SendClientMessage(playerid, -1, string);
}
Как и в самом первом примере, переменную name можно убрать, записывая ник прямиком в string.
PHP код:
CMD:myinfo3(playerid, params[])
{
new fmt_str[] = "Ваш ID: %d, ник: %s, транспорт: %s.";
const string_size = sizeof(fmt_str) + (-2 + 4) + (-2 + MAX_PLAYER_NAME) + (-2 + sizeof(vehicle_model_names[]);
new string[string_size];
new vehicle_name[sizeof(vehicle_model_names[])];
GetPlayerName(playerid, string, sizeof(string));
GetVehicleModelName(GetPlayerVehicleID(playerid), vehicle_name);
format(string, sizeof(string), fmt_str, playerid, string, vehicle_name);
return SendClientMessage(playerid, -1, string);
}
Но и это ещё не всё. Переменную vehicle_name тоже можно убрать, а название транспорта записывать - угадайте, куда? Тоже в string, но только не в самое начало массива, а с позиции, на которой закончится ник игрока, т.е. с MAX_PLAYER_NAME + 1.
Места в массиве string должно хватить, т.к. в его размер (string_size) уже входят и длина ника, и длина названия транспорта.
PHP код:
CMD:myinfo3(playerid, params[])
{
new fmt_str[] = "Ваш ID: %d, ник: %s, транспорт: %s.";
const string_size = sizeof(fmt_str) + (-2 + 4) +
(-2 + MAX_PLAYER_NAME) + (-2 + sizeof(vehicle_models_names[]);
new string[string_size];
GetPlayerName(playerid, string, sizeof(string));
GetVehicleModelName(GetPlayerVehicleID(playerid), string[MAX_PLAYER_NAME + 1]);
format(
string, sizeof(string), fmt_str,
playerid, string, string[MAX_PLAYER_NAME + 1]
);
return SendClientMessage(playerid, -1, string);
}
Вот так в Pawn можно грамотно рассчитать размер форматируемой строки. Впрочем, приведённые выше правила действуют и в других языках программирования, если не брать в расчёт особенности синтаксиса.
Если этот урок показался вам неполным и вы хотите что-то добавить или спросить - рад буду выслушать вас в комментариях.
Автор: Daniel_Cortez
Копирование данной статьи на других ресурсах без разрешения автора запрещено!