SizeOf — Функция Delphi


Содержание

Длина строки, sizeof, непонятный урок и экономия памяти

Но что-то как-то сомневаюсь, что это может с экономить ОЗУ. Ведь длина строки далеко не обязательно именно 1024 символа. Могут быть и строки из 2-3-х символов. А сколько отводится памяти по умолчанию для этого дела я не знаю. В Паскале в роде 255.

23.02.2014, 18:35

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

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

Экономия памяти
Скажите, будет ли второй вариант кода занимать меньше памяти? 1ый вариант: float a; f(a); .

QDir и экономия памяти
Пишу программу под ARM. Вывожу в таблицу содержимое директории расположенной на Flash с помощью.

Экономия памяти и справочные таблицы
Всем привет! Я не так давно работаю в Access и никак не могу уяснить для себя вот какое дело.

24.02.2014, 01:58 2

Решение

http://www.sources.ru/magazine/0804/delphistr.html
string[n] занимает n+1 байт памяти, нулевой байт определяет длину, за ним n байт описывают полезную инфомацию и всякий мусор в конце.
Скорее всего, sizeof возвращает длину (s[0], length(s)).

Если у Вас данные разреженные (мало строк, многие малой длины), можно использовать array of string, который выделяет память, примерно пропорциональзую объёму полезной информации.

Величина SizeOf

Delphi , Синтаксис , Типы и Переменные

Автор: Dennis Passmore

В Delphi 2.0 при использовании функции sizeof для моей записи:

sizeof (format_name) возвращает 9
sizeof (file_size) возвращает 2
sizeof (fields) возвращает 2

sizeof(f_format) возвращает 14 ; Почему это не возвращает 13 .

Вам необходимо выключить опцию компилятора ‘the Align Record Fields’ (выравнивание полей записи).

Статья Величина SizeOf раздела Синтаксис Типы и Переменные может быть полезна для разработчиков на Delphi и FreePascal.

Комментарии и вопросы

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

Что в Ruby эквивалентно функции Delphi SizeOf?

Я хочу получить количество байтов, занятых переменной или типом в Ruby. Есть ли эквивалентная функция SizeOf в Ruby on Rails?

Как видно из комментариев, в Ruby такого метода нет. Поскольку вы не можете манипулировать памятью непосредственно в Ruby и нет явных «простых» типов (все является объектом), и из-за динамической природы Ruby этот метод в любом случае не будет очень полезен.

Существует исключение сортов с классом String . Поскольку в Ruby строка представляет собой последовательность байтов, String#size будет возвращать размер данных в строке.

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

Также помните, что все классы и объекты могут динамически изменяться в Ruby во время выполнения, что может изменять объем используемой памяти. Просто подумайте о методах, которые добавляют дополнительные переменные экземпляра к объекту, которого раньше не было.

Delphi SizeOf (NativeInt) против C sizeof (int) на x86-64. Почему разница размеров?

Введение

Итак, после долгого времени работы C я вернулся в Delphi и узнал, что в Delphi есть что-то новое. Один из них NativeInt.

К моему удивлению, я обнаружил, что Delphi и C обрабатывают свои типы «native integer» 1 разные для x86-64. Delphi NativeInt, похоже, ведет себя как C void * и указатель Delphi, что противоречит тому, что я ожидал бы от имен.

В Delphi NativeInt имеет размер 64 бит. Выражается в коде:

C имеет только 64-битные указатели. int остается 32 бит. Выраженный в коде 2 :

Даже компилятор Free Pascal 3 согласен с размером NativeInt.

Вопрос

Почему было выбрано 64 бит для Delphi NativeInt и 32 бит для C int?

Конечно, оба действительны в соответствии с документацией/спецификацией языка. Однако «язык позволяет это» на самом деле не является полезным ответом.

Я предполагаю, что это связано со скоростью исполнения, так как сегодня это главный пункт продажи C. Википедия и другие источники говорят, что x86-64 имеют 64-разрядные регистры операндов. Однако они также заявляют, что размер операнда по умолчанию составляет 32 бит. Так что, возможно, операции с 64-разрядными операндами медленнее по сравнению с 32-битными операндами? Или, может быть, 64-битные регистры могут выполнять 2 32-битных операции одновременно? Это причина?

Возможно, есть еще одна причина, по которой создатели компиляторов выбрали эти размеры?

Сноски

  • Я сравниваю Delphi NativeInt с C int, потому что имя/спецификация предполагает, что они имеют схожую цель. Я знаю, что есть также Delphi Integer, который ведет себя как C int на x68 и x86-64 в Delphi.
  • sizeof() возвращает размер как кратное char в C. Однако char — 1 байт на x86-64.
  • Он делает это в режиме Delphi и режиме по умолчанию для NativeInt. Другие целые типы в режиме по умолчанию представляют собой целую другую червь червей.

c sizeof x86-64 delphi pascal

2 ответа

10 Решение David Heffernan [2013-09-12 21:13:00]

NativeInt — это просто целое число, такое же, как указатель. Отсюда и тот факт, что он меняет размер на разных платформах. В документации указано, что:

Размер NativeInt эквивалентен размеру указателя на текущей платформе.

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

Вы можете думать о Delphi NativeInt как о прямом эквиваленте типа .net IntPtr . В C и С++ типы дескрипторов ОС обычно объявляются как void* , который является типом указателя, а не целочисленным типом. Тем не менее, вы бы отлично использовали такой тип, как intptr_t , если хотите.

Вы используете термин «native integer» для описания NativeInt , но, несмотря на имя, очень важно понять, что NativeInt не является нативным целым типом языка. Это будет Integer . Нативный в NativeInt относится к базовой аппаратной платформе, а не к языку.

Тип Delphi Integer , родное целое число, совпадает с типом C int , родным типом соответствующего языка. И в Windows эти типы имеют 32 бита для 32 и 64-битных систем.

