FillChar — Процедура Delphi


Содержание

FillChar — Процедура Delphi

Описание
Процедура заполняет переменную X некоторым количеством байт, значение которых определено в параметре Value, а количество — в параметре Count. В зависимости от типа переменной X, параметр Value может иметь тип Byte или Char. Данную процедуру удобно использовать для заполнения массивов одинаковыми значениями.
Для того, чтобы не допустить возникновение ошибок, необходимо помнить, что процедура FillChar при заполнении переменной X не производит проверку выхода за пределы диапазона памяти, выделенного под переменную.

Пример
var
Mas: array[0..100] of Byte;
begin
FillChar( Mas, SizeOf(Mas), 0); // заполняет массив нулями
end;

Почему переменная валюты рассматривается как константа с FillChar в Delphi?

следующий код должен компилироваться и компилируется со многими другими типами.
Однако компилятор сообщает об ошибке «постоянный объект не может быть передан как параметр var» — несмотря на переменная совершенно очевидно, будучи переменная.

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

Я подозреваю, что это как-то связано с тем, что FillChar является магической процедурой компилятора и это Dest — это нетипизированное var . FillChar это единственная процедура, которую я нашел с этой проблемой.

  • что вызывает эту проблему?
  • затронуты ли другие типы?

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

редактировать

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

Дэвид предоставляет некоторые хорошие обходные приемы.

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

Почему большинство примеров Delphi используют FillChar () для инициализации записей?

Мне просто интересно, почему большинство примеров Delphi используют FillChar () для инициализации записей.

Здесь ( http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html ) моя заметка на эту тему. ИМО, лучше объявить константу со значением по умолчанию.

8 ответов

