LastDelimiter — Функция Delphi


Содержание

страничка tripsin’а

Автор: Орехов Роман aka tripsin

Не изобретай лишних сутей

AdjustLineBreaks

function AdjustLineBreaks(const S: string [; Style: TTextLineBreakStyle]): string;

Вот так определена эта функция в SysUtils :

TTextLineBreakStyle = (tlbsLF, tlbsCRLF);

function AdjustLineBreaks(const S: string; Style: TTextLineBreakStyle =

(А в модуле ComCtrls.pas эта функция написана на ассемблере для работы с PChar :

function AdjustLineBreaks(Dest, Source: PChar): Integer; assembler; )

Функция AdjustLineBreaks приводит строку S к формату установленной OS, преобразуя символы перевода строки (одиночные символы возврата каретки (#13), перевода строки (#10), и пары CR-LF) применительно к данной ОС. Файлы, копируемые из систем Unix и Macintosh, содержат иные символы окончания строк, чем могут «ввести в заблуждение» многие программы DOS и Windows. (В Macintosh используется только символ возврата каретки; в Unix — только символ перевода строки.)

Второй необязательный параметр позволяет управлять форматированием. Ты можешь адаптировать переносы строк, как под Windows ( tlbsCRLF ), так и под Unix ( tlbsLF ). По умолчанию он определяется установленной ОС.

var UnixStr: String;

begin // Преобразуем текст под Unix

UnixStr := AdjustLineBreaks(Memo1.Text, tlbsLF);

Эта функция часто может тебе понадобиться, чтобы общаться с серверами в сети, т.к. серверы чаще всего работают по никсами (а ты конечно под Виндой :) ).

Для примера переделаем WhoIs -клиент из всеми любимой ДГХ (стр. 142 «Их разыскивают бойцы 139-го порта»). Там, чтобы отформатировать полученную от сервера строку, написан цикл, сканирующий строку на символ #10 и делящий текст на кусочки. Мы сделаем все это в одной строке (для надежности я добавил обработку ошибок):

procedure TForm1.btnFindClick(Sender: TObject);

FindResult := IdWhoIs.WhoIs(edtDomain.Text); // Получаем данные

// Обработка – всего одна строка

on E: Exception do

StringReplace

type TRepiaceFlags = set of (rfReplaceAll, rfIgnoreCase);

function StringReplace(const S, OldSubStr, NewSubStr: string; Flags: TReplaceFlags): string;

StringReplace возвращает копию S, где OldSubStr заменена на NewSubStr. Если Flags содержит rfReplaceAll, заменяются все вхождения OldSubStr; иначе заменяется только первое вхождение. Поиск OldSubStr выполняется с учетом регистра, если только не включен флаг rflgnoreCase.

Пример замены текста в TMemo :

procedure TForm1.Button1Click(Sender: TObject);

Memo1.Lines.Add(‘Зачем ты учишь Delphi?’);

procedure TForm1.Button2Click(Sender: TObject);

Memo1.Text := StringReplace(Memo1.Text, ‘ты учишь’, ‘он учит’, [rfReplaceAll, rfIgnoreCase]);

WrapText

function WrapText(const Line, BreakStr: string; BreakChars: TSysCharSet; MaxCol: Integer): string;overload;

function WrapText(const Line: string; MaxCol: Integer = 45): string;overload;

Функция WrapText возвращает копию Line, разбитую на несколько строк шириной MaxCol столбцов. Каждая строка разбивается, когда ее длина доходит до MaxCol символов. Разбиение производится там, где есть символы из множества BreakChars. При нахождении символа из BreakChars , после него вставляется строка BreakStr. (В Delphi 5 как существующие переводы строк рассматриваются символы #13 и #10, независимо от BreakStr.)
Вторая форма WrapText использует [‘ ‘, ‘ -‘ , #9] (пробел, дефис, табуляция) в качестве Break Chars и #13#10 (возврат каретки, перевод строки) в качестве BreakStr. Получается эта форма просто переносит строку по словам.

Сама Delphi к этой функции в своих модулях не обращается. Для разных компонентов пишутся аналоги с другими названиями. Я не додумался до сколько-нибудь полезного примера, т.к. например в TMemo и TRichEdit это свойство уже реализовано.

В моем примере TListBox заполняется строками, примерно, как в TMemo и ширина и количество строк меняются, при изменении размера TListBox . Также в примере есть алгоритм подгонки ширины строк к ширине окна:

procedure TForm1.FormResize(Sender: TObject);

CharsInWidth: Integer; //Сколько символов влезет в ширину окна

S := ‘Очень длинная строка, которую мы сейчас разбиваем на ‘;

S := S + ‘кусочки и заполняем ими строки TListBox по всей ширине. ‘;

S := S + ‘Ширину TListBox мы меняем, изменяя размеры формы, ‘;

S := S + ‘ т . к . у TListBox свойство Align равно alClient.’;

DC := GetWindowDC(ListBox1.Handle); // Получаем контекст окна

SelectObject(DC, ListBox1.Font.Handle);//Выбираем в него шрифт окна

OldMapMode := GetMapMode(DC);// Запоминаем старый режим отображения

SetMapMode(DC, MM_TEXT); //Устанавливаем отображение в пикселях

GetTextMetrics(DC, Metrics); //Получаем параметры шрифта

SetMapMode(DC, OldMapMode);//Восстанавливаем режим отображения

ReleaseDC(ListBox1.Handle, DC); //Освобождаем контекст

//Получаем количество средних символов на ширину окна (6 — подобрано)

ListBox1.Clear; // Очищаем TListBox

ListBox1.Items.Text := WrapText(S, CharsInWidth); // Заполняем TListBox

Caption := ‘ Количество строк в ListBox = ‘ + IntToStr(ListBox1.Count);

IsValidIdent

function IsValidIdent(const Ident: string): Boolean;

Функция IsValidIdent возвращает True, если Ident содержит корректный идентификатор Delphi Pascal, т. е. строку, первый символ которой является буквой или подчеркиванием: [‘A’..’Z’, ‘a’..’z’, ‘_’] ,а последующие символы — буквами, цифрами или символами подчеркивания: [‘A’..’Z’, ‘a’..’z’, ‘0..’9′, ‘_’].

Наверняка пригодится где-нибудь при разработке компонентов для проверки корректности установленных свойств. Например Delphi использует эту функцию при установке имени компонента TComponent . Name .

Хочется показать определение этой функции. Смотри какой простой и правильный код:

function IsValidIdent(const Ident: string): Boolean;

AlphaNumeric = Alpha + [‘0’..’9′];

for I := 2 to Length(Ident) do if not (Ident[I] in AlphaNumeric) then Exit;

LastDelimiter

function LastDelimiter(const Delimiters, S : string): Integer;

Функция LastDelimiter возвращает индекс последнего (самого правого) вхождения любого из символов Delimters в строке S. Если ни один из символов Delimiters не присутствует в S, LastDelimiter возвращает ноль. Delimiters не может быть многобайтовой строкой, и нельзя использовать #0 в качестве одного из разделителей.

Может, например, понадобиться для разбора пути к файлу на каталоги: от конечного к корню диска.

А я для примера написал функцию, которая выводит слова в предложении в обратном порядке:

function ReverseString(S: String): String;

delim := ‘ ‘; // разделитель — пробел

Result := »; // Обнуляем на всякий случай

// Получаем последний пробел

LastDelim := LastDelimiter(delim, S);

// Копируем в newstr из tmpstr слово после пробела + пробел

Result := Result + Copy(S, LastDelim + 1, Length(S)-LastDelim) + delim;

// Укорачиваем tmpstr на последнее слово

Delete(S,LastDelim, Length(S)-LastDelim + 1);

until LastDelim = 0; // Работаем пока не кончатся пробелы

Result := TrimRight(Result); // Удаляем лишний пробел на конце

procedure TForm1.FormCreate(Sender: TObject);

Edit1.Text := ‘Ехал грека через реку.’;

procedure TForm1.Button1Click(Sender: TObject);

При работе с PChar для тех же целей используется функция AnsiStrRScan :

function AnsiStrRScan(Str: PChar; Chr: Char): PChar;

Функция AnsiStrRScan выполняет поиск последнего (самого правого) вхождения символа Chr в Str и возвращает указатель на символ в строке Str или nil, если символ не найден.

AnsiQuotedStr и AnsiExtractQuotedStr

function AnsiQuotedStr(const S: string; Quote: Char): string;

Функция AnsiQuotedStr возвращает копию S, заключенную в кавычки. Символ кавычек указан в параметре Quote. Вхождения Quote в S повторяются и в результирующей строке. Эта функция предполагает, что в S нет символов #0 (кроме #0, находящегося за концом строки).

function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string;

Функция AnsiExtractQuotedStr выбирает из Src строку, заключенную в кавычки. Первый символ Src должен быть равен Quote, иначе функция сразу завершается, возвращая пустую строку. Если это условие выполняется, строка, заключенная в символы кавычек, копируется и возвращается в качестве результата функции. Повторяющиеся символы кавычек преобразуются в одиночные кавычки. Src изменяется, указывая теперь на символ сразу за закрывающей кавычкой. Если в Src нет закрывающей кавычки, то Src присваивается указатель на завершающий байт #0, и функция возвращает весь текст до конца строки.

Эти функции могут тебе понадобится при работе с файлами, при записи-чтении информации. Delphi использует эти функции при чтении-записи свойства TStrings . CommaText .

Если хочешь знать еще больше, то запускай Delphi , создавай проект по умолчанию и, удерживая кнопочку CTRL кликай мышкой по слову SysUtils в секции uses . Смещай взгляд свой в левую сторону окна с кодом. И будет там окно Code Explorer . И когда нажмешь ты на плюсик слева от папочки Procedures , то будет тебе счастье. Изучай.

LastDelimiter — Функция Delphi

Доброго дня!
Имеется некоторый текст, разделенный точкой с запятой, из коего нужно получить список. И все б было хорошо, но только TStringList зачем-то делит его не только по заданному делиметру но и по пробелам. Избежать этого можно предварительно закавычив нужные куски. Но, в свою очередь, в тексте могут быть кавычки в не предназначенных для этого местах (да уже, сволочи, попадаются -:( ). Вот собственно вопрос: можно как то заставить stringlist делимитрить текст исключительно по символу Delimiter?

<
Преобразование строки с разделителями в список строк.
Параметры:
Source — исходная строка.
Delimiter — строка-разделитель.
Parts — список строк, заполняемый функцией. Создается и уничтожается
вызывающей функцией.
>
procedure StrBreakApart(const Source, Delimeter: string; Parts: TStrings);
var
curPos: Integer;
curStr: string;
begin
Parts.Clear;
if Length(Source) = 0 then
Exit;
Parts.BeginUpdate;
try
CurStr:= Source;
repeat
CurPos:= AnsiPos(Delimeter, CurStr);
if CurPos > 0 then begin
Parts.Add(Copy(CurStr, 1, Pred(CurPos)));
CurStr:= Copy(CurStr, CurPos+Length(Delimeter),
Length(CurStr)-CurPos-Length(Delimeter)+1);
end else
Parts.Add(CurStr);
until CurPos=0;
finally
Parts.EndUpdate;
end;
end;

> [1] Игорь Шевченко © (03.10.07 12:10)

Это конечно хорошо, но можно ли сделать сабж именно с помощью TStringList?

PS
Мне тоже интересно&#133


> можно как то заставить stringlist делимитрить текст исключительно
> по символу Delimiter?
>

Но можно на время работы этого сплит-метода заменить пробелы, например, на символ с кодом #255.

> Это конечно хорошо, но можно ли сделать сабж именно с помощью
> TStringList?

Нет. Достаточно взглянуть на код
procedure TStrings.SetDelimitedText(const Value: string);
где принпдлежность символа к текущему «слову» определяется так:
while (P^ > » «) and (P^ <> Delimiter) do

Какая версия Delphi? В новых версиях (2006 и выше) у TStringList вроде как есть булевское свойство StrongDelimiter или что-то типа того, и если оно равно true, пробелы за разделитель не считаются

> Кевларвестов Семен (03.10.2007 12:07:00) [0]

When assigning CommaText, the value is parsed as SDF formatted text. For SDF format, strings are separated by commas or spaces, and optionally enclosed in double quotes. Double quote marks that are part of the string are repeated to distinguish them from the quotes that surround the string. Spaces and commas that are not contained within double quote marks are delimiters. Two commas next to each other will indicate an empty string, but spaces that appear next to another delimiter are ignored

> Anatoly Podgoretsky © (03.10.07 12:26)

Вот, я в ситуациях как в сабже сначала преобразую строку так:

asdsd«sad;asd;

Determines how the Delimiter property is used.
Class
TStrings

[Delphi] property StrictDelimiter: Boolean read GetStrictDelimiter write SetStrictDelimiter;

Description
Use this property to specify whether the Delimiter is the only value used within the DelimitedText property. If set to True, individual strings in DelimitedText are separated only by the character that is the value of Delimiter. If set to False, individual strings in DelimitedText can be separated by a space, a non-printable character, or the character that is the value of Delimiter.

Kolan © (03.10.07 12:14) [2]


> Это конечно хорошо, но можно ли сделать сабж именно с помощью
> TStringList?

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

StrBreakApart(«text
more text
once again more text», «
«, Parts);


> Это конечно хорошо, но можно ли сделать сабж именно с помощью
> TStringList?

А что где-то было заявлено, что TStringList обладает искусственным интеллектом?

LastDelimiter — Функция Delphi

Описание
Функция возвращает индекс последнего символа-разделителя в строке S. Символы-разделители определяются параметром Delimiters. Данная функция поддерживает работу с многобайтовыми наборами символов (MBCS), поэтому строка S может содержать двухбайтовые символы. Список Delimiters, должен состоять только из однобайтных не пустых символов.

Пример
var
I: Integer;
begin
I:= LastDelimiter(‘!;.,-‘, ‘Казнить, нельзя, помиловать’);
// I := 16
end;

LastDelimiter Routine

Description

(Please provide a description in your own words. It is illegal to use the wording from the Delphi Help.)

Technical Comments

(Known issues / Documentation clarifications / Things to be aware of)

Examples

(Please provide links to articles/source code that show how to use this item.)

See Also

(Please provide links to items specifically related to this item.)

User Comments/Tips

(Please leave your name with your comment.)

Программирование на языке Delphi. Глава 2. Основы языка Delphi. Часть 3

Оглавление


Программные модули


Структура модуля

Логически обособленные группы процедур и функций чрезвычайно удобно объединять в специализированные библиотеки — модули. Для этого язык Delphi предлагает специальные средства и доступную каждому технологию. Приведем общую структуру программного модуля:

После слова unit записывается имя модуля . Оно должно совпадать с именем файла, в котором находится исходный текст модуля. Например, если файл называется MathLib.pas, то модуль должен иметь имя MathLib. Заголовок модуля формируется автоматически при сохранении файла на диске, поэтому его не следует изменять вручную. Чтобы дать модулю другой заголовок, просто сохраните его на диске под другим именем.

В разделе interface описываются глобальные данные , процедуры и функции, доступные для использования в основной программе и других модулях.

В разделе implementation реализуется программный код глобальных процедур и функций и описываются локальные данные , процедуры и функции, недоступные основной программе и другим модулям.

Блок initialization является необязательным. Он состоит из операторов и выполняется автоматически непосредственно перед запуском основной программы. Блоки инициализации подключенных к программе модулей выполняются в том порядке, в котором они упоминаются в секции uses .

Блок finalization тоже является необязательным. Он состоит из операторов и выполняется автоматически непосредственно после завершения основной программы. Блоки завершения подключенных к программе модулей выполняются в порядке, обратном порядку подключения модулей в секции uses .

Если модуль не нуждается в инициализации и завершении, блоки initialization и finalization можно опустить.

В качестве упражнения давайте создадим модуль и подключим его к основной программе (для этого сначала запустите среду Delphi):


    Выберите в главном меню команду File / New. , в появившемся диалоговом окне активизируйте значок с подписью Unit и щелкните на кнопке OK (рисунок 6).

Рисунок 6. Окно среды Delphi для создания нового модуля

Вы увидите, что среда Delphi создаст в редакторе кода новую страницу с текстом нового модуля Unit1 (рисунок 7):

Рисунок 7. Текст нового модуля в редакторе кода

  • Сохраните модуль под именем MathLib, выбрав в меню команду File / Save (рисунок 8):
  • Рисунок 8. Окно сохранения модуля

    Заметьте, что основная программа Console изменилась: в списке подключаемых модулей появилось имя модуля MathLib (рисунок 9). После слова in среда Delphi автоматически помещает имя файла, в котором находится модуль. Для стандартных модулей, таких как SysUtils, это не нужно, поскольку их местонахождение хорошо известно.

    Рисунок 9. Текст программы Console в окне редактора

    Теперь перейдем к содержимому модуля. Давайте объявим в нем константу Pi и две функции: Power — вычисление степени числа, и Average — вычисление среднего арифметического двух чисел:

    Вот как могла бы выглядеть программа, использующая модуль Math:

    После компиляции и запуска программы вы увидите на экране три числа (рисунок 10):

    Рисунок 10. Результат работы программы Console

    Стандартные модули языка Delphi

    В состав среды Delphi входит великолепный набор модулей, возможности которых удовлетворят даже самого привередливого программиста. Все модули можно разбить на две группы: системные модули и модули визуальных компонентов.

    К системным модулям относятся System, SysUtils, ShareMem, Math. В них содержатся наиболее часто используемые в программах типы данных, константы, переменные, процедуры и функции. Модуль System — это сердце среды Delphi; содержащиеся в нем подпрограммы обеспечивают работу всех остальных модулей системы. Модуль System подсоединяется автоматически к каждой программе и его не надо указывать в операторе uses .

    Модули визуальных компонентов (VCL — Visual Component Library) используются для визуальной разработки полнофункциональных GUI-приложений — приложений с графическим пользовательским интерфейсом (Graphical User Interface). Эти модули в совокупности представляют собой высокоуровневую объектно-ориентированную библиотеку со всевозможными элементами пользовательского интерфейса: кнопками, надписями, меню, панелями и т.д. Кроме того, модули этой библиотеки содержат простые и эффективные средства доступа к базам данных. Данные модули подключаются автоматически при помещении компонентов на форму, поэтому вам об этом заботиться не надо. Их список слишком велик, поэтому мы его не приводим.

    Все основные модули среды Delphi, включая модули визуальных компонентов, поставляются вместе с их исходными текстами на языке Delphi. По мере роста вашего профессионального опыта мы рекомендуем чаще обращаться к этим исходным текстам. Во-первых, в них вы найдете ответы на многие вопросы о внутреннем устройстве среды Delphi, а во-вторых, они послужат образцовым примером профессионального подхода в решении широкого круга задач. И, в-третьих, что не менее важно, это поможет научиться красиво и правильно (в рамках устоявшегося стиля) оформлять тексты Ваших собственных программ так, чтобы их с легкостью читали и понимали другие программисты.

    Исходные тексты стандартных модулей среды Delphi находятся в каталоге Delphi/Source.

    Область действия идентификаторов

    При программировании необходимо соблюдать ряд правил, регламентирующих использование идентификаторов:

    • каждый идентификатор должен быть описан перед тем, как он будет использован;
    • областью действия идентификатора является блок, в котором он описан;
    • все идентификаторы в блоке должны быть уникальными, т.е. не повторяться;
    • один и тот же идентификатор может быть по-разному определен в каждом отдельном блоке, при этом блоки могут быть вложенными;
    • если один и тот же идентификатор определен в нескольких вложенных блоках, то в пределах вложенного блока действует вложенное описание;
    • все глобальные описания подключенного модуля видны программе (подключающему модулю), как если бы они были сделаны в точке подключения;
    • если подключаются несколько модулей, в которых по-разному определен один и тот же идентификатор, то определение, сделанное в последнем подключенном модуле перекрывает все остальные;
    • если один и тот же идентификатор определен и в подключенном модуле, и в программе (подключающем модуле), то первый игнорируется, а используется идентификатор, определенный в программе (подключающем модуле). Доступ к идентификатору подключенного модуля возможен с помощью уточненного имени. Уточненное имя формируется из имени модуля и записанного через точку идентификатора. Например, чтобы в предыдущем примере получить доступ к стандартному значению числа ?, нужно записать System.Pi.

    Строки


    Строковые значения

    Строка — это последовательность символов. При программировании строковые значения заключаются в апострофы, например:

    Так как апостроф является служебным символом, для его записи в строке как значащего символа применяются два апострофа, следующих непосредственно друг за другом:

    Для записи отсутствующих на клавиатуре символов используется символ # , за которым следует десятичный номер символа в кодовой таблице ASCII, например:

    Строка, которая не содержит символов, называется пустой:

    Теперь, когда известно, что представляют собой строковые значения, займемся строковыми переменными.

    Строковые переменные

    Строковая переменная объявляется с помощью зарезервированного слова string или с помощью идентификатора типа данных AnsiString, например:

    Строку можно считать бесконечной, хотя на самом деле ее длина ограничена 2 ГБ. В зависимости от присваиваемого значения строка увеличивается и сокращается динамически. Это удобство обеспечивается тем, что физически строковая переменная хранит не сами символы, а адрес символов строки в области динамически распределяемой памяти (о динамически распределяемой памяти мы расскажем ниже). При создании строки всегда инициализируются пустым значением (»). Управление динамической памятью при операциях со строками выполняется автоматически с помощью стандартных библиотек языка Delphi.

    Вы конечно же можете описывать строковые типы данных и использовать их при объявлении переменных и типизированных констант, например:

    Символы строки индексируются от 1 до N+1, где N — реальная длина строки. Символ с индексом N+1 всегда равен нулю (#0). Для получения длины следует использовать функцию Length , а для изменения длины — процедуру SetLength (см. ниже).

    Для того чтобы в программе обратиться к отдельному символу строки, нужно сразу за идентификатором строковой переменной или константы в квадратных скобках записать его номер. Например, FriendName[1] возвращает значение ‘A’, а FriendName[4] — ‘x’. Символы, получаемые в результате индексирования строки, принадлежат типу Char.

    Достоинство строки языка Delphi состоит в том, что она объединяет в себе свойства строки самого языка Delphi и строки языка C. Оперируя строкой, вы оперируете значением строки, а не адресом в оперативной памяти. В то же время строка не ограничена по длине и может передаваться вместо C-строки (как адрес первого символа строки) в параметрах процедур и функций. Чтобы компилятор позволил это сделать, нужно, записывая строку в качестве параметра, преобразовать ее к типу PChar (тип данных, используемый в языке Delphi для описания нуль-терминированных строк языка C). Такое приведение типа допустимо по той причине, что строка всегда завершается нулевым символом (#0), который хоть и не является ее частью, тем не менее всегда дописывается сразу за последним символом строки. В результате формат строки удовлетворяет формату C-строки. О работе с нуль-терминированными строками мы поговорим чуть позже.

    Строки в формате Unicode

    Для поддержки работы со строками формата Unicode в язык Delphi имеется строковый тип данных WideString. Работа со строками типа WideString почти не отличается от работы со строками типа AnsiString; существуют лишь два отличия.

    Первое отличие состоит в представлении символов. В строках типа WideString каждый символ кодируется не одним байтом, а двумя. Соответственно элементы строки WideString — это символы типа WideChar, тогда как элементы строки AnsiString — это символы типа AnsiChar.

    Второе отличие состоит в том, что происходит при присваивании строковых переменных. Об этом вы узнаете чуть позже, прочитав параграф «Представление строк в памяти».

    Короткие строки

    Короткая строка объявляется с помощью идентификатора типа ShortString или зарезервированного слова string , за которым следует заключенное в квадратные скобки значение максимально допустимой длины, например:

    Короткая строка может иметь длину от 1 до 255 символов. Предопределенный тип данных ShortString эквивалентен объявлению string [255].

    Реальная длина строки может быть меньше или равна той, что указана при ее объявлении. Например, максимальная длина строки Friend в примере выше составляет 30 символов, а ее реальная длина — 9 символов. Реальную длину строки можно узнать с помощью встроенной функции Length . Например, значение Length(Friend) будет равно 9 (количество букв в слове Alexander).

    Все символы в строке типа ShortString пронумерованы от 0 до N, где N — максимальная длина, указанная при объявлении. Символ с номером 0 — это служебный байт, в нем содержится реальная длина короткой строки. Значащие символы нумеруются от 1. Очевидно, что в памяти строка занимает на 1 байт больше, чем ее максимальная длина. Поэтому значение SizeOf(Friend) будет равно 31.

    Обратиться к отдельному символу можно так же, как и к символу обычной строки. Например, выражения FriendName[1] и FriendName[9] возвращают соответственно символы ‘A’ и ‘r’. Значения FriendName[10] .. FriendName[30] будут случайными, так как при объявлении типизированной константы FriendName символы с номерами от 10 до 30 не были инициализированы. Символы, получаемые в результате индексирования короткой строки, принадлежат типу Char.

    Поскольку существует два типа строк: обычные (длинные) строки и короткие строки, возникает закономерный вопрос, можно ли их совмещать. Да, можно! Короткие и длинные строки могут одновременно использоваться в одном выражении, поскольку компилятор языка Delphi автоматически генерирует код, преобразующий их тип. Более того, можно выполнять явные преобразования строк с помощью конструкций вида ShortString(S) и AnsiString(S).

    Операции над строками

    Выражения, в которых операндами служат строковые данные, называются строковыми . Они состоят из строковых констант, переменных, имен функций и строковых операций. Над строковыми данными допустимы операции сцепления и отношения.

    Операция сцепления (+) применяется для сцепления нескольких строк в одну строку.

    Выражение


    Результат


    ‘Object’ + ‘ Pascal’ ‘Object Pascal’

    Операции отношения (=, <>, >, =, ‘ABCDE’ True ‘Office’ = ‘Office’ True ‘USIS’ > ‘US’ True

    Если короткой строке присваивается значение, длина которого превышает максимально допустимую величину, то все лишние символы справа отбрасываются.

    Объявление строки


    Выражение


    Значение строки


    Name: string[6]; Name := ‘Mark Twain’; ‘Mark T’

    Допускается смешение в одном выражении операндов строкового и символьного типа, например при сцеплении строки и символа.

    Строковые ресурсы

    В языке Delphi существует специальный вид строковых данных — строковые ресурсы. Строковые ресурсы очень похожи на строковые константы, но отличаются от них тем, что размещаются не в области данных программы, а в специальной области выполняемого файла, называемой ресурсами. Если данные всегда загружаются вместе с кодом программы и остаются в оперативной памяти вплоть до завершения программы, то ресурсы подгружаются в оперативную память лишь по мере надобности.

    В программе строковые ресурсы описываются как обычные строковые константы, с той лишь разницей что раздел их описания начинается не словом const , а словом resourcestring :

    Использование строковых ресурсов ничем не отличается от использования строковых констант:

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

    Форматы кодирования символов

    Существуют различные форматы кодирования символов. Отдельный символ строки может быть представлен в памяти одним байтом (стандарт Ansi), двумя байтам (стандарт Unicode) и даже четырьмя байтами (стандарт UCS-4 — Unicode). Строка «Wirth» (фамилия автора языка Pascal — прародителя языка Delphi) будет представлена в указанных форматах следующим образом (рисунок 11):

    Рисунок 11. Форматы кодирования символов

    Существует также формат кодирования MBCS (Multibyte Character Set), согласно которому символы одной строки кодируются разным количеством байт (одним или двумя байтами в зависимости от алфавита). Например, буквы латинского алфавита кодируются одним байтом, а иероглифы японского алфавита — двумя. При этом латинские буквы и японские иероглифы могут встречаться в одной и той же строке.

    Стандартные процедуры и функции для работы со строками

    Так как обработка строк выполняется практически в каждой серьезной программе, стандартно подключаемый модуль System имеет набор процедур и функций, значительно облегчающих этот процесс. Все следующие процедуры и функции применимы и к коротким, и к длинным строкам.

    • Concat (S1, S2, . , Sn): string — возвращает строку, полученную в результате сцепления строк S1, S2, . Sn. По своей работе функция Concat аналогична операции сцепления (+).
    • Copy (S: string, Index, Count: Integer): string — выделяет из строки S подстроку длиной Count символов, начиная с позиции Index.
    • Delete (var S: string, Index, Count: Integer) — удаляет Count символов из строки S, начиная с позиции Index.
    • Insert (Source: string; var S: string, Index: Integer) — вставляет строку Source в строку S, начиная с позиции Index.
    • Length (S: string): Integer — возвращает реальную длину строки S в символах.
    • SetLength (var S: string; NewLength: Integer) — устанавливает для строки S новую длину NewLength.

    Выражение


    Значение S


    S := Concat(‘Object ‘, ‘Pascal’);‘Object Pascal’S:= Copy(‘Debugger’, 3, 3);‘bug’S := ‘Compile’; Delete(S, 1, 3);‘pile’S := ‘Faction’; Insert(‘r’, S, 2)‘Fraction’

    • Pos (Substr, S: string): Byte — обнаруживает первое появление подстроки Substr в строке S. Возвращает номер той позиции, где находится первый символ подстроки Substr. Если в S подстроки Substr не найдено, результат равен 0.

    Выражение


    Результат


    Pos(‘rat’, ‘grated’)2Pos(‘sh’, ‘champagne’)

    • Str (X [: Width [: Decimals] ], var S: string) — преобразует числовое значение величины X в строку S. Необязательные параметры Width и Decimals являются целочисленными выражениями. Значение Width задает ширину поля результирующей строки. Значение Decimals используется с вещественными числами и задает количество символов в дробной части.

    Выражение


    Значение S


    Str(-200, S);‘-200’Str(200 : 4, S);‘ 200’Str(1.5E+02 : 4, S);‘ 150’

    • Val (S: string, var V; var Code: Integer) — преобразует строку S в величину целого или вещественного типа и помещает результат в переменную V. Если во время операции преобразования ошибки не обнаружено, значение переменной Code равно нулю; если ошибка обнаружена (строка содержит недопустимые символы), Code содержит номер позиции первого ошибочного символа, а значение V не определено.

    Выражение


    Значение V


    Значение Code


    Val(‘100’, V, Code); 100 Val(‘2.5E+01’, V, Code); 25.0 Val(‘2.5A+01’, V, Code); 4

    Описанные процедуры и функции являются базовыми для всех остальных подпрограмм обработки строк из модуля SysUtils.

    • AdjustLineBreaks (const S: string): string — возвращает копию строки S, в которой все мягкие переносы строк (одиночные символы #13 или #10) заменены жесткими переносами строк (последовательность символов #13#10).
    • AnsiCompareStr (const S1, S2: string): Integer — сравнивает две строки, делая различие между заглавными и строчными буквами; учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • AnsiCompareText (const S1, S2: string): Integer — сравнивает две строки, не делая различий между заглавными и строчными буквами; учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • AnsiDequotedStr (const S: string; Quote: Char): string — удаляет специальный символ, заданный параметром Quote, из начала и конца строки и заменяет парные спецсимволы на одиночные; если специальный символ отсутствует в начале или конце строки, то функция возвращает исходную строку без изменений.
    • AnsiExtractQuotedStr (var Src: PChar; Quote: Char): string — делает то же, что и функция AnsiDequotedStr, но результат возвращается вместо исходной строки, которая имеет тип PChar.
    • AnsiLowerCase (const S: string): string — преобразует заглавные буквы строки S к строчным буквам с учетом местного языка.
    • AnsiPos (const Substr, S: string): Integer — выполняет те же действия, что и функция Pos, но в отличие от нее поддерживает работу с многобайтовой MBCS-кодировкой.
    • AnsiQuotedStr (const S: string; Quote: Char): string — преобразует строку, заменяя все вхождения специального символа, заданного параметром Quote, на парные спецсимволы, а также помещает специальный символ в начало и конец строки. Поддерживает работу с MBCS-кодировкой.
    • AnsiSameCaption (const Text1, Text2: string): Boolean — сравнивает две строки, не делая различие между заглавными и строчными буквами, а также не учитывая символ ‘&’; учитывает местный язык.
    • AnsiSameStr (const S1, S2: string): Boolean — сравнивает строки, делая различие между строчными и заглавными буквами; учитывает местный язык.
    • AnsiSameText (const S1, S2: string): Boolean — сравнивает строки, не делая различие между строчными и заглавными буквами; учитывает местный язык.
    • AnsiUpperCase (const S: string): string — преобразует все строчные буквы в заглавные; учитывает местный язык.
    • CompareStr (const S1, S2: string): Integer — выполняет сравнение двух строк, делая различие между строчными и заглавными буквами; не учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • CompareText (const S1, S2: string): Integer — выполняет сравнение двух строк, не делая различий между строчными и заглавными буквами; не учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • DateTimeToStr (const DateTime: TDateTime): string — преобразует значение даты и времени в строку.
    • DateTimeToString (var Result: string; const Format: string; DateTime: TDateTime) — преобразует значение даты и времени в строку, выполняя при этом форматирование в соответствии со значением строки Format. Управляющие символы строки Format подробно описаны в справочнике по среде Delphi.
    • DateToStr (const DateTime: TDateTime): string — преобразует числовое значение даты в строку.
    • Format (const Format: string; const Args: array of const): string — форматирует строку в соответствии с шаблоном Format, заменяя управляющие символы шаблона на значения элементов открытого массива Args. Управляющие символы подробно описаны в справочнике по среде Delphi.
    • FormatDateTime (const Format: string; DateTime: TDateTime): string — преобразует значение даты и времени в строку, выполняя при этом форматирование в соответствии со значением строки Format. Управляющие символы строки Format подробно описаны в справочнике по среде Delphi.
    • BoolToStr (B: Boolean; UseBoolStrs: Boolean = False): string — преобразует булевское значение в строку. Если параметр UseBoolStrs имеет значение False, то результатом работы функции является одно из значений ‘0’ или ‘-1’. Если же параметр UseBoolStrs имеет значение True, то результатом работы является одно из значений ‘FALSE’ или ‘TRUE’ (программист может задать другие значения; о том, как это сделать, читайте в справочнике по системе Delphi).
    • IntToHex (Value: Integer; Digits: Integer): string — возвращает шестнадцатиричное представление целого числа Value. Параметр Digits задает количество цифр результирующей строки.
    • IntToStr (Value: Integer): string — преобразует целое число Value в строку.
    • IsDelimiter (const Delimiters, S: string; Index: Integer): Boolean — проверяет, является ли символ S[Index] одним из символов строки Delimiters. Функция поддерживает работу с многобайтовой MBCS-кодировкой.
    • IsValidIdent (const Ident: string): Boolean — возвращает True, если строка Ident является правильным идентификатором языка Delphi.
    • LastDelimiter (const Delimiters, S: string): Integer — возвращает индекс последнего вхождения одного из символов строки Delimiters в строку S.
    • LowerCase (const S: string): string — преобразует все заглавные буквы строки S к строчным; не учитывает местный язык (в преобразовании участвуют лишь символы в диапазоне от ‘A’ до ‘Z’).
    • QuotedStr (const S: string): string — преобразует исходную строку в строку, взятую в одиночные кавычки; внутри строки символы кавычки дублируются.
    • SameText (const S1, S2: string): Boolean — сравнивает строки, не делая различие между строчными и заглавными буквами; учитывает местный язык.
    • SetString (var S: string; Buffer: PChar; Len: Integer) — копирует строку с типом PChar в строку с типом string. Длина копируемой строки задается параметром Len.
    • StringOfChar (Ch: Char; Count: Integer): string — возвращает строку, в которой повторяется один и тот же символ. Количество повторений задается параметром Count.
    • StringToGUID (const S: string): TGUID — преобразует строковое представление глобального уникального идентификатора в стандартный тип TGUID.
    • StrToBool (const S: string): Boolean — преобразует строку в булевское значение.
    • StrToBoolDef (const S: string; const Default: Boolean): Boolean — преобразует строку в булевское значение. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
    • StrToDate (const S: string): TDateTime — преобразует строку со значением даты в числовой формат даты и времени.
    • StrToDateDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку со значением даты в числовой формат даты и времени. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
    • StrToDateTime (const S: string): TDateTime — преобразует строку в числовое значение даты и времени.
    • StrToDateTimeDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку в числовое значение даты и времени. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
    • StrToInt (const S: string): Integer — преобразует строку в целое число. Если строка не может быть преобразована в целое число, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
    • StrToIntDef (const S: string; Default: Integer): Integer — преобразует строку в целое число. Если строка не может быть преобразована в целое число, функция возвращает значение, заданное параметром Default.
    • StrToInt64 (const S: string): Int64 — 64-битный аналог функции StrToInt — преобразует строку в 64-битное целое число. Если строка не может быть преобразована в 64-битное число, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
    • StrToInt64Def (const S: string; const Default: Int64): Int64 — 64-битный аналог функции StrToIntDef — преобразует строку в 64-битное целое число. Если строка не может быть преобразована в 64-битное число, функция возвращает значение, заданное параметром Default.
    • StrToTime (const S: string): TDateTime — преобразует строку в числовой формат времени. Если строка не может быть преобразована в числовой формат времени, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
    • StrToTimeDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку в числовой формат времени. В случае ошибки преобразования, функция возвращает значение, заданное параметром Default.
    • TimeToStr (Time: TDateTime): string — преобразует числовое значение времени в строку.
    • Trim (const S: string): string — возвращает часть строки S без лидирующих и завершающих пробелов и управляющих символов.
    • Trim (const S: WideString): WideString — Unicode-аналог функции Trim — возвращает часть строки S без лидирующих и завершающих пробелов и управляющих символов.
    • TrimLeft (const S: string): string — возвращает часть строки S без лидирующих пробелов и управляющих символов.
    • TrimLeft const S: WideString): WideString — Unicode-аналог функции TrimLeft — возвращает часть строки S без лидирующих пробелов и управляющих символов.
    • TrimRight (const S: string): string — возвращает часть строки S без завершающих пробелов и управляющих символов.
    • TrimRight (const S: WideString): WideString — Unicode-аналог функции TrimRight — возвращает часть строки S без завершающих пробелов и управляющих символов.
    • UpperCase (const S: string): string — преобразует все строчные буквы строки S в заглавные; не учитывает местный язык (в преобразовании участвуют лишь символы в диапазоне от ‘a’ до ‘z’).
    • WideFormat (const Format: WideString; const Args: array of const): WideString — Unicode-аналог функции Format, учитывающий символы местного языка, — форматирует строку в соответствии с шаблоном Format, заменяя управляющие символы в шаблоне на значения элементов открытого массива Args. Управляющие символы подробно описаны в справочнике по системе Delphi.
    • WideFmtStr (var Result: WideString; const Format: WideString; const Args: array of const) — аналог функции WideFormat. Отличие в том, что WideFmtStr возвращает результат через параметр Result, а не как значение функции.
    • WideLowerCase const S: WideString): WideString — Unicode-аналог функции LowerCase (учитывает местный язык) — преобразует все заглавные буквы строки S к строчным буквам.
    • WideSameCaption (const Text1, Text2: WideString): Boolean — Unicode-аналог функции AnsiSameCaption — сравнивает две строки, не делая различие между строчными и заглавными буквами, а также не учитывая символ ‘&’; учитывает местный язык.
    • WideSameStr (const S1, S2: WideString): Boolean — Unicode-аналог стандартной операции сравнения строк — сравнивает две строки, делая различие между строчными и заглавными буквами.
    • WideSameText (const S1, S2: WideString): Boolean — Unicode-аналог функции SameText (учитывает местный язык) — сравнивает строки, не делая различие между строчными и заглавными буквами.
    • WideUpperCase (const S: WideString): WideString — Unicode-аналог функции UpperCase (учитывает местный язык) — преобразует все строчные буквы строки S в заглавные.
    • WrapText (const Line: string; MaxCol: Integer = 45): string — разбивает текст Line на строки, вставляя символы переноса строки. Максимальная длина отдельной строки задается параметром MaxCol.
    • WrapText (const Line, BreakStr: string; const BreakChars: TSysCharSet; MaxCol: Integer): string — более мощный аналог предыдущей функции — разбивает текст Line на строки, вставляя символы переноса строки.
    • AnsiToUtf8 (const S: string): UTF8String — перекодирует строку в формат UTF8.
    • PUCS4Chars (const S: UCS4String): PUCS4Char — возвращает указатель на первый символ строки формата UCS-4 для работы со строкой, как с последовательностью символов, заканчивающейся символом с кодом нуль.
    • StringToWideChar (const Source: string; Dest: PWideChar; DestSize: Integer): PWideChar — преобразует стандартную строку к последовательности Unicode-символов, завершающейся символом с кодом нуль.
    • UCS4StringToWideString (const S: UCS4String): WideString — преобразует строку формата UCS-4 к строке формата Unicode.
    • Utf8Decode (const S: UTF8String): WideString — преобразует строку формата UTF-8 к строке формата Unicode.
    • Utf8Encode (const WS: WideString): UTF8String — преобразует строку формата Unicode к строке формата UTF-8.
    • Utf8ToAnsi (const S: UTF8String): string — преобразует строку формата UTF-8 к стандратной строке.
    • WideCharLenToString (Source: PWideChar; SourceLen: Integer): string — преобразует строку формата Unicode к стандартной строке. Длина исходной строки задается параметром SourceLen.
    • WideCharLenToStrVar (Source: PWideChar; SourceLen: Integer; var Dest: string) — аналог предыдущей функции — преобразует строку формата Unicode к стандартной строке. Длина исходной строки задается параметром SourceLen, а результат возвращается через параметр Dest.
    • WideCharToString (Source: PWideChar): string — преобразует последовательность Unicode-символов, завершающуюся символом с кодом нуль, к стандартной строке.
    • WideCharToStrVar (Source: PWideChar; var Dest: string) — аналог предыдущей функции — преобразует последовательность Unicode-символов, завершающуюся символом с кодом нуль, к стандартной строке. Результат возвращается через параметр Dest.
    • WideStringToUCS4String (const S: WideString): UCS4String — преобразует строку формата Unicode к строке формата UCS-4.

    Массивы


    Объявление массива

    Массив — это составной тип данных, состоящий из фиксированного числа элементов одного и того же типа. Для описания массива предназначено словосочетание array of . После слова array в квадратных скобках записываются границы массива, а после слова of — тип элементов массива, например:

    После описания типа можно переходить к определению переменных и типизированных констант:

    Обратите внимание, что инициализация элементов массива происходит в круглых скобках через запятую.

    Массив может быть определен и без описания типа:

    Чтобы получить доступ к отдельному элементу массива, нужно в квадратных скобках указать его индекс, например

    Объявленные выше массивы являются одномерными, так как имеют только один индекс. Одномерные массивы обычно используются для представления линейной последовательности элементов. Если при описании массива задано два индекса, массив называется двумерным , если n индексов — n-мерным . Двумерные массивы используются для представления таблицы, а n-мерные — для представления пространств. Вот пример объявления таблицы, состоящей из 5 колонок и 20 строк:

    То же самое можно записать в более компактном виде:

    Чтобы получить доступ к отдельному элементу многомерного массива, нужно указать значение каждого индекса, например

    или в более компактной записи

    Эти два способа индексации эквивалентны.

    Работа с массивами

    Массивы в целом участвуют только в операциях присваивания. При этом все элементы одного массива копируются в другой. Например, если объявлены два массива A и B,

    то допустим следующий оператор:

    Оба массива-операнда в левой и правой части оператора присваивания должны быть не просто идентичны по структуре, а описаны с одним и тем же типом, иначе компилятор сообщит об ошибке. Именно поэтому все массивы рекомендуется описывать в секции type .

    С элементами массива можно работать, как с обычными переменными. В следующей программе элементы численного массива последовательно вводятся с клавиатуры, а затем суммируются. Результат выводится на экран.

    Для массивов определены две встроенные функции — Low и High. Они получают в качестве своего аргумента имя массива. Функция Low возвращает нижнюю, а High — верхнюю границу этого массива. Например, Low(A) вернет значение 1, а High(A) — 5. Функции Low и High чаще всего используются для указания начального и конечного значений в операторе цикла for . Поэтому вычисление суммы элементов массива A лучше переписать так:

    В операциях с многомерными массивами циклы for вкладываются друг в друга. Например, для инициализации элементов таблицы, объявленной как

    требуются два вложенных цикла for и две целые переменные Col и Row для параметров этих циклов:

    Массивы в параметрах процедур и функций

    Массивы, как и другие типы данных, могут выступать в качестве параметров процедур и функций. Вот как может выглядеть функция, вычисляющая среднее значение в массиве действительных чисел:

    Функция Average принимает в качестве параметра массив известной размерности. Требование фиксированного размера для массива-параметра часто является чрезмерно сдерживающим фактором. Процедура для нахождения среднего значения должна быть способна работать с массивами произвольной длины. Для этой цели в язык Delphi введены открытые массивы-параметры. Такие массивы были заимствованы разработчиками языка Delphi из языка Modula-2. Открытый массив-параметр описывается с помощью словосочетания array of , при этом границы массива опускаются:

    Внутри подпрограммы Average нижняя граница открытого массива A равна нулю (Low(A) = 0), а вот значение верхней границы (High(A)) неизвестно и выясняется только на этапе выполнения программы.

    Существует только два способа использования открытых массивов: обращение к элементам массива и передача массива другой подпрограмме, принимающей открытый массив. Нельзя присваивать один открытый массив другому, потому что их размеры заранее неизвестны.

    Вот пример использования функции Average:

    Заметьте, что во втором операторе открытый массив конструируется в момент вызова функции Average. Конструктор открытого массива представляет собой заключенный в квадратные скобки список выражений. В выражениях могут использоваться константы, переменные и функции. Тип выражений должен быть совместим с типом элементов массива. Конструирование открытого массива равносильно созданию и инициализации временной переменной.

    И еще одно важное замечание по поводу открытых массивов. Некоторые библиотечные подпрограммы языка Delphi принимают параметры типа array of const — открытые массивы констант . Массив, передаваемый в качестве такого параметра, обязательно конструируется в момент вызова подпрограммы и может состоять из элементов различных типов (!). Физически он состоит из записей типа TVarRec , кодирующих тип и значение элементов массива (записи рассматриваются ниже). Открытый массив констант позволяет эмулировать подпрограммы с переменным количеством разнотипных параметров и используется, например, в функции Format для форматирования строки (см. выше).

    Уплотнение структурных данных в памяти

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

    Ключевое слово packed указывает компилятору, что элементы структурного типа должны храниться плотно прижатыми друг к другу, даже если это замедляет к ним доступ. Если структурный тип данных описан без ключевого слова packed , компилятор выравнивает его элементы на 2- и 4-байтовых границах, чтобы ускорить к ним доступ.

    Заметим, что ключевое слово packed применимо к любому структурному типу данных, т.е. массиву, множеству, записи, файлу, классу, ссылке на класс.

    Множества


    Объявление множества

    Множество — это составной тип данных для представления набора некоторых элементов как единого целого. Область значений множества — набор всевозможных подмножеств, составленных из его элементов. Все элементы множества должны принадлежать однобайтовому порядковому типу. Этот тип называется базовым типом множества .

    Для описания множественного типа используется словосочетание set of , после которого записывается базовый тип множества:

    Теперь можно объявить переменную множественного типа:

    Можно объявить множество и без предварительного описания типа:

    В выражениях значения элементов множества указываются в квадратных скобках: [2, 3, 5, 7], [1..9], [‘A’, ‘B’, ‘C’]. Если множество не имеет элементов, оно называется пустым и обозначается как [ ]. Пример инициализации множеств:

    Количество элементов множества называется мощностью . Мощность множества в языке Delphi не может превышать 256.

    Операции над множествами

    При работе с множествами допускается использование операций отношения (=, <>, >=, in .

    Операции сравнения (=, <>). Два множества считаются равными, если они состоят из одних и тех же элементов. Порядок следования элементов в сравниваемых множествах значения не имеет. Два множества A и B считаются не равными, если они отличаются по мощности или по значению хотя бы одного элемента.

    Выражение


    Результат


    [1, 2] <> [1, 2, 3] True [1, 2] = [1, 2, 2] True [1, 2, 3] = [3, 2, 1] True [1, 2, 3] = [1..3] True

    Операции принадлежности (>=, = B равно True, если все элементы множества B содержатся в множестве A. Выражение A = [1, 2] True [1, 2] in . Используется для проверки принадлежности элемента указанному множеству. Обычно применяется в условных операторах.

    Выражение


    Результат


    5 in [1..9] True 5 in [1..4, 6..9] False

    Операция in позволяет эффективно и наглядно выполнять сложные проверки условий, заменяя иногда десятки других операций. Например, оператор

    можно заменить более коротким:

    Операцию in иногда пытаются записать с отрицанием: X not in S. Такая запись является ошибочной, так как две операции следуют подряд. Правильная запись имеет вид: not (X in S).

    Объединение множеств (+) . Объединением двух множеств является третье множество, содержащее элементы обоих множеств.

    Выражение


    Результат


    [ ] + [1, 2] [1, 2] [1, 2] + [2, 3, 4] [1, 2, 3, 4]

    Пересечение множеств (*) . Пересечение двух множеств — это третье множество, которое содержит элементы, входящие одновременно в оба множества.

    Выражение


    Результат


    [ ] * [1, 2] [ ] [1, 2] * [2, 3, 4] [2]

    Разность множеств (-) . Разностью двух множеств является третье множество, которое содержит элементы первого множества, не входящие во второе множество.

    Выражение


    Результат


    [1, 2, 3] — [2, 3] [1] [1, 2, 3] — [ ] [1, 2, 3]

    В язык Delphi введены две стандартные процедуры Include и Exclude, которые предназначены для работы с множествами.

    Процедура Include (S, I) включает в множество S элемент I. Она дублирует операцию + (плюс) с той лишь разницей, что при каждом обращении включает только один элемент и делает это более эффективно.

    Процедура Exclude (S, I) исключает из множества S элемент I. Она дублирует операцию — (минус) с той лишь разницей, что при каждом обращении исключает только один элемент и делает это более эффективно.

    Выражение


    Результат


    S := [1, 3]; [1, 3] Include(S, 2); [1, 2, 3] Exclude(S, 3) [1, 2]

    Использование в программе множеств дает ряд преимуществ: значительно упрощаются сложные операторы if , улучшается наглядность программы и понимание алгоритма решения задачи, экономится время разработки программы. Поэтому множества широко используются в библиотеке компонентов среды Delphi.

    Записи


    Объявление записи



    Запись — это составной тип данных, состоящий из фиксированного числа элементов одного или нескольких типов. Описание типа записи начинается словом record и заканчивается словом end . Между ними заключен список элементов, называемых полями , с указанием идентификаторов полей и типа каждого поля:

    Идентификаторы полей должны быть уникальными только в пределах записи. Допускается вложение записей друг в друга, т.е. поле записи может быть в свою очередь тоже записью.

    Чтобы получить в программе реальную запись, нужно создать переменную соответствующего типа:

    Записи можно создавать и без предварительного описания типа, но это делается редко, так как мало отличается от описания полей в виде отдельных переменных.

    Доступ к содержимому записи осуществляется посредством идентификаторов переменной и поля, разделенных точкой. Такая комбинация называется составным именем . Например, чтобы получить доступ к полям записи Friend, нужно записать:

    Обращение к полям записи имеет несколько громоздкий вид, что особенно неудобно при использовании мнемонических идентификаторов длиной более 5 символов. Для решения этой проблемы в языке Delphi предназначен оператор with, который имеет формат:

    Однажды указав имя записи в операторе with, можно работать с именами ее полей как с обычными переменными, т.е. без указания идентификатора записи перед идентификатором поля:

    Допускается применение оператора присваивания и к записям в целом, если они имеют один и тот же тип. Например,

    После выполнения этого оператора значения полей записи Friend станут равными значениям соответствующих полей записи BestFriend.

    Записи с вариантами

    Строго фиксированная структура записи ограничивает возможность ее применения. Поэтому в языке Delphi имеется возможность задать для записи несколько вариантов структуры. Такие записи называются записями с вариантами . Они состоят из необязательной фиксированной и вариантной частей.

    Вариантная часть напоминает условный оператор case . Между словами case и of записывается особое поле записи — поле признака . Оно определяет, какой из вариантов в данный момент будет активизирован. Поле признака должно быть равно одному из расположенных следом значений. Каждому значению сопоставляется вариант записи. Он заключается в круглые скобки и отделяется от своего значения двоеточием. Пример описания записи с вариантами:

    Обратите внимание, что у вариантной части нет отдельного end , как этого можно было бы ожидать по аналогии с оператором case . Одно слово end завершает и вариантную часть, и всю запись.

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

    Delphi: StringList Delimiter всегда является символом пробела, даже если задан разделитель

    У меня возникают проблемы с разделителем в классе TStringList. Посмотрите:

    sl[1] ДОЛЖЕН возвращаться ‘foo bar’

    sl[1] Возвращает ‘foo’

    Кажется, что теперь разделитель ‘^’ AND ‘ ‘

    Вы должны установить s1.StrictDelimiter := True , чтобы пробелы не считались разделителями, подробнее здесь.

    Поскольку вы работаете в версии, которая не поддерживает вышеописанную (как было выяснено после отправки ответа), у вас есть два варианта:

    • Поиск знака, который, как вы знаете, не будет использоваться в исходном тексте (например, подчеркивание), преобразовать все пробелы в этот символ перед разбиением и преобразовать обратно после разделения. Это предложение robosoft.
    • Если в тексте нет инвертированных запятых и пробелов, вы можете использовать трюк Alexander и обернуть текст между разделителями в инвертированной команде, чтобы ‘hello hello^bye bye’ переходит в ‘»hello hello»^»bye bye»‘ . Если вы выбрали этот путь, и он работает для вас, пожалуйста, примите ответ Александра, а не мой, он также предоставит код для его реализации.

    Оба обхода, не использующие StrictDelimiter , имеют ограничения: для первого требуется некоторый неиспользуемый символ, а второй не требует перевернутых запятых и пробелов в исходном тексте.

    Возможно, пришло время перейти на более новую версию Delphi:)

    LastDelimiter — Функция Delphi

    Доброго дня!
    Имеется некоторый текст, разделенный точкой с запятой, из коего нужно получить список. И все б было хорошо, но только TStringList зачем-то делит его не только по заданному делиметру но и по пробелам. Избежать этого можно предварительно закавычив нужные куски. Но, в свою очередь, в тексте могут быть кавычки в не предназначенных для этого местах (да уже, сволочи, попадаются -:( ). Вот собственно вопрос: можно как то заставить stringlist делимитрить текст исключительно по символу Delimiter?

    <
    Преобразование строки с разделителями в список строк.
    Параметры:
    Source — исходная строка.
    Delimiter — строка-разделитель.
    Parts — список строк, заполняемый функцией. Создается и уничтожается
    вызывающей функцией.
    >
    procedure StrBreakApart(const Source, Delimeter: string; Parts: TStrings);
    var
    curPos: Integer;
    curStr: string;
    begin
    Parts.Clear;
    if Length(Source) = 0 then
    Exit;
    Parts.BeginUpdate;
    try
    CurStr:= Source;
    repeat
    CurPos:= AnsiPos(Delimeter, CurStr);
    if CurPos > 0 then begin
    Parts.Add(Copy(CurStr, 1, Pred(CurPos)));
    CurStr:= Copy(CurStr, CurPos+Length(Delimeter),
    Length(CurStr)-CurPos-Length(Delimeter)+1);
    end else
    Parts.Add(CurStr);
    until CurPos=0;
    finally
    Parts.EndUpdate;
    end;
    end;

    > [1] Игорь Шевченко © (03.10.07 12:10)

    Это конечно хорошо, но можно ли сделать сабж именно с помощью TStringList?

    PS
    Мне тоже интересно&#133


    > можно как то заставить stringlist делимитрить текст исключительно
    > по символу Delimiter?
    >

    Но можно на время работы этого сплит-метода заменить пробелы, например, на символ с кодом #255.

    > Это конечно хорошо, но можно ли сделать сабж именно с помощью
    > TStringList?

    Нет. Достаточно взглянуть на код
    procedure TStrings.SetDelimitedText(const Value: string);
    где принпдлежность символа к текущему «слову» определяется так:
    while (P^ > » «) and (P^ <> Delimiter) do

    Какая версия Delphi? В новых версиях (2006 и выше) у TStringList вроде как есть булевское свойство StrongDelimiter или что-то типа того, и если оно равно true, пробелы за разделитель не считаются

    > Кевларвестов Семен (03.10.2007 12:07:00) [0]

    When assigning CommaText, the value is parsed as SDF formatted text. For SDF format, strings are separated by commas or spaces, and optionally enclosed in double quotes. Double quote marks that are part of the string are repeated to distinguish them from the quotes that surround the string. Spaces and commas that are not contained within double quote marks are delimiters. Two commas next to each other will indicate an empty string, but spaces that appear next to another delimiter are ignored

    > Anatoly Podgoretsky © (03.10.07 12:26)

    Вот, я в ситуациях как в сабже сначала преобразую строку так:

    asdsd«sad;asd;

    Determines how the Delimiter property is used.
    Class
    TStrings

    [Delphi] property StrictDelimiter: Boolean read GetStrictDelimiter write SetStrictDelimiter;

    Description
    Use this property to specify whether the Delimiter is the only value used within the DelimitedText property. If set to True, individual strings in DelimitedText are separated only by the character that is the value of Delimiter. If set to False, individual strings in DelimitedText can be separated by a space, a non-printable character, or the character that is the value of Delimiter.

    Kolan © (03.10.07 12:14) [2]


    > Это конечно хорошо, но можно ли сделать сабж именно с помощью
    > TStringList?

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

    StrBreakApart(«text
    more text
    once again more text», «
    «, Parts);


    > Это конечно хорошо, но можно ли сделать сабж именно с помощью
    > TStringList?

    А что где-то было заявлено, что TStringList обладает искусственным интеллектом?

    Функции Delphi модуля SysUtils

    Модуль SysUtils

    Предлагаем список функций модуля SysUtils, используемого в среде разработки Delphi.

    AnsiCompareStr Сравнение двух строк на равенство
    AnsiCompareText Сравнение двух строк на равенство
    AnsiLowerCase Символы верхнего регистра изменяются в строку со строчными буквамм
    AnsiPos Находит позицию одной строки в другой
    ChangeFileExt Изменяет расширение имени файла
    CompareStr Сравнивает две строки, чтобы увидеть, какая из них больше
    CompareText Сравнивает две строки, игнорируя регистр
    CreateDir Создаёт директорию
    CurrToStr Преобразует денежную величину в строку
    CurrToStrF Преобразует денежную величину в строку с форматированием
    Date Возвращает текущую дату
    DateTimeToFileDate Преобразует значение TDateTime в формат date/time формат файла
    DateTimeToStr Конвертирует значение даты и времени TDateTime в строку
    DateToStr Преобразует значение даты TDateTime в строку
    DayOfWeek Выдает индекс дня недели для значения TDateTime
    DeleteFile Удаляет файл, указанный в параметре
    DirectoryExists Возвращает true, если указанная директория существует
    DiskFree Выдает число свободных байтов на указанном диске
    DiskSize Выдает размер указанного диска в байтах
    EncodeDate Формирует значение TDateTime из значений года, месяца и дня
    EncodeTime Формирует значение TDateTime из значений часа, минуты, секунды и миллисеккунды
    ExtractFileDir Извлекает из полного имени файла название папки
    ExtractFileDrive Извлекает из полного имени файла название диска
    ExtractFileExt Извлекает из полного имени файла его расширение
    ExtractFileName Извлекает из полного имени файла краткое имя файла
    ExtractFilePath Извлекает из полного имени файла название патча
    FileAge Получение дата/время последнего изменения файла, не открывая его
    FileDateToDateTime Конвертирует формат даты/времени файла в значение TDateTime
    FileExists Возвращает True если указанный файл существует
    FileGetAttr Выдаёт атрибуты файла
    FileSearch Поиск файла в одной или более папках
    FileSetAttr Устанавливает атрибуты файла
    FindClose Закрывает успешный FindFirst поиск файла
    FindCmdLineSwitch Определяет, был передан некоторый параметр выключатель
    FindFirst Находит все файлы, соответствующие маске файла и атрибутов
    FindNext Находит следующий файл после успешного FindFirst
    FloatToStr Преобразует значение с плавающей запятой в строку
    FloatToStrF Преобразует значение с плавающей запятой в строку с форматированием
    ForceDirectories Создаёт новый путь каталогов
    Format Богатое форматирование чисел и текста в строке
    FormatCurr Богатое форматирование значений валюты в строку
    FormatDateTime Богатое форматирование переменной TDateTime в строку
    FormatFloat Богатое форматирование числа с плавающей запятой в строку
    GetCurrentDir Возвращает текущий каталог (диск плюс каталог)
    IncMonth Увеличивает TDateTime переменную на некоторое число месяцев
    IntToHex Преобразует целое число в шестнадцатеричную строку
    IntToStr Конвертирует целое число в строку
    IsLeapYear Возвращает True, если данный календарный год високосный
    LastDelimiter Находит последнюю позицию указанных символов в строке
    LowerCase Изменяет символы верхнего регистра в строке в строчные буквы
    Now Выдает текущую дату и время
    RemoveDir Позволяет удалить директорию
    Rename Переименовка фала
    RenameFile Переименование файла или директории
    SetCurrentDir Изменяет текущую директорию
    StrScan Ищет заданные символы в строке
    StrToCurr Преобразует числовую строку в денежное выражение
    StrToDate Конвертирует строку с датой в значение типа TDateTime
    StrToDateTime Конвертирует строку с датой и временем в значение типа TDateTime
    StrToFloat Преобразует числовую строку в значение с плавающей запятой
    StrToInt Преобразует строку с целым значением в Integer
    StrToInt64 Преобразует строку с целым значением в Int64
    StrToInt64Def Преобразует строку с целым значением в Int64, учитывая значение по умолчанию
    StrToIntDef Преобразует строку с значение с типом Integer, учитывая значение по умолчанию
    Time Возвращает текущее время
    TimeToStr Конвертирует значение времени типа TDateTime в строку
    Trim Удаляет начальные и конечные пробелы в строке
    TrimLeft Удаляет начальные пробелы в строке
    TrimRight Удаляет конечные пробелы в строке
    UpperCase Изменяет символы в строке из нижнего регистра в верхний
    WrapText Добавьте перенос строки в строку, чтобы имитировать перенос слов
    Abort Прерывает обработку команд и выходит к последнему исключительному блоку
    AppendStr Конкатенация одной строки в конец другой
    Beep Делает звук гудка
    DateTimeToString Огромные возможности форматирования даты в строку
    DecodeDate Извлекает значения года, месяца, дня из TDateTime переменной
    DecodeDateTime Разбивает TDateTime переменную на ее части даты/времени
    DecodeTime Разбивает значение TDateTime на отдельные значения времени
    FreeAndNil Освобождение памяти объекта и установка его в nil
    FreeMem Освобождает память, используемую переменной
    GetLocaleFormatSettings Получает региональные значения для безопасных потоков функций.
    ReplaceDate Изменяет только часть даты TDateTime переменной
    ReplaceTime Изменяет только часть времени TDateTime переменной

    LastDelimiter — Функция Delphi

    иногда, после использования дельфевого диалога открытия файла, лог пишется в ту диру, из которой выбрали последний файл . Понятно что диалог меняет CurrentDirectory, но не путь загрузки библиотеки же . Почему тогда пишется не туда, куда должно?

    ПыСы: попробовала поменять ParamStr(0) на GetModuleName(HInstance). Не помогло

    ПыПыСы: не помогло потомучто дельфи и сама использует GetModuleFileName

    1. bislomet , 11.06.2013 17:56
    ask google:get dll location delphi
    То, что у Вас написано — никаким образом не поможет узнать путь к DLL, только к exe
    2. olivenoel , 11.06.2013 18:06
    ну да. Но это не совсем к делу относится. Длл не шарная, она лежит рядом со своим хост-ехе. Причем остальные классы-методы пишут в нужное место (т.е. в ParamStr(0)), хотя и в разные файлы.

    Все как-то сложно. Происходит не всегда. Вчера не было, в пятницу и сегодня было. Причем было так, что в пятницу, лог стройно писался куда положено до 07:15, потом почему-то кусок до 07:24 записан в папку, из которой предположительно открывался файл, а потом снова п правильную папку.

    3. bislomet , 11.06.2013 18:38
    olivenoel
    значит, у Вас еще кто-то в эту переменную пишет.

    цитата (olivenoel): иногда, после использования дельфевого диалога открытия файла, лог пишется в ту диру, из которой выбрали последний файл

    4. vertur , 11.06.2013 18:40

    цитата: bislomet:
    olivenoel
    значит, у Вас еще кто-то в эту переменную пишет.

    В какую переменную? В ParamStr(0)? Ведь для флаша ставится жесткий путь в эту папку.

    А даже если и пишет, то почему сбивается аккурат на папку возможного открытия файлов?

    Добавление от 11.06.2013 18:44:

    цитата (olivenoel): иногда, после использования дельфевого диалога открытия файла, лог пишется в ту диру, из которой выбрали последний файл

    Ужас. Наверно глупый оленевод использует не полный путь к логу, а относительный относительно текущей рабочей директории.

    5. olivenoel , 11.06.2013 18:43
    6. bislomet , 11.06.2013 18:44
    Вам тут уже Вашу проблему обозначили — Вы не используете полный путь, а относительный.

    Добавление от 11.06.2013 18:45:

    А «почему» — это к Вам вопрос — где-то там у Вас «changedir» затесался

    цитата (olivenoel): глупый оленевод использует ParamStr(0)/GetFileName для «собирания» пути. Блин выше же код сброса строк в файл.

    Глупый оленевод для начала должен посмотреть на функцию ExtractFileDir.

    7. vertur , 11.06.2013 18:52
    8. Konstantin Mironovich , 11.06.2013 18:55
    olivenoel
    Писать должна в ту директорию, где сама лежит.
    это неправильно.
    если dll и весь пакет лежит в Program Files, то туда под юзером писать не получится.
    писать нужно в %temp%, %appdata%, %localappdata%
    9. bislomet , 11.06.2013 18:59
    Konstantin Mironovich
    Это будет темой следующего топика

    цитата (olivenoel): глупый оленевод использует ParamStr(0)/GetFileName для «собирания» пути. Блин выше же код сброса строк в файл.

    Глупый оленевод для начала должен посмотреть на функцию ExtractFileDir.

    эта функция работает только с той строкой, что в нее пришла. Ни с какими другими. Она просто копирует в результат входящую строку до последнего PathDelim.

    10. olivenoel , 11.06.2013 19:00

    цитата (Konstantin Mironovich): писать нужно в %temp%, %appdata%, %localappdata%

    Это тысячу раз да, и сидеть надо не под админом. Но для виндовс-секты это как горох о стену.

    olivenoel
    Надо смотреть внимательнее.

    11. vertur , 11.06.2013 19:02

    цитата: Konstantin Mironovich:
    olivenoel
    Писать должна в ту директорию, где сама лежит.
    это неправильно.
    если dll и весь пакет лежит в Program Files, то туда под юзером писать не получится.
    писать нужно в %temp%, %appdata%, %localappdata%

    программа НЕ БУДЕТ там лежать. Программа не инсталится, а запускается с шары, на которую есть права у запускающего. Пункт.

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

    12. olivenoel , 11.06.2013 19:05
    13. Konstantin Mironovich , 11.06.2013 19:07
    vertur
    Но для виндовс-секты это как горох о стену.
    вот не надо.
    во «Writting Secure Code» (http://www.amazon.com/Writing-Secure-Code-Second-Edition/dp/0735617228) четко написано: никаких относительных путей. только полные.
    а запись в спецкаталоги — это требование WinLogo или типатого..
    14. vertur , 11.06.2013 19:07
    olivenoel
    тем не менее писать можно только и только в домашних юзеру директориях. В винде есть roaming app data которая правильно синхронизируется при использовании системы не через ж..у.
    15. bislomet , 11.06.2013 19:08
    olivenoel
    Вам все равно надо сперва построить абсолютный путь, а потом с ним работать — независимо от того, идет речь о program files, %appdata% или network share.
    Тогда никакие change dir-ы не повлияют на пути.
    16. vertur , 11.06.2013 19:10
    Konstantin Mironovich
    Тем не менее каждый производитель (особенно Гондурасовский) своего говно-продукта пишет абы куда и часто в свою установочную директорию.

    Правда всякие 3ds Max Studio/AutoCAD этим тоже страдают в особо изощеренной форме.

    ЗЫ: В Unix-е таких быдлокодеров быстро отрезвляют — сказано нельзя, значит не запишеш.

    исправили быстро Вся проблема в том, что в туда приходит ParamStr(0) aka GetModuleFileName. Он разве может приносить пустой путь? Т.е. программа может и не знать, откуда ее запустили?

    17. olivenoel , 11.06.2013 19:11
    18. vertur , 11.06.2013 19:12
    olivenoel
    О-да. Паскаль снес мне крышу.
    LastDelimiter вернет 0 или 1 ?

    19. Konstantin Mironovich , 11.06.2013 19:13
    vertur
    В винде есть roaming app data
    это %appdata% и есть.

    доменный админ вполне способен достучаться до домашних каталогов всех пользователей.

    а потом еще жалуются на засилье вирусов.

    20. bislomet , 11.06.2013 19:14
    olivenoel
    это Вы не оттуда начинаете.
    Еще раз — откуда — из программы или из dll Вы вызываете функцию ParamStr?

    Добавление от 11.06.2013 19:14:

    т.е. Dll может не знать ничего о paramstr(0)

    21. Konstantin Mironovich , 11.06.2013 19:15
    vertur
    пишет абы куда и часто в свою установочную директорию.
    под юзером при попытке записи в ProgramFiles получаем реальный access denied.
    я не знаю как они это решают.
    скорее всего у них собственный инсталлер и свой собственный каталог в руте. нафиг таких..

    цитата (Konstantin Mironovich): скорее всего у них собственный инсталлер и свой собственный каталог в руте. нафиг таких..

    И это практически у всех (кого я видел).

    цитата (Konstantin Mironovich): под юзером при попытке записи в ProgramFiles получаем реальный access denied.

    22. vertur , 11.06.2013 19:20

    цитата: bislomet:
    olivenoel
    это Вы не оттуда начинаете.
    Еще раз — откуда — из программы или из dll Вы вызываете функцию ParamStr?

    Добавление от 11.06.2013 19:14:

    т.е. Dll может не знать ничего о paramstr(0)

    Как она не может знать? ParamStr вызывает GetModuleFileName(0, Buffer, Length(Buffer)). Т.е. ищет полный путь до процесса, из которого вызвана, или? Длл загружена в процесс хост-приложения. Т.е. GetModuleFileName(0. ) вернет путь к хост-приложению. Всегда ?! Но флашится в совсем левый путь? И ведь не постоянно, а на какое-то время. Потом (не понятна сама привязка по времени, когда путь «сбрасывается») GetModuleFileName снова находит правильный путь?

    23. bislomet , 11.06.2013 19:20
    Konstantin Mironovich
    под юзером при попытке записи в ProgramFiles получаем реальный access denied
    Зависит от версии Windows и прав юзера.
    Power User в 24. olivenoel , 11.06.2013 19:26
    25. Konstantin Mironovich , 11.06.2013 19:28
    bislomet
    Зависит от версии Windows и прав юзера.
    начиная с Vista/UAC, ес-но.

    vertur
    Это если «под админом не сидеть».
    ну вот я как раз под админом сижу. но чтобы записать туда, мне нужен elevate.

    26. vertur , 11.06.2013 19:29
    GetModuleFileName с NULL параметром всегда возвращает имя exe текущего процесса, в случае ошибки пустую строку. Не имееет никакой разницы откуда вызвана эта функция: из тела самого exe, или из тела подгруженной dll.

    Добавление от 11.06.2013 19:31:

    цитата (Konstantin Mironovich): ну вот я как раз под админом сижу

    цитата: vertur:
    olivenoel
    О-да. Паскаль снес мне крышу.
    LastDelimiter вернет 0 или 1 ?

    в худшем случае, должен единицу вернуть. Т.е. если слэшей и двоеточий вообще нет. Индексация символов строки начинается с 1. По индексу 0 лежит длина строки.

    27. olivenoel , 11.06.2013 19:31
    28. bislomet , 11.06.2013 19:32
    olivenoel
    Индексация символов строки начинается с 1. По индексу 0 лежит длина строки
    Это уже очень давно не так — если, конечно, Вы не работаете с ShortString
    29. vertur , 11.06.2013 19:33
    olivenoel
    По индексу 0 у вас будет runtime error. Чисто механически в нулевом байте (а потом и в начальном слове) лежит длинна строки, только кто же вас туда пустит.

    I у вас содержит длинну копируемой части строки. вот там и ищете лучше.

    30. Konstantin Mironovich , 11.06.2013 19:38
    vertur
    вот и все так говорят. Вот и результат.
    да ладно.. это под XP я вынужден был заводить специального юзера.
    а после появления UAC это стало ненужно. по умолчанию работаешь с правами юзера.
    есть, конечно, встроенный Admin, но нафига он..

    GetModuleFileName с NULL параметром всегда возвращает имя exe текущего процесса, в случае ошибки пустую строку.
    ну если бинарики грузятся по сетке, то может быть и пусто.. наверное.. тогда имеет смысл посмотреть GetLastError.

    31. vertur , 11.06.2013 19:44
    Konstantin Mironovich
    Из шаред фолдера, не должно быть пусто, хотя.
    32. Konstantin Mironovich , 11.06.2013 19:49
    vertur
    Из шаред фолдера,
    так shared он на той стороне. а mounted ли он на этой — неизвестно.

    цитата: vertur:
    olivenoel
    По индексу 0 у вас будет runtime error. Чисто механически в нулевом байте (а потом и в начальном слове) лежит длинна строки, только кто же вас туда пустит.

    да. Именно так. Корроче, индексация символов строки начинается с 1.

    цитата: vertur:
    olivenoel
    I у вас содержит длинну копируемой части строки. вот там и ищете лучше.

    I вообще то не у меня. Это код из System.SysUtils.pas — генофонд дельфи. Я его переделать не могу.

    GetModuleFileName генофонд винды. Его переполнение может случится, если возвращаемый путь длиннее 260 (MAX_PATH) символов. Но пока программа запущена ее путь не меняется? Т.е. она всегда должна возвращать одну и ту же строку? Почему иногда эта строка может стать длиннее 260 символов?

    33. olivenoel , 11.06.2013 19:54
    34. vertur , 11.06.2013 20:00
    olivenoel
    1. При переполнении оно просто отрубит «лишние» смиволы в пути. Узнать этот случай можно по возвращенной длинне записанного в буфер.

    2. Современные пути обычно больше чем 260 символов (260 символов — пережиток прошлого, одно длинное имя файла само может быть 256 символов и больше).
    Наблюдается кстати это очень хорошо в TotalCommander (Wincmd)

    if you call ExtractFilePath(ParamStr(0)) from a DLL you will get the path of the web server, so if you want the path of the DLL itself use:
    function ScriptPath: String;
    var path: array[0..MaxPathLength-1] of char;
    begin
    if IsLibrary then
    SetString(Result, path, GetModuleFileName(HInstance, path, SizeOf(path)))
    else Result := ParamStr(0);
    end;

    Кстати, что-то мне подсказывает, что максимальная длина пути в Windows, начиная в WIndows NT должна быть 1024, а не 256 символов (т.е. Ansi- or WideChar).

    И если вернуться к теме:
    1. получить путь к dll
    2. преобразовать его в _абсолютный_ путь
    3. далее его использовать.

    35. bislomet , 11.06.2013 20:05

    цитата (bislomet): Кстати, что-то мне подсказывает, что максимальная длина пути в Windows, начиная в WIndows NT должна быть 1024

    Твоё «подсказывает» тебе нагло врет. Длинна может быть любой (в ранних вариантах не более 65536 символов).

    36. vertur , 11.06.2013 20:16
    37. bislomet , 11.06.2013 20:24
    Maximum Path Length Limitation (http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath)

    цитата:
    Maximum Path Length Limitation

    In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters. A local path is structured in the following order: drive letter, colon, backslash, name components separated by backslashes, and a terminating null character. For example, the maximum path on drive D is «D:\some 256-character path string » where » » represents the invisible terminating null character for the current system codepage. (The characters are used here for visual clarity and cannot be part of a valid path string.)

    И если вернуться к теме:
    1. получить путь к dll
    2. преобразовать его в _абсолютный_ путь
    3. далее его использовать.

    ну так я и сделала.

    за исключением наверное

    цитата: 2. преобразовать его в _абсолютный_ путь

    но путь от GetModuleFileName и так _абсолютный_. Или и тут бывают исключения?

    38. olivenoel , 11.06.2013 20:27
    39. Konstantin Mironovich , 11.06.2013 20:35
    olivenoel
    ну так я и сделала.
    еще file к директории присоединяют с помощью ::PathAppend (http://msdn.microsoft.com/en-us/library/windows/desktop/bb773565%28v=vs.85%29.aspx)
    40. bislomet , 11.06.2013 20:37
    olivenoel
    но путь от GetModuleFileName и так _абсолютный_. Или и тут бывают исключения?

    вроде нет. MS утверждает, что возвращается full qualified path.

    Только из Вашего последнего приведенного куска кода не видно, как считается logfile path.

    цитата (bislomet): Кстати, что-то мне подсказывает, что максимальная длина пути в Windows, начиная в WIndows NT должна быть 1024

    Твоё «подсказывает» тебе нагло врет. Длинна может быть любой (в ранних вариантах не более 65536 символов).

    41. olivenoel , 11.06.2013 20:38
    42. bislomet , 11.06.2013 20:39
    Konstantin Mironovich
    еще file к директории присоединяют с помощью : athAppend

    Это в мире C++. В Delphi для этого есть Combine (http://Combine) — полный аналог .Net-овского

    Добавление от 11.06.2013 20:42:

    olivenoel
    максимальная длинна пути в Win XP — 260 символов. Для этого есть системная переменная MAX_PATH. Только что попробовала — путь не может быть больше этой длины (эксплорер не переименовывает). Если путь исчерпывает всю длину, то файл невозможно запустить
    Да, точно. Но это — ограничение explorer-а, а не OS/FS
    Путь может быть до 32760 Unicode (http://msdn.microsoft.com/en-us/library/windows/desktop/ee681827%28v=vs.85%29.aspx) .
    а 1024 — это макс. длина переменной окружения PATH
    90° — то прямий кут.

    цитата: bislomet:
    olivenoel
    но путь от GetModuleFileName и так _абсолютный_. Или и тут бывают исключения?

    вроде нет. MS утверждает, что возвращается full qualified path.

    Только из Вашего последнего приведенного куска кода не видно, как считается logfile path.

    потому что он все еще «считается» так, как приведено в первом (или около того) посте. Просто вместо ParamStr(0) теперь GetCurrentStartUp.

    olivenoel
    максимальная длинна пути в Win XP — 260 символов. Для этого есть системная переменная MAX_PATH. Только что попробовала — путь не может быть больше этой длины (эксплорер не переименовывает). Если путь исчерпывает всю длину, то файл невозможно запустить
    Да, точно. Но это — ограничение explorer-а, а не OS/FS
    Путь может быть до 32760 Unicode (http://msdn.microsoft.com/en-us/library/windows/desktop/ee681827%28v=vs.85%29.aspx) .
    а 1024 — это макс. длина переменной окружения PATH
    90° — то прямий кут.

    цитата: Limits
    Feature NTFS exFAT
    Maximum file name length 255 Unicode characters 255 Unicode characters

    Maximum path name length 32,760 Unicode characters 32,760 Unicode characters
    with each path component with each path component
    no more than 255 no more than 255
    characters characters

    43. olivenoel , 11.06.2013 20:45
    44. bislomet , 11.06.2013 20:46
    в первом посте у Вас FFilePath — откуда он взялся?

    цитата: bislomet:
    в первом посте у Вас FFilePath — откуда он взялся?

    это имя/путь файла для слива лога. Он устанавливается из вне. Проверку на наличие буквы тома (т.е. на абсолютность пути) добавила. Но fullFilename := Format(‘%s\%s’, [libPath, FFilePath]); осталась.

    45. olivenoel , 11.06.2013 20:53
    46. bislomet , 11.06.2013 21:06
    olivenoel
    Сделйте Combine вместо format().
    За такое посторение пути отрывают ручки-ножки во младенчестве

    И — откуда Вы знаете, что передаете что-то правильное?
    Т.е. например у Вас dll path: d:\mydir1\mydir2
    и Вы передали c:\wrongdir1\wrongdir2\logfile.log
    что Вы ожидаете получить в результате применения команды format()?

    47. Konstantin Mironovich , 11.06.2013 21:15
    bislomet
    Это в мире C++. В Delphi для этого есть Combine
    в мире C++ есть CPath (по кр мере в ATL/MFC)
    ссылка про комбайны интересная
    48. AleX_SPb , 11.06.2013 21:18
    А в каком месте dll и в какой момент определяется путь, куда писать? Допустим, если путь определяется в DllEntryPoint по DLL_PROCESS_ATTACH посредством GetModuleFileName(NULL. ), а потом эта dll грузится каким-то другим процессом, то и путь (в данном случае к exe’шнику) при следующей загрузке очевидно изменится.

    цитата: AleX_SPb:
    А в каком месте dll и в какой момент определяется путь, куда писать? Допустим, если путь определяется в DllEntryPoint по DLL_PROCESS_ATTACH посредством GetModuleFileName(NULL. ), а потом эта dll грузится каким-то другим процессом, то и путь (в данном случае к exe’шнику) при следующей загрузке очевидно изменится.

    при заполнении хранилища лога (в данном случае TStringList) до определенного размера (в данном случае FLogContent.Count) вызывается FlushLog. Dll грузится статически, по типу:

    где dllname — имя (только имя) файла биб-ки, т.е. длл должная лежать по месту прибывания исполняемого файла.

    Про размещение по умолчанию в курсе, но биб-ка не копируется в системные папки. К тому же используется не GetModuleFileName(NULL. ), а GetModuleFileName(HInstance (*внутри библиотеки*). ) — он возвращает таки абсолютный путь библиотеки.

    Добавление от 11.06.2013 23:58:

    цитата: bislomet:
    olivenoel
    Сделйте Combine вместо format().
    За такое построение пути отрывают ручки-ножки во младенчестве

    Combine — это из .net (если судить по System.IO)? На него распространяются политики безопасности фреймворка?

    ПыСы: в моем случае употребление Format ИМХО более ожидаемо, потому что он вообще используется в любой обработке строк (т.е. выражений типа

    ), кроме взятия подстроки.

    цитата: bislomet:
    И — откуда Вы знаете, что передаете что-то правильное?
    Т.е. например у Вас dll path: d:\mydir1\mydir2
    и Вы передали c:\wrongdir1\wrongdir2\logfile.log
    что Вы ожидаете получить в результате применения команды format()?

    ето хорошее замечание, с одной стороны. Но в ф-ции есть проверка (теперь есть) на присутствие буквы тома. Если она в FFilePath есть, то он берется без изменений. Если нет, то комбинируется форматом. Т.е. если

    49. olivenoel , 11.06.2013 23:44
    50. Konstantin Mironovich , 11.06.2013 23:59
    со времени первого постинга мы сильно продвинулись..
    51. olivenoel , 12.06.2013 00:02
    последняя редакция выгрузки лога в файл:

    52. vertur , 12.06.2013 00:05
    bislomet
    olivenoel

    цитата: Путь может быть до 32760 Unicode.

    Это старое ограничение о котором я говорил, на самом деле уже в WinXP путь может быть произвольной длинны. И все там прекрасно запускается. просто нечего этаким дерьмом как эксплорер пользоваться.

    цитата (olivenoel): максимальная длинна пути в Win XP — 260 символов. Для этого есть системная переменная MAX_PATH

    это не так. И MAX_PATH вовсе не переменная.
    Определение MAX_PATH — это величайшее зло-наследие DOS сдуру оставленое мелкомягкими.

    53. Konstantin Mironovich , 12.06.2013 00:10
    olivenoel
    if (ExtractFileDrive(FFilePath) = »)
    для этого есть методы WinAPI
    PathIsDirectory(); PathIsFileSpec(); PathIsRoot();

    цитата (olivenoel): Dll грузится статически

    Добавление от 12.06.2013 00:14:

    цитата (AleX_SPb): . а потом эта dll грузится каким-то другим процессом.

    Добавление от 12.06.2013 00:16:

    цитата (Konstantin Mironovich): со времени первого постинга мы сильно продвинулись..

    Лучше обьясните почему оленевод еще работает на этой позиции при явном несоответсвии.

    54. vertur , 12.06.2013 00:12

    цитата: Путь может быть до 32760 Unicode.

    Это старое ограничение о котором я говорил, на самом деле уже в WinXP путь может быть произвольной длинны. И все там прекрасно запускается. просто нечего этаким дерьмом как эксплорер пользоваться.

    господин vertur, Вы непоследовательны. Т.е. программить под админом нельзя, а называть файлы так, что 95% целевой группы пользователей их потом не откроет (потому как пользуются пользователи стандартным проводником) — это завсегда пожалуйста?

    цитата (olivenoel): максимальная длинна пути в Win XP — 260 символов. Для этого есть системная переменная MAX_PATH

    это не так. И MAX_PATH вовсе не переменная.
    Определение MAX_PATH — это величайшее зло-наследие DOS сдуру оставленое мелкомягкими.

    Добавление от 12.06.2013 00:26:

    цитата (olivenoel): Dll грузится статически

    Гыж. DLL = dynamic link library, оно по определению не может «грузится статически». Статически может быть слинкована прослойка-stub которая осуществляет линковку с функциями dll.

    тогда назовем это все «ранним связыванием». Или как еще можно назвать, когда явно не вызываются LoadLibrary/FreeLibrary?

    В большинстве источников (могу ошибаться но когда-то читала у Фленова кажется в очередной «Библии Дельфи») применение external, т.е. линковка биб-ки во время написания кода в одну строчку, называется «статической» (http://delphi.about.com/od/windowsshellapi/a/delphi-dll-loading-static-dynamic.htm) , в пику применения LoadLibrary\GetProcessAddress\FreeLibrary.

    When you want to call a function exported by a DLL, one question comes up: should you use static or dynamic DLL loading?

    Before you can call routines defined in DLL, you must import them. Functions exported from a DLL can be imported in two ways: by declaring an external procedure or function (static), or by direct calls to DLL specific API functions (dynamic).

    55. olivenoel , 12.06.2013 00:18

    цитата (olivenoel): Вы непоследовательны. Т.е. программить под админом нельзя, а называть файлы так, что 95% целевой группы пользователей их потом не откроет (потому как пользуются пользователи стандартным проводником) — это завсегда пожалуйста?

    Вы ошибаетесь. Речь идет не о «называть», а о том что ваша программа заведомо и необосновано полагает что путь будет небольше чем 260 символов. Вы должны поддерживать путь неограниченной длинны, вот об этом и идет речь.

    цитата (olivenoel): с помощью чего Otto Normalverbraucher (который знает как открыть интернет обозреватель и/или папку GAMES) сможет открыть файл, абсолютный путь которого длиннее 260 символов?

    С помощью приложения которое работает с этим файлом. Если оно создало такой файл, значит и должно уметь открывать как минимум. Разьве не очевидно ?

    цитата: В большинстве источников (могу ошибаться но когда-то читала у Фленова кажется в очередной «Библии Дельфи») применение external, т.е. линковка биб-ки во время написания кода в одну строчку, называется «статической», в пику применения LoadLibrary\GetProcessAddress\FreeLibrary.

    Не читайте всякое говно, тем более «библии X» или «Y для чайников». Статическая линковка — это когда линкуемая библиотека становится частью тела вашего приложения или тела вашей динамической библиотеки. (Static DLL Loading — это не статическая линковка).

    56. vertur , 12.06.2013 00:45

    цитата (olivenoel): Вы непоследовательны. Т.е. программить под админом нельзя, а называть файлы так, что 95% целевой группы пользователей их потом не откроет (потому как пользуются пользователи стандартным проводником) — это завсегда пожалуйста?

    Вы ошибаетесь. Речь идет не о «называть», а о том что ваша программа заведомо и необосновано полагает что путь будет не больше чем 260 символов. Вы должны поддерживать путь неограниченной длинны, вот об этом и идет речь.

    ладно, положим не обосновано. Но по какой причине, ParamStr(0) (а точнее GetModuleFileName(0. ) ) может возвращать разный результат в разные моменты времени существования одного и того же процесса?

    ПыСы: на счет статической и динамической линковки. Тейксейра с Пачеко именуют их неявной и явной загрузкой. Кэнту не нашла. Но вот про то, что читать, так что было в то время, то и читала. Даже Фленов был редкостью и только для читального зала. А «заграничных» так и вообще не было. И интернета в конце 90-х под боком не было еще.

    57. olivenoel , 12.06.2013 01:18

    цитата (olivenoel): Но по какой причине, ParamStr(0) (а точнее GetModuleFileName(0. ) ) может возвращать разный результат в разные моменты времени существования одного и того же процесса?

    Она обязана возвращать одно и тоже значение.

    цитата: И интернета в конце 90-х под боком не было еще.

    И как жэ я так без интернету то и Паскаль изучил, и Си, и С++. УжОс. И сейчас приходится еще и азы винапи растолковывать некоторым.

    Впрочем по делу — лог у вас есть, а пользоваться им вы не умеете. Распечатайте в нем те значения которые возвращает ваш стек функций начиная от GetModuleFileName. Я даю 80% вероятности что в итоге у вас получается относительный путь.

    PS: Кстати Дельфевая SetString требует вторым параметром указатель на буфер. Где там у вас указатель я в упор не вижу (посколько дельфи не знаю), но тем не менее пример выглядит так:

    PS2: Тоже самое кассается и вызова GetModuleFileName.

    Т.е. на мой скромный взгляд строка должна выглядеть так:

    58. vertur , 12.06.2013 01:54

    цитата (olivenoel): Но по какой причине, ParamStr(0) (а точнее GetModuleFileName(0. ) ) может возвращать разный результат в разные моменты времени существования одного и того же процесса?

    Она обязана возвращать одно и тоже значение.

    Добавление от 12.06.2013 02:46:

    цитата: vertur:
    PS: Кстати Дельфевая SetString требует вторым параметром указатель на буфер. Где там у вас указатель я в упор не вижу (посколько дельфи не знаю), но тем не менее пример выглядит так:

    PS2: Тоже самое кассается и вызова GetModuleFileName.

    Т.е. на мой скромный взгляд строка должна выглядеть так:

    Добавление от 12.06.2013 03:00:

    цитата (olivenoel): Но по какой причине, ParamStr(0) (а точнее GetModuleFileName(0. ) ) может возвращать разный результат в разные моменты времени существования одного и того же процесса?

    Она обязана возвращать одно и тоже значение.

    libPath становится пустой в ExtractFileDir, то значит она была пуста и до вызова изъятия директории.

    Потому что только если в

    придет пустая строка — I станет равна нулю и в Result будет пустая строка. И тогда в libPath тоже будет пустая строка.

    59. olivenoel , 12.06.2013 02:33
    60. bislomet , 12.06.2013 08:25
    Кстати, Вы все еще продолжаете пользоваться StringList-ом для логов

    Добавление от 12.06.2013 10:13:

    olivenoel
    Combine — это из .net (если судить по System.IO)? На него распространяются политики безопасности фреймворка?
    Как у Вас все запущено.

    В версиях Delphi XExx реимплементированы многие функции .Net — без .Net
    Исходники-то лежат в rtl.common — можете заглянуть, они не делают из этого секрет.

    одинаковые namespace-ы — для облегчения перехода (или одновременного использования).

    цитата: bislomet:
    Кстати, Вы все еще продолжаете пользоваться StringList-ом для логов

    так как раз в ним и нет никаких проблем.

    61. olivenoel , 12.06.2013 10:13
    62. bislomet , 12.06.2013 10:50
    olivenoel
    так как раз в ним и нет никаких проблем.
    Этот вопрос в одном из Ваших предыдущих топиков уже горячо дискутировался.
    Можете его еще раз перечитать — на тему того, почему так делать не надо _никогда_
    63. AleX_SPb , 12.06.2013 23:43
    olivenoel
    К тому же используется не GetModuleFileName(NULL. ), а GetModuleFileName(HInstance (*внутри библиотеки*). ) — он возвращает таки абсолютный путь библиотеки.
    Ну и хорошо. Но зачем каждый раз определять? Один раз, еще при загрузке dll, собрали строку с путем\именем файла и вперед. Еще проще писать логи в %Temp% и не париться Или это уже чисто академический интерес?

    Еще проще писать логи в %Temp% и не париться Или это уже чисто академический интерес?

    а собирать потом эти логи из темпов . цати машин я как должна?

    64. olivenoel , 13.06.2013 10:11
    65. AleX_SPb , 13.06.2013 11:12
    olivenoel
    А Вы им %Temp% какой-нибудь одинаковый назначьте. Заодно и мусор оттуда выгребать будет проще Серьезно.
    На самом деле, определяйте путь 1 раз — при загрузке. И не парьтесь.
    Чудес-то не бывает. У Вас найденный путь в лог пишется? Похоже, GetModuleFileName у Вас возвращает ошибку и/или пустую строку в результате. И тогда понятно, что файл будет писаться в текущий каталог.

    . У Вас найденный путь в лог пишется? Похоже, GetModuleFileName у Вас возвращает ошибку и/или пустую строку в результате. И тогда понятно, что файл будет писаться в текущий каталог.

    похоже то, похоже. Вопрос только почему? По описанию на сайте $MS может вернуть не весь путь, но не пустую строку. Да и почему сначала правильно, потом тремя минутами позже неправильно, а потом снова правильно?

    66. olivenoel , 13.06.2013 11:57

    цитата (olivenoel): похоже то, похоже. Вопрос только почему?

    Пресловутые 260 символов ?

    цитата (olivenoel): По описанию на сайте $MS может вернуть не весь путь, но не пустую строку.

    M$ не может, а вот глючный дельфийский враппер на эту функцию вполне может.

    PS: Про Addr не забудте — как вариант.

    67. vertur , 13.06.2013 12:38

    цитата (olivenoel): похоже то, похоже. Вопрос только почему?

    Пресловутые 260 символов ?

    так место расположения программы не меняется, в зависимости от вызова той или иной функции. Почему «тут играть, тут не играть»?

    цитата (olivenoel): По описанию на сайте $MS может вернуть не весь путь, но не пустую строку.

    M$ не может, а вот глючный дельфийский враппер на эту функцию вполне может.

    PS: Про Addr не забудте — как вариант.

    Как страшно жить (с). никому нельзя верить.

    68. olivenoel , 13.06.2013 12:49
    69. AleX_SPb , 13.06.2013 19:09
    olivenoel
    Может и с самого начала неправильно. При запуске текущий каталог — каталог проги. И пишет разумеется в него, типа куда надо. Потом меняется и.

    цитата: AleX_SPb:
    olivenoel
    Может и с самого начала неправильно. При запуске текущий каталог — каталог проги. И пишет разумеется в него, типа куда надо. Потом меняется и.

    Призыв к LastDelimiter () не компилируется

    Delphi 7 — если я делаю новый проект и добавить строку

    Если я пытаюсь использовать LastDelimiter в моем проекте не удается.

    Я начал с stamenet сравнения LastDelimter(#$D#$A, myString) и вэнь , что не удалось , я попытался с выше селезенке, который также не удалось.

    Я use SysUtils, но это не было даже необходимо , когда я создал новый проект.

    Если я закомментировать эту строку мой реальный проект строит просто отлично. Если я раскоментировать я получаю «отсутствует оператор или точку с запятой». То же самое, если я двигаю LastDelimiter на уступки, даже на кулак линии функции которой она находится в — это действительно не выглядит как проблема с предыдущей строкой.

    Любые идеи (сокр меня размещение нескольких klocs)?

    Может быть, где-то в Вашей области является глобальным переменным определяется примерно следующим образом:

    Проверьте единицы измерения, используемые в проекте. Она должна быть глобальной переменной, объявленной в секции интерфейса.

    Или это может быть постоянным, объявленная в разделе интерфейса, похожее на это:

    Вы узнали, что такое пространство имен или идентификатор или «сфера столкновения» является сегодня. Некоторые единицы в вашей реализации или интерфейс использует положение, также определяет LastDelimiter, и так как это внутреннее или самое последнее заявление, что делает этот конкретный идентификатор в SysUtils невидимым, пока вы не укажете префикс SysUtils.

    Если у вас есть коллизии пространства имен, вы должны квалифицировать имя функции с именем блока, и он будет затем компилировать для вас:

    По этой причине, некоторые разработчики, которые знакомы с языками, как C # и Java используются всегда требуют пространств имен необходимо указать, что их код выглядит следующим образом:

    Просто потому, что мы не всегда делаем, что в Delphi не означает, что не раз, когда вы должны делать это.

    Илон Маск рекомендует:  Псевдокласс target в CSS
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL
    70. olivenoel , 13.06.2013 19:35