Когда дизайнеры Windows начали работать с 64-битными Windows, у них была большая память о том, что произошло, когда int изменился с 16 до 32 бит при переходе от 16-битных до 32-разрядных систем. Это было совсем не весело, хотя это было правильное решение. В этот раз, с 32 до 64, не было никакой веской причины сделать int 64-разрядный тип. Если бы дизайнеры Windows сделали это, это сделало бы портирование гораздо более трудной работой. И поэтому они решили оставить int как 32-разрядный тип.

С точки зрения производительности архитектура AMD64 была разработана для эффективной работы с 32-разрядными типами. Поскольку 32-битное целое число составляет половину от 64-битного целого числа, то использование памяти уменьшается, делая int только 32 бита в 64-битной системе. И это будет иметь преимущество в производительности.

  • Вы заявляете, что «C имеет только 64-битные указатели». Это не так. 32-битный C-компилятор обычно использует плоскую 32-битную модель памяти с 32-разрядными указателями.
  • Вы также говорите: «в Delphi NativeInt размер 64 бит». Опять же, это не так. Он может быть 32 или 64 бита в зависимости от цели.

0 Johan [2013-09-12 23:02:00]

Обратите внимание, что NativeInt не предназначен для взаимодействия с указателем!

Проблема заключается в том, что nativeInt подписан.
Обычно это не, что вы хотите, потому что указатель указывает начало блока данных. Отрицательные смещения будут иметь для вас нарушение прав доступа.
Если у вас указатель, указывающий на середину (потому что вы делаете индексирование или что-то в этом роде), то применяются отрицательные смещения, и NativeInt aka IntPtr появляется в представлении.

Для стандартных указателей (указывающих на начало): используйте UIntPtr, потому что это будет не, когда смещение станет больше 2 ^ 31/2 ^ 63.
(Вероятно, на 32-битной платформе, а не на 64-битной)

По этой причине существует UIntPtr , который точно соответствует C-эквиваленту. UIntPtr является NativeUint .

Использование случаев
Какой из типов, которые вы хотите использовать, зависит от варианта использования.

A: Я хочу самое быстрое целое число → Выберите Int32 aka integer ;
B1: Я хочу иметь целое число для арифметики указателя → Выберите UIntPtr aka NativeUint *.
B2: Я индексирую указатель → Выберите IntPtr aka NativeInt .
C: Я хочу большое целое число, но не хочу большого замедления, которое Int64 дает мне на X86 → выберите NativeInt. D: Я хочу bigint: выберите Int64. (но знайте, что он будет медленным на X86).

*), если вы хотите дать понять читателю вашего кода, что вы возитесь с указателями, вам нужно называть его UIntPtr , очевидно.

Использование процедур и функций в Delphi

Скобки

Добавление скобок при вызове процедур и функций без параметров уже давно не является новинкой в Delphi, тем не менее, эта возможность мало известна. Эту возможность оценят по достоинству те программисты, которым приходится работать на двух языках (C++ и Delphi), так как им не нужно будет постоянно помнить о разнице в синтаксисе при вызове процедур и функций в разных языках. В Delphi оба варианта, приведенные ниже, считаются корректными.

Возможность перегрузки

Впервые концепция перегрузки процедур и функций была реализована в Delphi 4. Она позволяет иметь несколько различных процедур и функций с одинаковыми именами, но с разными списками параметров. Такие процедуры и функции должны быть описаны с применением директивы overload.

procedure Test (I: integer); overload;
procedure Test (S: string); overload;
procedure Test (D: double); overload;

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

Передача параметров

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

Передача параметров по значению

Этот режим передачи параметров применяется по умолчанию. Если параметр передается по значению, создается локальная копия данной переменной, которая и предоставляется для обработки в процедуру или функцию. Посмотрите на следующий пример:

procedure Test(s: string);

При вызове указанной процедуры будет создана копия передаваемой ей в качестве параметра строки s, с которой и будет работать процедура Test. При этом все внесенные в строку изменения никак не отразятся на исходной переменной s.

Однако это не относится к объектам. Например, если в функцию передается переменная (а точнее экземпляр объекта) TStringList, то в данном случае произойдет передача по ссылке (даже если это не указано явно). Этот способ передачи является у большинства самым излюбленным, но в тоже время является и самым не практичным, т.к. для выполнения метода выделяется дополнительная память для создания точной копией передаваемой переменой. Для решения этой проблемы следует использовать один из способов описанных ниже.

Передача параметров по ссылке

Pascal позволяет также передавать параметры в функции или процедуры по ссылке — такие параметры называются параметрами-переменными. Передача параметра по ссылке означает, что функция или процедура сможет изменить полученные значения параметров. Для передачи параметров по ссылке используется ключевое слово var, помещаемое в список параметров вызываемой процедуры или функции.

procedure ChangeMe(var x: longint);
begin
x := 2; // Параметр х изменен вызванной процедурой
end;

Вместо создания копии переменной x, ключевое слово var требует передачи адреса самой переменной x, что позволяет процедуре непосредственно изменять ее значение.

Передача параметров констант

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

procedure Test(const s: string );

Передача открытых массивов

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

function AddEmUp(A: array of integer): integer;

В открытом массиве можно передавать переменные, константы или выражения из констант.

Для получения информации о фактически передаваемом массиве параметров в функции или процедуре могут использоваться функции High, Low и SizeOf.

Object Pascal также поддерживает тип array of const, который позволяет передавать в одном массиве данные различных типов. Синтаксис объявления функций или процедур, использующих такой массив для получения параметров, следующий:

procedure WhatHaveIGot( A: array of const );

Вызвать объявленную выше функцию можно, например, с помощью такого оператора:

procedure WhatHaveIGot( [‘Text’, 10, 5.5, @WhatHaveIGot, 3.14, true, ‘c’] );

При передаче функции или процедуре массива констант все передаваемые параметры компилятор неявно конвертирует в тип TVarRec. Тип данных TVarRec объявлен в модуле System следующим образом:

PVarRec = ^TVarRec;
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
vtWideString: (VWideString: Pointer);
vtInt64: (VInt64: PInt64);
end;

Поле VType определяет тип содержащихся в данном экземпляре записи TVarRec данных и может принимать одно приведенных значений.

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