Исторические причины, в основном. FillChar () восходит к дням Турбо Паскаля и использовался для таких целей. Название на самом деле немного неправильное, потому что, хотя оно говорит Fill Char (), на самом деле это Fill Byte (). Причина в том, что последний параметр может принимать символ или байт. Таким образом, FillChar (Foo, SizeOf (Foo), # 0) и FillChar (Foo, SizeOf (Foo), 0) эквивалентны. Еще одним источником путаницы является то, что по состоянию на Delphi 2009 FillChar по-прежнему заполняет только байты, хотя Char эквивалентен WideChar. Рассматривая наиболее распространенное использование FillChar, чтобы определить, использует ли большинство людей FillChar для фактического заполнения памяти символьными данными или просто использует его для инициализации памяти с некоторым заданным байтовым значением, мы обнаружили, что именно последний случай доминировал в его использовании. а не первый. После этого мы решили сохранить байты FillChar.

Это правда, что очистка записи с помощью FillChar, которая содержит поле, объявленное с использованием одного из «управляемых» типов (строки, вариант, интерфейс, динамические массивы), может быть небезопасной, если не используется в надлежащем контексте. Однако в приведенном вами примере безопасно вызывать FillChar для локально объявленной переменной записи, если это первое, что вы когда-либо делаете с записью в этой области . Причина в том, что компилятор сгенерировал код для инициализации строкового поля в записи. Это будет уже установить строковое поле в 0 (ноль). Вызов FillChar (Foo, SizeOf (Foo), 0) просто перезапишет всю запись 0 байтами, включая строковое поле, которое уже равно 0. Использование FillChar для переменной записи после того, как значение было присвоено строковому полю, не рекомендуется , Использование вашей техники инициализированных констант является очень хорошим решением этой проблемы, потому что компилятор может сгенерировать правильный код, чтобы гарантировать, что существующие значения записи будут правильно завершены во время присваивания.

Если у вас Delphi 2009 и более поздние версии, используйте вызов Default для инициализации записи.

Преимущество использования вызова по Default(TSomeType) заключается в том, что запись завершается до ее очистки. Нет утечек памяти и нет явных опасных низкоуровневых вызовов FillChar или ZeroMem. Когда записи сложны, возможно, содержат вложенные записи и т. Д., Исключается риск ошибок.

Ваш метод инициализации записей может быть сделан еще проще:

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

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

FillChar прекрасно подходит, чтобы убедиться, что вы не получите мусора в новой неинициализированной структуре (запись, буфер, массив . ).
Его не следует использовать для «сброса» значений, не зная, что вы сбрасываете.
Не более, чем просто написание MyObject := nil и ожидание предотвращения утечки памяти.
В частности, все управляемые типы должны внимательно наблюдаться.
Смотрите функцию финализации .

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

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

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

Традиционно символ представляет собой один байт (больше не имеет значения для Delphi 2009), поэтому при использовании fillchar с # 0 инициализируется выделенная память, так что он содержит только нули, или байт 0, или бин 00000000.

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

Этот вопрос имеет более широкое значение, которое было в моей голове целую вечность. Я тоже был воспитан на использовании FillChar для записей. Это хорошо, потому что мы часто добавляем новые поля в запись (data) и, конечно, FillChar (Rec, SizeOf (Rec), # 0) заботится о таких новых полях. Если мы «сделаем это правильно», нам придется перебирать все поля записи, некоторые из которых являются перечисляемыми типами, некоторые из которых могут быть самими записями, а полученный код будет менее читаемым, а также, возможно, ошибочным, если мы не добавим new записывать поля к нему прилежно. Строковые поля являются общими, поэтому FillChar теперь нет-нет. Несколько месяцев назад я обошел и преобразовал все свои FillChars на записях со строковыми полями в итеративную очистку, но я не был доволен решением и задался вопросом, есть ли аккуратный способ выполнения Fill для простых типов (ordinal / float) и ‘Finalize’ для вариантов и строк?

Вопрос также может быть:

В Windows нет функции ZeroMemory . В заголовочных файлах ( winbase.h ) это макрос, который в мире C поворачивается и вызывает memset:

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

Delphi-эквивалент memset — это FillChar .

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

Так что во многих отношениях вызов FillChar — это микрооптимизация производительности, которой больше не существует, если встроен ZeroMemory:

Бонус Чтение

Windows также содержит функцию SecureZeroMemory . Он делает то же самое, что и ZeroMemory . Если он делает то же самое, что и ZeroMemory , почему он существует?

Потому что некоторые умные компиляторы C / C ++ могут признать, что установка памяти в 0 до того, как избавиться от памяти, — пустая трата времени — и оптимизировать вызов ZeroMemory .

Я не думаю, что компилятор Delphi такой же умный, как многие другие компиляторы; поэтому нет необходимости в SecureFillChar .

Delphi in a Nutshell by Ray Lischner

Stay ahead with the world’s most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to v > Start Free Trial

No credit card required

Syntax

Description

FillChar fills a variable with Count bytes, copying Fill as many times as needed. Fill can be a Byte -sized ordinal value. FillChar is not a real procedure.

Tips and Tricks

Note that Buffer is not a pointer. Do not pass the address of a variable, but pass the variable itself. If you dynamically allocate memory, be sure to dereference the pointer when calling FillChar .

If the ordinal value of the Fill argument is out of the range of a Byte , Delphi silently uses only the least significant byte as the fill byte. Delphi does not report an error, even if you have overflow checking enabled.


The most common use for FillChar is to fill a buffer with zeros. You can also call the SysUtils.AllocMem function, which calls GetMem and then FillChar to fill the newly allocated memory with all zeros.

When allocating a new record or array that contains long strings, dynamic arrays, interfaces, or Variant s, you must initialize those elements. The Initialize procedure is usually the best way to do this, but it does not initialize any other elements of the array or record. Instead, you can call FillChar to fill the new memory with all zeros, which is a correct initial value for strings, dynamic arrays, interfaces, and Variant s. When you free the record, be sure to call Finalize to free the memory associated with the strings, dynamic arrays, interfaces, and Variant s.

FillChar — Процедура Delphi

Если размер массив четный и делится на 4, можно прямо ассемблерной вставкой:

Еще можно через mmx кидать сразу по 8 байт, а через sse вообще по 16 байт за раз, но могут быть косяки во-первых из-за выравнивания массива в памяти, а во-вторых из-за большой латентности самих SIMD комманд. Так что тут палка о двух концах.

Вот, померил кстати, да, асмовая вставка оказалась самым быстрым вариантом. MMX и SSE только тормозят, не знаю почему так. Может конечно я косо написал.

FillChar() time: 5890 ms
rep stosd time: 5875 ms
MMX fill: 6141 ms
SSE fill: 5906 ms

Хоть бы версию Delphi указали.

FillChar в D7 и D2007 — как небо и земля.

03.10.2010, 16:33 #6

Я специально дал сорцы — берите да проверяйте для какой надо делфи. Хоть для Delphi v7, хоть для Delphi 2010. Результаты выше были для Free Pascal Compiler v2.4.0 конкретно.

Ща на Delphi v7 проверю. Да те же самые резалты тащемта:

03.10.2010, 16:40 #7
FillChar() time: 5860 ms
rep stosd time: 5812 ms
MMX fill: 6094 ms
SSE fill: 5859 ms
03.10.2010, 18:27 #8
требуется очищать каждые 0.05с. Т.к. он является буффером глубины прорисовки кадра.

м. Что именно имеется ввиду под «буффером глубины»?
А при этой прорисовке нельзя просто менять значения на новые? А то лишние «очистки» — зло.

По моим сведеньям быстрее всего на MMX. Источник вроде был достаточно надёжный, хотя сам не проверял.

Tronix, за тест спасибо, ща тоже померяю. Только жалко FPC у мня не фурычит, сделаю на Делфи.
А почему через GetTickCount, а не через QueryPerformanceCounter?
Да и по-хорошему проверять нужно не по TIME_CRITICAL, а вообще по всем.

Why most Delphi examples use FillChar() to initialize records?

I just wondered, why most Delphi examples use FillChar() to initialize records.

Here (http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html) is my note on this topic. IMO, declare a constant with default value is a better way.

8 Answers 8

Historical reasons, mostly. FillChar() dates back to the Turbo Pascal days and was used for such purposes. The name is really a bit of a misnomer because while it says FillChar(), it is really FillByte(). The reason is that the last parameter can take a char or a byte. So FillChar(Foo, SizeOf(Foo), #0) and FillChar(Foo, SizeOf(Foo), 0) are equivalent. Another source of confusion is that as of Delphi 2009, FillChar still only fills bytes even though Char is equivalent to WideChar. While looking at the most common uses for FillChar in order to determine whether most folks use FillChar to actually fill memory with character data or just use it to initialize memory with some given byte value, we found that it was the latter case that dominated its use rather than the former. With that we decided to keep FillChar byte-centric.

It is true that clearing a record with FillChar that contains a field declared using one of the «managed» types (strings, Variant, Interface, dynamic arrays) can be unsafe if not used in the proper context. In the example you gave, however, it is actually safe to call FillChar on the locally declared record variable as long as it is the first thing you ever do to the record within that scope. The reason is that the compiler has generated code to initialize the string field in the record. This will have already set the string field to 0 (nil). Calling FillChar(Foo, SizeOf(Foo), 0) will just overwrite the whole record with 0 bytes, including the string field which is already 0. Using FillChar on the record variable after a value was assigned to the string field, is not recommended. Using your initialized constant technique is a very good solution this problem because the compiler can generate the proper code to ensure the existing record values are properly finalized during the assignment.

Почему большинство примеров Delphi использовать FillChar () для инициализации записей?

Мне просто интересно, почему большинство примеров Delphi использовать FillChar () для инициализации записей.

Здесь ( http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html ) моя заметка на эту тему. ИМО, объявить константу со значением по умолчанию является лучшим способом.

Исторические причины, в основном. FillChar () восходит к временам , Turbo Pascal и используется для таких целей. Название действительно немного некорректным , потому что в то время как она говорит Fill Char (), это действительно Fill Byte (). Причина заключается в том, что последний параметр может принимать символ илибайт. Так FillChar (Foo, SizeOf (Foo), # 0) и FillChar (Foo, SizeOf (Foo), 0) эквивалентны. Другим источником путаницы является то, что в Delphi 2009, FillChar до сих пор заполняет только байт, хотя Char эквивалентно WideChar. Глядя на наиболее распространенных применениях FillChar для того, чтобы определить, является ли использовать большинство людей FillChar фактически заполнить память с символьными данными или просто использовать его для инициализации памяти с некоторым заданным значением байта, мы обнаружили, что это был последний случай, который доминировал его использование а не бывший. С этим мы решили сохранить FillChar байт-ориентированными.

Это правда , что очистка запись с FillChar , который содержит поле , объявленные с помощью одного из «управляемых» типов (строки, Variant, интерфейс, динамические массивы) может быть небезопасно , если не используется в правильном контексте. В этом примере вы дали, однако, на самом деле безопасно вызывать FillChar на локально объявленной переменной записей до тех пор , как это первая вещь , которую вы когда — либо сделать , чтобы запись в этой области видимости . Причина заключается в том, что компилятор сгенерировал код для инициализации поля строки в записи. Это будет уже установлено поле строкового 0 (ноль). Вызов FillChar (Foo, SizeOf (Foo), 0) будет просто переписать всю запись с 0 байт, в том числе поле строки , которая уже 0. Используя FillChar на переменной записей послезначение было назначено на поле строки, не рекомендуется. Использование инициализированной постоянной техники является очень хорошим решением этой проблемы, потому что компилятор может генерировать правильный код, чтобы обеспечить существующие значения записи правильно завершены в процессе выполнения задания.

Если у вас есть Delphi 2009 , а затем, с помощью Default вызова для инициализации записи.

Преимущество использования Default(TSomeType) вызова, является то , что запись будет завершена , прежде чем он будет очищен. Нет утечки памяти и нет явного вызова опасно низкого уровня для FillChar или ZeroMem. Когда записи являются сложными, возможно , содержащие вложенные записи и т.д., риск совершения ошибок исключается.

Ваш метод для инициализации записи можно сделать еще проще:

Иногда требуется параметр имеет значение не по умолчанию, а затем сделать так:

Это позволит сэкономить ввод и фокус устанавливается на важные вещи.

FillChar прекрасно , чтобы убедиться , что вы не получаете никакого мусора в новом, неинициализированной структуру (записи, буфер, arrray . ).
Она не должна использоваться для «сброса» значения , не зная , что ваш перезагружаем.
Не больше , чем просто писать MyObject := nil и ожидая , чтобы избежать утечки памяти.
В particulart все управляемые типы должны внимательно следить.
См Finalize функции.

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

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

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

Традиционно, персонаж не один байт (больше не верно для Delphi 2009), так что использование FillChar с # 0 будет инициализировать память, выделенную таким образом, чтобы она содержала только нули, или байт 0, или бен 00000000.

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

Этот вопрос имеет более широкое значение, что было в моем сознании в течение веков. Я тоже был воспитан на использование FillChar для записей. Это хорошо, потому что мы часто добавлять новые поля к (данных) записи и конечно FillChar (Rec, SizeOf (Rec), # 0) заботится о таких новых областях. Если мы «делать это правильно», мы должны перебрать все поля записи, некоторые из которых перечислены типы, некоторые из которых могут быть сами записи и полученный код менее читаемым, а возможно, будут ошибочными, если мы не добавлять новые поля записи к нему усердно. Строковые поля не являются общими, что FillChar не представляет никакого нет в настоящее время. Несколько месяцев назад я ходил и превращал все мои FillChars на записях с строковыми полей для повторных очисток, но я не был доволен решением и интересно, если есть аккуратный способ делать «Заливку»

Вопрос может быть вопросом:

Там нет ZeroMemory функции в Windows. В заголовочных файлах ( winbase.h ) это макрос , который, в мире C, оборачивается и называет MemSet:

ZeroMemory язык нейтральный термин «функции вашей платформы , которая может быть использована для нулевой памяти»

Delphi эквивалент memset это FillChar .

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

Так во многих отношениях, называя FillChar является производительность микро-оптимизация — которая больше не существует в настоящее время , что ZeroMemory является встраиваемым:

Бонус Reading

Windows , также содержит SecureZeroMemory функцию. Это делает точно такую же вещь , как ZeroMemory . Если он делает то же самое , как ZeroMemory , почему она существует?

Поскольку некоторые умные C / C ++ компилятор может признать , что установка памяти 0 перед тем , как избавиться от памяти является пустой тратой времени — и оптимизировать на вызов ZeroMemory .

Я не думаю , что компилятор Delphi является таким умным , как и многими другими компиляторы; так что нет никакой необходимости в SecureFillChar .


Использование процедур и функций в 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
Глава 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.

FillChar — Процедура Delphi

Статья опубликована пользователем Delphist.

Автор оригинала Coder
Delphi. Работа над ошибками

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

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

Выработанные правила направлены на:

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

Warnings and Hints

Компилятор Delphi снабжен «анализатором» качества кода. Он может предупреждать о потенциально опасных или бессмысленных ситуациях. Не пренебрегайте его услугами.

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

Используйте именованные константы. Это увеличивает «настраиваемость» исходного кода. А также избавляет от проблем связанных с изменением значения константы в случае ее множественного вхождения.

Range Check и Integer Overflow Check

К сожалению, эти опции компилятора по умолчанию отключены в Delphi, и многие разработчики не пользуются их услугами, а зря. Появления этих ошибок говорит о наличии в программе семантических ошибок, таких как неправильная индексация массива или использование несоответствующего целочисленного типа. Последствия этих ошибок могут быть весьма коварны. Я советую оставлять эти флаги всегда включенными, независимо от того — это отладочная или «финальная» версия программы. Лучше иметь неработающую программу (или ее часть), чем программу работающую неправильно (IMHO).

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

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

  • Проверяйте значения переменных на допустимость. Особенно это касается переменных типа указатель, процедурных переменных и объектов.
  • Защищайте пары выделение-освобождение ресурсов блоками try/finally. Предполагайте, что исключение может произойти в любом операторе.
  • Используйте процедуру Assert для проверки условий, которые всегда должны быть истинными.

Объем кода, добавленный для проверок и обработки ошибок, может достигать порядка «полезного» кода! Но, такой стиль программирования является необходимым условием при написании сложных систем. Что поделаешь, из бревен небоскреб не построишь

Значения по умолчанию и «неопределенные» значения

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

Для указателей и объектов пустым значением должно являться значение nil.
Для числовых типов лучше всего резервировать значение ноль.
Для строковых переменных — пустая строка
Для перечислимых типов необходимо предусмотреть специальное значение.
Пример:
TDayOfWeek = (dwNone,dwSun,dwMon,dwTue,dwWen,dwThu,dwFri,dwSat);

Правило №2:
«Неопределенными» значениями лучше всего выбирать такие, чье двоичное представление соответствует нулю (нулям). Это увеличивает устойчивость, когда не выполнена начальная инициализация переменной, но произведена инициализация блока памяти, в котором она размещается.

Пример
Для перечислимых типов «неопределенное» значение должно быть первым, так как оно соответствует целочисленному нулю.

Инициализация переменных и полей

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

Правило:

  • Для глобальных переменных: использовать типизированные константы, инициализированные переменные или присваивать начальные значения переменным в секции инициализации модуля.
  • Для локальных переменных: присваивать начальные значения в первых строках процедуры или функции.
  • Для полей объектов: присваивать начальные значения полям в конструкторе и не полагаться на то, что память, выделенная под объект, инициализируется нулями.
  • Массивы, записи и выделенные блоки памяти очень удобно инициализировать при помощи функции FillChar. Но, с появлением в Delphi «управляемых» (manageable) типов (длинные строки, динамические массивы, варианты и интерфейсы), пользоваться ей необходимо с четким пониманием.

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