Vlad писал(а):
На самом деле все очень просто. Строки с нулем на конце нужны только для WinAPI (ну или других внешних либ). Если они возникают где-то еще, значит вы используете их не по назначению, со всеми вытекающими :)
Казалось бы, это так. Но стандартная процедура COPY копирует строку не целиком, а до нулевого символа. Присваивание "a := b" копирует целиком, но работает только при условии, что a и b одного типа. Если b получено в процедуру как open array, т.е. без указания типа, присваивание невозможно, остаётся либо COPY, либо самописанный цикл. Конкатенации строк (+) в Обероне[-2] вообще нет.
Проблема в том, что язык сохраняет для программиста различение между контейнером (массив символов) и содержимым (используемым объёмом - до нулевого символа). В КП признали наличие проблемы и ввели символ $, чтобы явно из контейнера вытаскивать содержимое до нуля, после чего такое содержимое сделали совместимым по присваиванию с любым контейнером, обойдя чрезмерную типобезопасность. Добавили и конкатенацию. Вот цитата из "Docu/CP-New/What is New in Component Pascal.odc": "The + operator now allows to concatenate strings. The target variable must be of sufficient length to hold the resulting string".
Видите, переменная-результат не
будет, а
должна иметь достаточный размер. Если я не хочу трэпа, а хочу заранее убедиться в таком предусловии или даже выделить нужную память - как мне следует поступить? Просуммировать LEN каждого из параметров и в результате выделить память с большим запасом? Или померить длину каждого из них до нуля и сложить эти значения? Я понимаю, что можно выполнить LEN(s$) для всех элементов, но по производительности это будет только хуже.
Когда я продумывал стратегию работы со строками, у меня был соблазн считать так, как предлагает Vlad и на преимущество чего указывает Сполски, - что длина строки = LEN контейнера. В этом случае ноль в конце ставить не нужно, ну разве что для общения с ОС и прочими внешними библиотеками. Однако для констант компилятор сам добавляет ноль, не спрашивая меня. Без нуля не работает COPY (Оберон) и "$" (КП) - придётся использовать SYSTEM.MOVE с присущими ему рисками и вообще импортом SYSTEM. Да и вообще, получается как минимум непоследовательность: где-то нули есть, а где-то нет.
Если уж нули нужны, то пусть лучше будут везде, подумал я. Пусть реальная длина строки будет LEN-1, с этим неудобством можно смириться, применяя одни кострукции и воздерживаясь от других (скажем, используя везде WHILE i < LEN, но не FOR i := 0 TO LEN). Всё равно это константное смещение, а не интервал - длина строки равна LEN-1, а не 0..LEN-1, как в случае если 0X завершает строку. Возвращаясь к разбору конкатенации, я должен выделить под результат число байт = (сумме LEN всех компонентов минус число компонентов) плюс один. Однако снова проблема: конкатенация не всегда будет давать всегда строку ожидаемой длины, поскольку любой 0X в середине строки будет считать завершающим. На уровне компилятора укоренено представление о том, что строка завершается нулём - так было в Обероне, так осталось в КП. Значит, конкатенация в варианте КП для меня бесполезна, всё равно надо создавать библиотечную функцию. У пользователя такой библиотеки будет возникать вопрос - в каком случае что использовать.
Есть ещё один немаловажный аспект, о котором пишет Сполски, - производительность. Гораздо дешевле размещать контейнеры фиксированного размера в стеке, чем динамической длины в куче. Здесь Оберон/КП перекладывают принятие решений на разработчика: критичные по быстродействию вещи лучше написать без динамической памяти. Учитывая это, более эффективным для конкатенации решением будет существующее: есть контейнер фиксированного размера, накидаем в него куски строк, а в конце добавим ноль. В результате добавление каждого фрагмента сопровождается двумя вычислениями длины, то есть, преимущества паскалевских строк для этой типичной операции - потеряны. И LEN ничем уже помочь не может.
В общем, я пришёл примерно к такому выводу. Работу со строками лучше вынести в [стандартную] библиотеку или полностью встроить в компилятор. Наличие в компиляторе полумер только маскирует проблему, откладывая её решение. И чем больше таких полумер заложено в компилятор, тем сложнее написать библиотеку, совместимую со всеми этими огрызками решений. В идеале строка не должна ничего общего иметь с массивом символов, достаточно предусмотреть конвертацию одного в другое, и всё. Для работы со строками должны быть свои библиотечные функции, либо - по соображениям эффективности - языковые операции. Строка и контейнер должны чётко различаться, и работа со строками должна быть воможной без оглядки на контейнер. Если хочешь работать на уровне контейнеров - работай с массивами символов. Если хочешь работать со строками, то работай с ними. В последнем случае должно работать прозрачное выделение памяти под содержимое и быть моментально доступным знание о реальной длине строки. Не думаю, что удастся полноценно совместить работу нижнего и верхнего уровня, если поместить это в библиотеку, а не в компилятор. Но это пока что только мнение.
В Обероне 2007 были какие-то подвижки на тему строк, сейчас не припомню.
Кстати, кто-нибудь знает какую-нибудь хорошую книгу или статью про эффективную работу со строками?