procedure WhatHaveIGot( A: array of const );
var
i: integer;
TypeStr: string;
begin
for i := Low(A) to High(A) do
begin
case A[i].VType of
vtInteger : TypeStr := ‘Integer’;
vtBoolean : TypeStr := ‘Boolean’;
vtChar : TypeStr := ‘Char’;
vtExtended : TypeStr := ‘Extended’;
vtString : TypeStr := ‘String’;
vtPointer : TypeStr := ‘Pointer’;
vtPChar : TypeStr := ‘PChar’;
vtObject : TypeStr := ‘Object’;
vt ;
vtW ;
vtPW ;
vtAnsiString : TypeStr := ‘AnsiString’;
vtCurrency : TypeStr := ‘Currency’;
vtVariant : TypeStr := ‘Variant’;
vtInterface : TypeStr := ‘Interface’;
vtW ;
vtInt64 : TypeStr := ‘Int64’;
end;
ShowMessage( Format( ‘Array item %d is a %s’, [i, TypeStr] ) );
end;
end;

Значения параметров по умолчанию

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

procedure HasDefVal( s: string; i: integer = 0 );

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

procedure HasDefVal( ‘Hello’, 26 );

Во втором случае можно задать только значение параметра s, а для параметра i использовать значение, установленное по умолчанию:

procedure HasDefVal( ‘Hello’ );

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

  • Параметры, имеющие значения по умолчанию, должны располагаться в конце списка параметров. Параметр без значения по умолчанию не должен встречаться в списке после параметра, имеющего значение по умолчанию.
  • Значения по умолчанию могут присваиваться только параметрам обычных типов, указателям или множествам.
  • Значение по умолчанию может передаваться только по значению либо с модификатором const. Оно не может быть ссылкой или нетипизированным параметром.

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

function Add( I1, I2: integer ): integer;
begin
Result := I1 + I2;
end;

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

function Add( I1, I2: integer; I3: integer = 0 ): integer;
begin
Result := I1 + I2 + I3;
end;

Директива

Директива <$X->запрещает вызов функций как процедур (с игнорированием возвращаемого результата). По умолчанию этот режим включен (<$X+>). Так вот, запомните, использование переменной Result недопустимо при сброшенном флажке опции Extended Syntax, расположенном во вкладке Compiler диалогового окна Project Options, или при указании директивы компилятора <$X->.

В каждой функции языка Objecl Pascal существует локальная переменная с именем Result, предназначенная для размещения возвращаемого значения. Кроме того, вернуть значение из функции можно также путем присвоения значения переменной, имеющей то же имя, что и данная функция. Это стандартный синтаксис языка Pascal, сохранившийся от его предыдущих версий. При использовании в теле функции переменной с ее именем не забывайте, что существуют большие отличия в обработке этого имени — все зависит от того, где она расположена — в левой части оператора присвоения или же в любом другом месте текста функции. Если имя функции указано в левой части оператора присвоения, то предполагается, что назначается возвращаемое функцией значение. Во всех других случаях предполагается, что осуществляется рекурсивный вызов этой функции.

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

Эрудиты

Работа с массивами большой размерности в Delphi

Часто приходиться иметь дело с массивами, длина которых не поддерживается стандартным array of . Мне приходилось иметь дело с массивом данных содержащих более тридцами миллионов записей, каждая из которых была record агрегирующий в себе 7 простых типов данных. При этом вы хотите обрадовать массив не простых типов данных Integer или Double, а массив данных, которые вы объявили при помощи конструкций TMyRecord = Record .. end; или TMy >

Debugger Exception Notification

raised exception class ERangeError with message ‘Range check error’.

Break Continue Help

Debugger Exception Notification

raised exception class EOutOfMemory with message ‘Out of memory’.

Break Continue Help

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

Для решения проблемы воспользуемся функцией

function GetMem ( var StoragePointer : Pointer; StorageSize : Integer ) ;

которая описана в модуле (Юните, Unit) System;

Официальные источники дают следующие описание данной процедуры:

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

Таким образом, для того, что бы воспользоваться этой функцией необходимо знать количество элементов (объектов), которое мы хотим выделить и количество байтов (размер каждого элемента). При этом StoragePointer – должен быть указателем а StorageSize – содержать длину оттельного объекта типа TMyRecord. При объявлении TMyRecord обязательно объявляйте его packed record. Например,

TMyRecord = packed Record .. end;

Директива packed поможет менеджеру памяти наиболее эффективно расположить в памяти каждую запись. Так же воздержитесь от использования в record использования данных не фиксированной длины, например, String вместо него используйте String[255] или любой другой нужной вам длины.

Длину отдельного объекта можно определить при помощи функции SizeOf.

Официальные источники дают следующие описание данной процедуры:

Функция SizeOf возвращает занимаемый размер (в байтах) переменной (Variable) или типа (Type).


То есть для того что бы узнать длину класса TMyRecord необходимо вызвать SizeOf(TMyRecord), при этом следует помнить, что SizeOf(TMyRecord) один раз вычисляет длину класса в байтах и при повторном вызове не будет повторно пересчитывать его длину, так что вызовы SizeOf(TMyRecord) не могут серьезно повлиять на время выполнения программы.

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

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

Операторы

Общие положения

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

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

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

Оператор присваивания

Оператор присваивания (:=) вычисляет выражение, заданное в его правой части, и присваивает результат переменной, идентификатор которой расположен в левой части. Например:

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

В общем случае для числовых типов данных действует следующее правило: выражение с более узким диапазоном возможных значений можно присвоить переменной с более широким диапазоном значений. Например, выражение с типом данных Byte можно присвоить переменной с типом данных Integer, а выражение с типом данных Integer можно присвоить переменной с типом данных Real. В таких случаях преобразование данных из одного представления в другое выполняется автоматически:

Исключение составляет случай, когда выражение принадлежит 32-разрядному целочисленному типу данных (например, Integer), а переменная — 64-разрядному целочисленному типу данных Int64. Для того, чтобы на 32-разрядных процессорах семейства x86 вычисление выражения происходило правильно, необходимо выполнить явное преобразование одного из операндов выражения к типу данных Int64. Следующий пример поясняет сказанное:

Оператор вызова процедуры

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

Составной оператор

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

Частным случаем составного оператора является тело следующей программы:

Хотя символ точки с запятой служит разделителем между операторами и перед словом end может опускаться, мы рекомендуем ставить его в конце каждого оператора (как в примере), чтобы придать программе более красивый вид и избежать потенциальных ошибок при наборе текста.

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

Оператор ветвления if

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

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

Логика работы оператора if очевидна: выполнить оператор 1, если условие истинно, и оператор 2, если условие ложно. Поясним сказанное на примере:

В данном случае значение выражения А > В ложно, следовательно на экране появится сообщение C=8.

У оператора if существует и другая форма, в которой else отсутствует:

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

В результате на экране появится сообщение С=0, поскольку выражение А > В ложно и присваивание С := А + В пропускается.

Один оператор if может входить в состав другого оператора if. В таком случае говорят о вложенности операторов. При вложенности операторов каждое else соответствует тому then, которое непосредственно ему предшествует. Например

Конструкций со степенью вложенности более 2–3 лучше избегать из-за сложности их анализа при отладке программ.

Оператор ветвления case

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

Оператор case вычисляет значение переключателя (который может быть задан выражением), затем последовательно просматривает списки его допустимых значений в поисках вычисленного значения и, если это значение найдено, выполняет соответствующий ему оператор. Если переключатель не попадает ни в один из списков, выполняется оператор, стоящий за словом else. Если часть else отсутствует, управление передается следующему за словом end оператору.

Переключатель должен принадлежать порядковому типу данных. Использовать вещественные и строковые типы в качестве переключателя не допускается.

Список значений переключателя может состоять из произвольного количества констант и диапазонов, отделенных друг от друга запятыми. Границы диапазонов записываются двумя константами через разграничитель в виде двух точек (..). Все значения переключателя должны быть уникальными, а диапазоны не должны пересекаться, иначе компилятор сообщит об ошибке. Тип значений должен быть совместим с типом переключателя. Например:

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

Операторы повтора — циклы

Алгоритм решения многих задач требует многократного повторения одних и тех же действий. При этом суть действий остается прежней, но меняются данные. С помощью рассмотренных выше операторов трудно представить в компактном виде подобные действия в программе. Для многократного (циклического) выполнения одних и тех же действий предназначены операторы повтора (циклы). К ним относятся операторы for, while и repeat. Все они используются для организации циклов разного вида.

Любой оператор повтора состоит из условия повтора и повторяемого оператора (тела цикла). Тело цикла представляет собой простой или структурный оператор. Оно выполняется столько раз, сколько предписывает условие повтора. Различие среди операторов повтора связано с различными способами записи условия повтора.

Оператор повтора for

Оператор повтора for используется в том случае, если заранее известно количество повторений цикла. Приведем наиболее распространенную его форму:

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

Оператор for обеспечивает выполнение тела цикла до тех пор, пока не будут перебраны все значения параметра цикла от начального до конечного. После каждого повтора значение параметра цикла увеличивается на единицу. Например, в результате выполнения следующей программы на экран будут выведены все значения параметра цикла (от 1 до 10), причем каждое значение — в отдельной строке:

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

В качестве начального и конечного значений параметра цикла могут использоваться выражения. Они вычисляются только один раз перед началом выполнения оператора for. В этом состоит важная особенность цикла for в языке Delphi, которую следует учитывать тем, кто имеет опыт программирования на языках C/C++.

После выполнения цикла значение параметра цикла считается неопределенным, поэтому в предыдущем примере нельзя полагаться на то, что значение переменной I равно 10 при выходе из цикла.

Вторая форма записи оператора for обеспечивает перебор значений параметра цикла не по возрастанию, а по убыванию:

Например, в результате выполнения следующей программы на экран будут выведены значения параметра цикла в порядке убывания (от 10 до 1):

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

Оператор повтора repeat

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

Тело цикла выполняется до тех пор, пока условие завершения цикла (выражение булевского типа) не станет истинным. Оператор repeat имеет две характерные особенности, о которых нужно всегда помнить:

  • между словами repeat и until может находиться произвольное число операторов без операторных скобок begin и end;
  • так как условие завершения цикла проверяется после выполнения операторов, цикл выполняется, по крайней мере, один раз.

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

Часто бывает, что условие выполнения цикла нужно проверять перед каждым повторением тела цикла. В этом случае применяется оператор while, который, в отличие от оператора repeat, содержит условие выполнения цикла, а не условие завершения.

Оператор повтора while

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

Перед каждым выполнением тела цикла происходит проверка условия. Если оно истинно, цикл выполняется и условие вычисляется заново; если оно ложно, происходит выход из цикла, т.е. переход к следующему за циклом оператору. Если первоначально условие ложно, то тело цикла не выполняется ни разу. Следующий пример показывает использование оператора while для вычисления суммы S = 1 + 2 + .. + N, где число N задается пользователем с клавиатуры:

Прямая передача управления в операторах повтора

Для управления работой операторов повтора используются специальные процедуры-операторы Continue и Break, которые можно вызывать только в теле цикла.

Процедура-оператор Continue немедленно передает управление оператору проверки условия, пропуская оставшуюся часть цикла (рисунок 4):

Рисунок 4. Схема работы процедуры-оператора Continue

Процедура-оператор Break прерывает выполнение цикла и передает управление первому оператору, расположенному за блоком цикла (рисунок 5):

Рисунок 5. Схема работы процедуры-оператора Break

Оператор безусловного перехода

Среди операторов языка Delphi существует один редкий оператор, о котором авторы сперва хотели умолчать, но так и не решились. Это оператор безусловного перехода goto («перейти к»). Он задумывался для того случая, когда после выполнения некоторого оператора надо выполнить не следующий по порядку, а какой-либо другой, отмеченный меткой, оператор.

Метка — это именованная точка в программе, в которую можно передать управление. Перед употреблением метка должна быть описана. Раздел описания меток начинается зарезервированным словом label, за которым следуют имена меток, разделенные запятыми. За последним именем ставится точка с запятой. Типичный пример описания меток:

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

Эта программа будет выполняться бесконечно, причем второй оператор Write не выполнится ни разу!

Внимание! В соответствии с правилами структурного программирования следует избегать применения оператора goto, поскольку он усложняет понимание логики программы. Оператор goto использовался на заре программирования, когда выразительные возможности языков были скудными. В языке Delphi без него можно успешно обойтись, применяя условные операторы, операторы повтора, процедуры Break и Continue, операторы обработки исключений (последние описаны в главе 4).

Подпрограммы

Общие положения

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

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

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

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

Все процедуры и функции языка Delphi подразделяются на две группы: встроенные и определенные программистом.

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

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

Стандартные подпрограммы

Abs(X) Возвращает абсолютное значение аргумента X. Exp(X) Возвращает значение e x . Ln(X) Возвращает натуральный логарифм аргумента X. Pi Возвращает значение числа ?. Sqr(X) Возвращает квадрат аргумента X. Sqrt(X) Возвращает квадратный корень аргумента X.

Выражение

Результат Abs(–4) 4 Exp(1) 2.17828182845905 Ln(Exp(1)) 1 Pi 3.14159265358979 Sqr(5) 25 Sqrt(25) 5

ArcTan(X) Возвращает угол, тангенс которого равен X. Cos(X) Возвращает косинус аргумента X (X задается в радианах). Sin(X) Возвращает синус аргумента X (X задается в радианах).

Выражение

Результат ArcTan(Sqrt(3)) 1.04719755119660 Cos(Pi/3) 0.5 Sin(Pi/6) 0.5

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

Функции выделения целой или дробной части

Frac(X) Возвращает дробную часть аргумента X. Int(X) Возвращает целую часть вещественного числа X. Результат принадлежит вещественному типу. Round(X) Округляет вещественное число X до целого. Trunc(X) Возвращает целую часть вещественного числа X. Результат принадлежит целому типу.

Выражение

Результат Frac(2.5) 0.5 Int(2.5) 2.0 Round(2.5) 3 Trunc(2.5) 2

Функции генерации случайных чисел

Random Возвращает случайное вещественное число в диапазоне 0 ? X Входной Передается копия значения const Входной Передается копия значения либо ссылка на значение в зависимости от типа данных out Выходной Передается ссылка на значение var Входной и выходной Передается ссылка на значение

Таблица 10. Способы передачи параметров

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

Опущенные параметры процедур и функций

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

Для параметра InitValue задано стандартное значение, поэтому его можно опустить при вызове процедуры Initialize:

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

Перегрузка процедур и функций

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

В языке Delphi существует возможность дать двум и более процедурам (функциям) одинаковые идентификаторы при условии, что все такие процедуры (функции) отличаются списком параметров. Такая возможность называется перегрузкой. Для указания того, что процедура (функция) перегружена, служит стандартная директива overload. С ее помощью вышеприведенный пример можно переписать следующим образом:

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

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

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

Очевидно, что одно и то же число может интерпретироваться и как Longint, и как Shortint (например, числа 5 и –1). Логика компилятора в таких случаях такова: если значение фактического параметра попадает в диапазон значений нескольких типов, по которым происходит перегрузка, то компилятор выбирает процеудуру (функцию), у которой тип параметра имеет меньший диапазон значений. Например, вызов Print(5) будет означать вызов того варианта процедуры, который имеет тип параметра Shortint. А вот вызов Print(150) будет означать вызов того варианта процедуры, который имеет тип параметра Longint, т.к. число 150 не вмещается в диапазон значений типа данных Shortint.

Поскольку в нынешней версии среды Delphi обощенный тип данных Integer совпадает с фундаментальным типом данных Longint, следующий вариант перегрузки является ошибочным:

Такая же ошибка возникает при использовании пользовательских типов данных, определенных через общий базовый тип.

Что делать в тех случаях, когда такая перегрузка просто необходима? Для этого пользовательский тип данных необходимо создавать с использованием ключевого слова type:

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

Вызов процедуры Increment с одним параметром вызовет неоднозначность:

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

Соглашения о вызове подпрограмм

В различных языках программирования используются различные правила вызова подпрограмм. Для того чтобы из программ, написанных на языке Delphi, возможно было вызывать подпрограммы, написанные на других языках (и наоборот), в языке Delphi существуют директивы, соответствующие четырем известным соглашениям о вызове подпрограмм: register, stdcall, pascal, cdecl.

Директива, определяющая правила вызова, помещается в заголовок подпрограммы, например:

Директива register задействует регистры процессора для передачи параметров и поэтому обеспечивает наиболее эффективный способ вызова подпрограмм. Эта директива применяется по умолчанию. Директива stdcall используется для вызова стандартных подпрограмм операционной системы. Директивы pascal и cdecl используются для вызова подпрограмм, написанных на языках Delphi и C/C++ соответственно.

Рекурсивные подпрограммы

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

Приведенная ниже программа содержит функцию Factorial для вычисления факториала. Напомним, что факториал числа определяется через произведение всех натуральных чисел, меньших либо равных данному (факториал числа 0 принимается равным 1):

Из определения следует, что факториал числа X равен факториалу числа (X – 1), умноженному на X. Математическая запись этого утверждения выглядит так:

Последняя формула используется в функции Factorial для вычисления факториала:

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

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

Упреждающее объявление процедур и функций

Для реализации алгоритмов с косвенной рекурсией в языке Delphi предусмотрена специальная директива предварительного описания подпрограмм forward. Предварительное описание состоит из заголовка подпрограммы и следующего за ним зарезервированного слова forward, например:

Заметим, что после такого первичного описания в полном описании процедуры или функции можно не указывать список формальных параметров и тип возвращаемого значения (для функции). Например:

Процедурные типы данных

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

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

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

Дополнительная информация

За дополнительной информацией обращайтесь в компанию Interface Ltd.

Поиск этой функции в delphi MD5 (tmpBuffer, sizeof (opera_salt) + DES_KEY_SZ, hashSignature1);

Я переношу операнд из уже существующего кода на С++

Я ударил эту функцию

Где я могу получить этот блок MD5?

steve0, код в вашей ссылке на реализацию md5 openssl, вы можете найти оригинальное объявление функции MD5, используемой в коде в этой ссылке

вы можете использовать блок MessageDigest_5 (начиная с Delphi 2007) для вычисления md5 для буфера или класса TIdHashMessageDigest5 из компоненты indy.


SizeOf — Функция Delphi

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

В Части I этой серии было сказано, что Delphi 2009 по умолчанию будет использовать строку, основанную на UTF-16. В результате некоторые части существующего кода могут потребовать изменений. В основном, большая часть существующего кода будет прекрасно работать в Delphi 2009. Как Вы увидите, основные изменения в коде касаются только очень специфических, даже эзотерических, моментов. Как бы там ни было, нужно рассмотреть те особые части кода, которые, скорее всего, придется редактировать. Также нужно будет проверить результаты работы такого кода, чтобы убедиться, что он правильно работает с UnicodeString.

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

  • считающий, что SizeOf(Char)=1;
  • считающий, что длина строки равна количеству байт в строке;
  • который пишет и читает строки из какого-либо постоянного хранилища или использует строку как буфер для данных

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

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

Части, которые должны «работать прямо так»

Здесь рассказывается о тех частях кода, которые будут продолжать работать и не потребуют никаких изменений для корректной работы с новой UnicodeString. VCL и RTL были полностью обновлены, чтобы работать в Delphi 2009 так, как и всегда. С маленькими-маленькими оговорками так оно и есть. К примеру, TStringList теперь полностью поддерживает Юникод, и весь существующий код, в котором используется TStringList, должен работать так же, как и раньше. Кроме того, TStringList был улучшен для работы специально с Юникодом, поэтому если Вы хотите использовать новую функциональность, Вы можете это сделать, но если это Вам не нужно — можете вообще о ней не думать.

Обычное использование строковых типов

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

Runtime Library

Дополнения к Runtime Library были подробно рассмотрены в Части II.

В той статье не упоминался новый модуль, добавленный в RTL — AnsiString.pas. Этот модуль существует для обратной совместимости с кодом, который использует или требует для своей работы AnsiString.

Код Runtime Library выполняется как обычно, и в основном не требует изменений. Части, которые нужно изменить, описаны ниже.

Вся VCL поддерживает Юникод. Все существующие VCL-компоненты работают с момента установки, как всегда и было. Практически весь код, использующий VCL, будет нормально работать. Мы проделали огромную работу, чтобы быть уверенными, что VCL совместим как со старым кодом, так и с Юникодом. Обычный VCL-код, не выполняющий никаких особенных манипуляций со строками, будет работать как и раньше.

Индексация в строках

Индексация в строках работает точно так же, как и прежде, поэтому код, индексирующий элементы строк, не требует изменений:

Length/Copy/Delete/SizeOf для строк

Функция Copy будет работать, как всегда, без изменений. То же самое относится к Delete и всем остальным процедурам работы со строками, основанными на SysUtils.

Вызов Length(SomeString), как и всегда, вернет количество элементов в переданной строке.

Вызов SizeOf для любого идентификатора строки вернет 4, так как все строковые объявления — это ссылки и размер указателя равен 4.

Вызов Length для любой строки вернет количество элементов в этой строке.

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

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

Работа с указателями для PChar

Работа с указателями для PChar будет выполняться, как и раньше. Компилятору известен размер PChar, поэтому код, подобный приведенному ниже, будет работать, как и ожидается:

Этот код будет работать точно так же, как и в предыдущих версиях Delphi, но, конечно, с другими типами данных: PChar это теперь PWideChar и MyString — это теперь UnicodeString.

ShortString

ShortString осталась неизменной, как по функциям, так и по объявлению, она будет работать, как и раньше.

Объявления ShortString выделяют буфер для заданного количества AnsiChar»ов. Такой код:

выведет на экран следующее:

Обратите внимание, что общий размер алфавита — 26, это говорит о том, что переменная содержит AnsiChar»ы.

Рассмотрим также и такой код:

Это запись будет расположена в памяти точно так же, так и раньше — это будет запись из двух AnsiString»ов, содержащих AnsiChar»ы. Если у Вас есть File of Rec из записей, содержащих ShortString»и, то приведенный выше код будет работать, как и раньше, и любое чтение или запись не потребует никаких изменений.

Однако помните, что Char — это теперь WideChar, поэтому если Вы используете код, который читает такие записи из файла и потом делаете что-то вроде:

то Вы должны помнить, что SomeChar превратит AnsiChar в String1[3] в WideChar. Если Вам нужно, чтобы этот код работал, как раньше, измените объявление SomeChar:

Части, которые должны быть проверены

В этой части статьи описаны различные конструкции, которые должны быть проверены в существующем коде для совместимости с Юникодом. Поскольку Char теперь является WideChar, данные о байтовом размере символьного массива или строки могут быть неверными. Ниже приведены несколько примеров конструкций кода, которые нужно изучить, чтобы убедиться в их совместимости с новым типом UnicodeString.

SaveToFile/LoadFromFile

Вызовы SaveToFile и LoadFromFile можно было бы отнести к предыдущей части статьи (Части, которые должны «работать прямо так»), если бы они выполняли чтение и запись так же, как они делали это раньше. Однако Вам может понадобиться использование новых перегруженных версий этих процедур, если Вы решили работать с Юникод-данными.

К примеру, TStrings теперь включает следующий набор перегруженных методов:

Второй метод — это новая перегрузка, принимающая кодировку в качестве параметра, который задает, каким образом данные будут записаны в файл. (В Части II Вы можете прочитать описание типа TEncoding.) Если Вы вызовете первый метод, строковые данные будут записаны так же, как это делалось обычно — как ANSI-данные. Благодаря этому уже существующий код будет работать точно так же, как и всегда.

Однако если Вам нужно записать текст в формате Юникод, то нужно вызвать второй вариант метода, передав ему в параметре соответствующее значение типа TEncoding. Если не сделать этого, строки будут записаны как ANSI-данные, что, скорее всего, приведет к потере информации.

Таким образом, наилучший способ в этом случае — проанализировать вызовы SaveToFile и LoadFromFile и добавить к ним второй параметр, чтобы показать, каким образом нужно сохранить или загрузить данные. Если Вы считаете, что никогда не будете добавлять или использовать Юникод-строки, то можете оставить все, как есть.

Использование функции Chr

Существующий код, превращающий значение типа integer в Char может использовать функцию Chr. Это может привести к следующей ошибке:

Если в коде, использующем функцию Chr, имеется присвоение ее результата переменной типа AnsiChar, то эту ошибку можно легко исключить, заменив функцию Chr преобразованием в тип AnsiChar.

То есть, такой код:

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

Символьные множества

Наверное, самой распространенной идиомой, которая может создать проблемы компилятору, является использование символов в множествах. Раньше, когда символ занимал один байт, хранение символов в множествах не создавало никаких трудностей. Но теперь Char объявлен как WideChar, и поэтому больше не может храниться в множестве. Поэтому, если у Вас есть код наподобие этого:

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

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

Функция CharInSet вернет булевское значение и код скомпилируется без предупреждений компилятора.

Использование строк в качестве буферов данных

Часто при работе с данными строки используются как буферы. Это делается часто, потому что это просто — работа со строками проста и понятна. Однако, существующий код, который так делает, в большинстве случаев потребует дополнительной настройки, исходя из того, что string это теперь UnicodeString.

Есть несколько способов разобраться с кодом, который использует строки как буферы данных. Первый — это просто объявить переменную, используемую в качестве буфера, как AnsiString вместо string. Если для работы с байтами буфера в коде используются Char»ы — объявите эти переменные как AnsiChar. Если Вы выберете этот путь, весь Ваш код будет работать, как и прежде, но Вы должны помнить: все переменные, работающие с таким строковым буфером, должны быть ANSI-типа.

Второй, более верный путь, — это преобразовать Ваш буфер из строкового типа в массив байтов или TBytes. Тип TBytes был создан специально для этой цели и работает так же, как и раньше, если использовался тип string.

Вызов SizeOf для буферов

Вызов SizeOf при использовании символьных массивов должен быть проверен на корректность. Рассмотрим следующий код:

Вот что этот код выведет в Memo1:

В этом коде Length вернет количество символов в данной строке (плюс терминальный символ), а SizeOf вернет количество байтов, использованных этим массивом, в данном случае 34, то есть по два байта на символ. В предыдущих версиях Delphi этот код вернул бы 17 в обоих случаях.

Использование FillChar

Вызов FillChar также нужно проверить при работе со строками и символами. Рассмотрим следующий код:

Length возвращает размер в символах, но FillChar ожидает, что Count будет в байтах. В этом случае вместо Length нужно использовать SizeOf (или нужно умножить Length на размер Char).

Кроме того, так как по умолчанию размер Char равен 2, FillChar заполнит строку байтами, а не символами, как раньше.

Это заполнит массив символами с кодом не $09, а $0909. Чтобы получить прежний результат, код нужно изменить:

Использование буквенных символов

распознает символ Евро и в итоге даст True в большинстве кодовых страниц ANSI. Однако в Delphi 2009 он даст False, так как #128 — это символ Евро в большинстве ANSI-страниц, а в Юникоде это — управляющий символ. В Юникоде символом Евро имеет код #$20AC.

При переходе на Delphi 2009 разработчикам следует заменить все коды символов со #128 по #255 на их буквенные значения, тогда:

будет работать так же, как #128 в ANSI, но будет нормально функционировать (то есть распознавать символ Евро) в Delphi 2009 (где ‘€’ имеет код #$20AC)

Использование Move

Следует проанализировать использование функции Move при работе со строками или символьными массивами. Рассмотрим следующий код:

Length возвращает размер в символах, но Move ожидает, что Count будет в байтах. В этом случае вместо Length нужно использовать SizeOf (или нужно умножить Length на размер Char).

Методы Read/ReadBuffer для TStream

Вызов TStream.Read/ReadBuffer также следует рассмотреть, если используются строки или символьные массивы. Рассмотрим следующий код:

Примечание: работа зависит от формата читаемых данных. Смотрите описание нового класса TEncoding, приведенное выше, для получения сведений о правильном кодировании текста в Stream»е.

Write/WriteBuffer

Как и в случае Read/ReadBuffer, использование TStream.Write/WriteBuffer следует проверить, если используются строки или символьные массивы. Рассмотрим следующий код:

Примечание: работа зависит от формата читаемых данных. Смотрите описание нового класса TEncoding, приведенное выше, для получения сведений о правильном кодировании текста в Stream»е.

LeadBytes

Замените такой код:

использованием функции IsLeadChar:

TMemoryStream

В тех случаях, когда для записи текста в файл используется TMemoryStream, важной является запись Byte Order Mark (BOM) в качестве начальных данных файла. Вот пример записи BOM в файл:

TStringStream

TStringStream теперь происходит от нового типа TByteStream. TByteStream добавляет свойство Bytes, дающее прямой доступ к байтам из TStringStream. TStringStream продолжает работать, как и всегда, за исключением того, что строка, которую он хранит, теперь является Юникод-строкой.

MultiByteToWideChar

Вызовы MultiByteToWideChar можно просто убрать и заменить простым присвоением. Пример использования MultiByteToWideChar:

А после перехода к Юникоду этот код был изменен, чтобы компилироваться как для ANSI, так и для Юникода:

SysUtils.AppendStr

Этот метод может использовать только AnsiString, нет его перегруженной версии для UnicodeString.

Замените вызовы вроде этого:

Или, еще лучше, используйте новый класс TStringBuilder для соединения строк.

GetProcAddress

При вызове GetProcAddress всегда следует использовать PAnsiChar (в SDK нет функции с суффиксом «W»). Например:

Примечание: Windows.pas содержит перегруженный метод, который выполняет это преобразование.

Использование преобразований к PChar() для работы с указателями при указании на не символьные типы

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

В приведенном выше куске кода Node не содержит символьных данных. Он преобразовывается к PChar только для доступа к данным, расположенным через заданное число байт после Node. Раньше это работало, так как SizeOf(Char) = SizeOf(Byte). Теперь это работать не будет. Чтобы сделать работу кода правильной, следует использовать PByte вместо PChar. Если оставить все без изменений, Result будет указывать на некорректные данные.

Параметры с вариантными массивами

Если Ваш код использует TVarRec для работы с параметром — вариантным массивом — возможно, Вам придется отредактировать его для работы с UnicodeString. Для этого теперь есть новый тип vtUnicodeString, хранящий данные из UnicodeString. Рассмотрим следующий кусок из DesignIntf.pas, показывающий, в каком случае следует добавить новый код для работы с UnicodeString.

CreateProcessW

Юникод-версия CreateProcess (CreateProcessW) работает немного иначе, нежели ANSI-версия. Цитата MSDN из описания параметра lpCommandLine:

«Юникод-версия это функции, CreateProcessW, может изменить содержимое этой строки. Таким образом, этот параметр не может указывать на память только-для-чтения (то есть быть константной переменной или символьной строкой). Если этой параметр — константа, функция может вызвать ошибку доступа.»

Из-за этого существующий код, вызывающий CreateProcess, может начать выдавать ошибки доступа (Access Violations) после компиляции в Delphi 2009.

Примеры такого кода:

Передача строковой константы


Передача константного выражения


Передача строки с числом ссылок (Reference Count) -1:


Код для проверки

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

  • найти любое использование «of Char» или «of AnsiChar», чтобы проверить, что буферы корректно работают с Юникодом;
  • найти «string[» и проверить, что символ, полученный по ссылке, заносится в Char (то есть в WideChar).
  • проверить неявную работу с AnsiString, AnsiChar и PAnsiChar, убедиться, что она по-прежнему нужна и правильно работает;
  • найти неявное использование ShortString, убедиться, что оно по-прежнему требуется и правильно работает;
  • найти вызовы Length( и проверить, чтобы там не подразумевалось, что Length это то же самое, что SizeOf;
  • найти вызовы Copy(, Seek(, Pointer(, AllocMem( и GetMem( и проверить, чтобы они правильно работали со строками или символьными массивами.

Это типичные конструкции, которые, возможно, придется изменить для поддержки нового типа UnicodeString.

Заключение

Вот и все потенциально узкие места в коде, которые нужно проверить на правильность для жизни в мире Юникода. Большая часть Вашего кода по-прежнему будет работоспособной. Большинство предупреждений, которые получит Ваш код от компилятора, можно легко исправить. Значительная часть моментов, которые нужно проверить, встречается достаточно редко, поэтому практически весь Ваш код будет работать просто прекрасно.

Length() vs Sizeof() on Unicode strings

Quoting the Delphi XE8 help:

For single-byte and multibyte strings, Length returns the number of bytes used by the string. Example for UTF-8:

For Unicode (WideString) strings, Length returns the number of bytes divided by two.

This arises important questions:

  1. Why the difference in handling is there at all?
  2. Why Length() doesn’t do what it’s expected to do, return just the length of the parameter (as in, the count of elements) instead of giving the size in bytes in some cases?
  3. Why does it state it divides the result by 2 for Unicode (UTF-16) strings? AFAIK UTF-16 is 4-byte at most, and thus this will give incorrect results.

1 Answer 1

Length returns the number of elements when considering the string as an array.

  • For strings with 8 bit element types (ANSI, UTF-8) then Length gives you the number of bytes since the number of bytes is the same as the number of elements.
  • For strings with 16 bit elements (UTF-16) then Length is half the number of bytes because each element is 2 bytes wide.

Your string ‘1¢’ has two code points, but the second code point requires two bytes to encode it in UTF-8. Hence Length(Utf8String(‘1¢’)) evaluates to three.

You mention SizeOf in the question title. Passing a string variable to SizeOf will always return the size of a pointer, since a string variable is, under the hood, just a pointer.

To your specific questions:

Why the difference in handling is there at all?

There is only a difference if you think of Length as relating to bytes. But that’s the wrong way to think about it Length always returns an element count, and when viewed that way, there behaviour is uniform across all string types, and indeed across all array types.

Why Length() doesn’t do what it’s expected to do, return just the length of the parameter (as in, the count of elements) instead of giving the size in bytes in some cases?

It does always return the element count. It just so happens that when the element size is a single byte, then the element count and the byte count happen to be the same. In fact the documentation that you refer to also contains the following just above the excerpt that you provided: Returns the number of characters in a string or of elements in an array. That is the key text. The excerpt that you included is meant as an illustration of the implications of this italicised text.

Why does it state it divides the result by 2 for Unicode (UTF-16) strings? AFAIK UTF-16 is 4-byte at most, and thus this will give incorrect results.

UTF-16 character elements are always 16 bits wide. However, some Unicode code points require two character elements to encode. These pairs of character elements are known as surrogate pairs.

You are hoping, I think, that Length will return the number of code points in a string. But it doesn’t. It returns the number of character elements. And for variable length encodings, the number of code points is not necessarily the same as the number of character elements. If your string was encoded as UTF-32 then the number of code points would be the same as the number of character elements since UTF-32 is a constant sized encoding.

A quick way to count the code points is to scan through the string checking for surrogate pairs. When you encounter a surrogate pair, count one code point. Otherwise, when you encounter a character element that is not part of a surrogate pair, count one code point. In pseudo-code:

Another point to make is that the code point count is not the same as the visible character count. Some code points are combining characters and are combined with their neighbouring code points to form a single visible character or glyph.

Finally, if all you are hoping to do is find the byte size of the string payload, use this expression:

This expression works for all types of string.

Илон Маск рекомендует:  Что такое код getmapmode
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL