Char — Тип Delphi


Строковые типы в Delphi. Особенности реализации и использования

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

  1. Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга
  2. Преобразование строк из одного типа в другой
  3. Некоторые приемы использования строк типа AnsiString:
    1. Функции для работы со строками о которых многие часто забывают или вовсе не знают
    2. Передача строк в качестве параметров
    3. Использование строк в записях
    4. Запись в файл и чтение из файла
    5. Использование строк в качестве параметров и результатов функций размещенных в DLL.

Ну что, интересно? Тогда поехали.

Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?

В Delphi 1.0 существовал лишь единственный строковый тип String, полностью эквивалентный одноименному типу в Turbo Pascal и Borland Pascal. Однако, этот тип имеет существенные ограничения, о которых я расскажу позднее. Для обхода этих ограничений, в Delphi 2, разработчики из Borland устроили небольшую революцию. Теперь, начиная с Delphi 2, имеются три фундаментальных строковых типа: ShortString, AnsiString, и WideString. Кроме того, тип String теперь стал логическим. Т.е., в зависимости от настройки соответствующего режима компилятора (режим больших строк), он приравнивается либо к типу ShortString (для совместимости со старыми программами), либо к типу AnsiString (по умолчанию). Управлять режимом, можно используя директиву компиляции <$LONGSTRINGS ON/OFF>(короткая форма <$H+/->) или из окна настроек проекта – вкладка «Compiler» -> галочка «Huge strings». Если режим включен, то String приравнивается к AnsiString, иначе String приравнивается ShortString. Из этого правила есть исключение: если в определении типа String указан максимальный размер строки, например String[25], то, вне зависимости от режима компилятора, этот тип будет приравнен к ShortString соответствующего размера.

Поскольку, как вы узнаете в дальнейшем, типы ShortString и AnsiString имеют принципиальное отличие в реализации, то я вообще не рекомендую пользоваться логическим типом String без указания размера, если Вы, конечно, не пишете программ под Delphi 1. Если же Вы все-таки используете тип String, то я настоятельно рекомендую прямо в коде Вашего модуля указывать директиву компиляции, устанавливающую подразумеваемый Вами режим работы компилятора. Особенно если Вы используете особенности реализации соответствующего строкового типа. Если этого не сделать, то однажды, когда Ваш код попадет в руки другого программиста, не будет никакой гарантии того, что его компилятор будет настроен, так же как и Ваш.

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

Сразу же упомяну о различии между типами AnsiString и WideString. Эти типы имеют практически одинаковую реализацию, и отличаются лишь тем, что WideString используется для представления строк в кодировке UNICODE использующей 16-ти битное представление каждого символа (WideChar). Эта кодировка используется в тех случаях когда необходима возможность одновременного присутствия в одной строке символов из двух и более языков (помимо английского). Например, строк содержащих одновременно символы английского, русского и европейских языков. За эту возможность приходится платить – размер памяти, занимаемый такими строками в два раза больше размера, занимаемого обычными строками. Использование WideString встречается не часто, поэтому, я буду в основном рассказывать о строках типа AnsiString. Но, поскольку они имеют одинаковую реализацию, почти все сказанное относительно AnsiString будет действительно и для WideString, естественно с учетом разницы в размере каждого символа.

Тоже самое касается и разницы между pChar и pWideChar.

Строковый тип AnsiString, обычно используется для представления строк в кодировке ANSI, или других (например OEM) в которых для кодирования одного символа используется один байт (8 бит). Такой способ кодирования называется single-byte character set, или SBCS. Но, очень многие не знают о существовании еще одного способа кодирования многоязычных строк (помимо UNICODE) используемого в системах Windows и Linux. Этот способ называется multibyte character sets, или MBCS. При этом способе, некоторые символы представляются одним байтом, а некоторые, двумя и более. В отличие от UNICODE, строки, закодированные таким способом, требуют меньше памяти для своего хранения, но требуют более сложной обработки. Так вот, строковый тип AnsiString может использоваться для хранения таких строк. Я не буду подробно останавливаться на этом способе кодирования, поскольку он применяется крайне редко. Лично я, ни разу не встречал программ использующих данный способ кодирования.

Знатоки Delphi вероятно мне сразу напомнят еще и о типах pChar (pWideChar) и array [. ] of Char. Однако, я считаю, что это не совсем строковые типы, но я расскажу и о них, поскольку они очень часто используются в сочетании со строковыми типами.

Итак, приведу основные характеристики строковых типов:

Тип Максимальный размер строки Размер переменной Объем памяти, требуемый для хранения строки
String[n] где 0 0, поэтому то Delphi и не освобождает память, занятую строкой.

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

Здесь, как мы уже знаем, после выполнения оператора s2 := s1, обе переменные указывают на один и тот же экземпляр строки ‘abc123’. Однако, что же произойдёт когда выполниться оператор s2[1] := ‘X’? Казалось бы, в единственном имеющимся в нашем распоряжении экземпляре строки первая буква будет заменена на ‘X’. И как следствие, обе строки станут равными ‘Xbc123’. s1 то за что «страдает»? Но, к счастью это не так. Здесь на помощь Delphi вновь приходит счетчик ссылок. Delphi, при выполнении этого оператора понимает, что строка на которую указывает s2 будет изменена, а это может повлиять на других. Поэтому, перед изменением строки, проверяется ее счётчик ссылок. Обнаружив, что на нее ссылается более одной строковой переменной, делается следующее: создается копия этой строки со счётчиком ссылок равным 1, и адрес этой копии, присваивается s2; У исходного экземпляра строки, счетчик ссылок уменьшается на 1 – ведь s2 на неё теперь не ссылается. И лишь после этого, происходит изменение первой буквы, теперь уже собственного экземпляра строки. Т.е., по окончанию выполнения этого оператора, в памяти будут находиться две строки: ‘abc123’ и ‘Xbc123’. Причем, s1 будет ссылаться на первую, а s2 на вторую.

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

Казалось бы, при завершении работы процедуры, экземпляр строки ‘Вася’ должен быть уничтожен. Но в данном случае это не так. Ведь, при следующем входе в процедуру, для выполнения присваивания нужно будет вновь где-то взять строку ‘Вася’. Для этого, ещё при компиляции, Delphi размещает экземпляр строки ‘Вася’ в области констант программы, где её даже невозможно изменить, по крайней мере, простыми методами. Но как же при завершении процедуры определить что строка ‘Вася’ – константная строка, и ее нельзя уничтожать? Все очень просто. Для константных строк, счётчик ссылок устанавливается равным -1. Это значение, «выключает» нормальный алгоритм работы со «счётчиком ссылок». Он не увеличивается при присваивании, и не уменьшается при уничтожении переменной. Однако, при попытке изменения переменной (помните s2[1]:=’X’), значение счётчика равное -1 будет всегда считаться признаком того, что на строку ссылается более одной переменной (ведь он не равен 1). Поэтому, в такой ситуации всегда будет создаваться уникальный экземпляр строки, естественно, без декремента счётчика ссылок старой. Это защитит от изменений экземпляр строки-константы.

К сожалению, этот алгоритм срабатывает не всегда. Но об этом, мы поговорим позже, при рассмотрении вопросов преобразования строковых типов.

Где же Delphi хранит «счётчик ссылок»? Причем, для каждой строки свой! Естественно, вместе с самой строкой. Вот что представляет собой эта область памяти, хранящая экземпляр строки ‘abc’:

Байты с 1 по 4 Счётчик ссылок равный -1
Байты с 5 по 8 Длина строки равная 3
Байт 9 Символ ‘a’
Байт 10 Символ ‘b’
Байт 11 Символ ‘c’
Байт 12 Символ с кодом 0 (#0)

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

Смещение Размер Значение Назначение
-8 4 -1 Счётчик ссылок
-4 4 3 Длина строки
1 ‘a’
1 1 ‘b’
2 1 ‘c’
3 1 #0

С полем по смещению -8, нам уже должно быть все ясно. Это значение, хранящееся в двойном слове (4 байта), тот самый счетчик, который позволяет оптимизировать хранение одинаковых строк. Значение этого счетчика имеет тип Integer, т.е. может быть отрицательным. На самом деле, используется лишь одно отрицательное значение – «-1», и положительные значения. 0 не используется.

Теперь, обратите внимание на поле, лежащее по смещению -4. Это, четырёхбайтовое значение длинны строки (почти как в ShortString). Думаю, Вы заметили, что размер памяти выделенной под эту строку не имеет избыточности. Т.е. компилятор выделяет под строку минимально необходимое число байт памяти. Это конечно хорошо, но, при попытке «нарастить» строку: s1 := s1 + ‘d’, компилятору, точнее библиотеке времени исполнения (RTL) придется перераспределить память. Ведь теперь строке требуется больше памяти, аж на целый байт. Для перераспределения памяти нужно знать текущий размер строки. Вероятно, именно для того, что бы не приходилось каждый раз сканировать строку, определяя её размер, разработчики Delphi и включили поле длины, строки в эту структуру. Длина строки, хранится как значение Integer, отсюда и ограничение на максимальный размер таких строк – 2 Гбайт. Надеюсь, мы не скоро упрёмся в это ограничение. Кстати, именно потому, что память под эти строки выделяется динамически, они и получили ещё одно свое название: динамические строки.

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

Поскольку, переменные этого типа реально являются указателями, то для них и реально такое значение как Nil – указатель в «никуда». Это значение в переменной типа AnsiString по смыслу приравнивается пустой строке. Более того, чтобы не тратить память и время на ведение счётчика ссылок, и поля размера строки всегда равного 0, при присваивании пустой строке переменной этого типа, реально, присваивается значение Nil. Это не очевидно, поскольку обычно не заметно, но как мы увидим позже, очень важная особенность.

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

Преобразование строк из одного типа в другой

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

Преобразование между «настоящими» строковыми типами String[n], ShortString, и AnsiString выполняются легко, и прозрачно. Никаких явных действий делать не надо, Delphi все сделает за Вас. Надо лишь понимать, что в маленькое большое не влезает. Например:

В результате выполнения этого кода, в переменной s3 окажется строка ‘abc’, а не ‘abcdef’. С преобразованием из pChar в String[n], ShortString, и AnsiString, тоже всё очень не плохо. Просто присваивайте, и все будет нормально.

Сложности начинаются тогда, когда мы начинаем преобразовывать «настоящие» строковые типы в pChar. Непосредственное присваивание переменным типа pChar значений строк не допускается компилятором. На оператор p := s где p имеет тип pChar, а s :AnsiString, компилятор выдаст сообщение: «Incompatible types: ‘String’ and ‘PChar'» — несовместимые типы ‘String’ и ‘PChar’. Чтобы избежать такой ошибки, надо применять явное приведение типа: p := pChar(s). Так рекомендуют разработчики Delphi. В общем, они правы. Но, если вспомнить, как хранятся динамические строки — с нулем в конце, как и pChar. А еще и то, что к AnsiString применимо преобразование в тип Pointer. Станет очевидным, что всего, возможно целых три способа преобразования строки в pChar:

Все они, синтаксически правильны. И кажется, что все три указателя (p1, p2 и p3) будут в результате иметь одно и то же значение. Но это не так. Всё зависит от того, что находится в s. Если быть более точным, равно ли значение s пустой строке, или нет:

Чтобы Вы понимали причину такого явления, я опишу, как Delphi выполняет каждое из этих преобразований. В начале напомню, что переменные AnsiString представляющие пустые строки, реально имеют значение Nil. Так вот:

Для выполнения преобразования pChar(s), компилятор генерит вызов специальной внутренней функции @LstrToPChar. Эта функция проверяет – если строковая переменная имеет значение Nil, то вместо него, она возвращает указатель на реально размещенную в памяти пустую строку. Т.е. pChar(s) никогда не вернет указатель равный Nil.

Тут все просто, такое преобразование просто возвращает содержимое строковой переменной. Т.е. если она при пустой строке содержит Nil, то и результатом преобразования будет Nil. Если же строка не пуста, то результатом будет адрес экземпляра строки.

Здесь, все совсем по другому. Перед выполнением такого преобразования, компилятор вставляет код, обеспечивающий ситуацию, когда указатель, хранящийся в s, будет единственным указателем на экземпляр строки в памяти. В нашем примере, если строка не пуста, то будет создана копия исходной строки. Вот ее-то адрес и будет возвращен как результат такого преобразования. Но, если строка пуста, то результатом будет Nil, как и во втором случае.

Теперь, интересно отметить, что если в приведенном примере, преобразование p3 := @(s[1]) выполнить первым, то при не пустой строке в s, все указатели (p1, p2, и p3), будут равны. И содержать они будут адрес «персонального» экземпляра строки.

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

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

Здесь, поскольку параметр передаётся по значению, при вызове будет создана локальная копия переменной (указателя) Msg. Об этом я ещё расскажу ниже. Эта переменная, при завершении процедуры будет уничтожаться, что приведёт к освобождению «персональной» копии экземпляра переданной и преобразованной строки.

Функции этой группы используются для сравнения строк. Результатом будет одно из значений:

>0 если S1 > S2
255 символов приходится платить.

Если же тебе достаточно и 255 символов, то используй ShortString, или String[n].

Есть еще одно, на мой взгляд, замечание. Если Вы обратили внимание на тип переменной Len в моем примере, то возможно у Вас возник вопрос: А почему LongInt, а не Integer? Жаль если у Вас вопрос не возник – либо вы все знаете, либо ничего :). Для остальных поясню: дело в том, что тип LongInt фундаментальный тип, размер которого (4 байта) не будет меняться в последующих версиях Delphi. А тип Integer, это универсальный тип, размерность которого может меняться от версии к версии. Например, для 64-разрядных компьютеров он наверняка «вырастет» до 8-ми байт (64 бита). Лично мне, хочется, что бы файлы данных записанные моей старой версией программы могли быть нормально прочитаны более поздними версиями, возможно скомпилированными уже под 64-разрядной OS.

Использование строк в записях

Проблема возникает тогда, когда одно поле (или несколько полей) имеют тип динамической строки. В этом случае, часто возникает проблема схожая с проблемой записи динамической строки в файл – не все данные лежат в записи, динамические строки представлены в ней лишь указателями. Решается проблема также как и с записью строк в файл. Жаль только что тогда нельзя будет оперировать (записать/прочитать) целиком всей записью. Строки придется обрабатывать особо. Можно сделать, например, так:

Обратите внимание на то, что перед записью в поток я делаю так, что бы в поле f3 попал указатель Nil. Если этого не сделать, то в поток попадет адрес текущего экземпляра динамической строки. При чтении, он будет прочитан в поле f3. Т.е. поле f3 станет указывать на какое-то место в памяти. При выполнении SetLength, поскольку Delphi сочтет что текущее значение f3 лежит по указанному адресу, будет попытка интерпретировать лежащую там информацию как динамическую строку. Если же в поток записать Nil, то SetLength, никуда лезть не будет – экземпляра-то нет.

Использование строк в качестве параметров и результатов функций размещенных в DLL.

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

Общий смысл этого эпоса в том, что если Ваша Dll экспортирует хотя бы одну процедуру или функцию с типом параметра соответствующим любой динамической строке (AnsiString например), или функцию, возвращающую результат такого типа. Вы должны обязательно и в Dll, и в использующей ее программе, первым модулем в списке импорта (uses) указать модуль ShareMem. И как следствие, поставлять со своей программой и Dll еще одну стандартную библиотеку BORLNDMM.DLL.

Вы не задумывались над вопросами: «Зачем все эти сложности?»; «Что будет если этого не сделать?» и «Можно ли этого избежать?»; «Если да, то как?» Если не задумывались, то самое время сделать это.

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

Сначала, при выполнении процедуры X, функция IntToStr(100) создаст экземпляр динамической строки ‘100’, и ее адрес будет помещен в переменную Str. Затем, адрес этой переменной будет передан процедуре Y. В ней, при выполнении оператора s := s+’$’, будет создан экземпляр новый строки ‘100$’, Экземпляр старой строки ‘100’ станет не нужным и память, выделенная для него при создании, будет освобождена. Кроме того, при завершении процедуры X, будет освобождена и память, выделенная для строки ‘100$’, так как перестанет существовать единственная ссылка на нее — переменная Str.

Всё вроде бы хорошо. Но до тех пор, пока обе процедуры располагаются в одном исполняемом модуле (EXE-файле). Если например поместить процедуру Y в Dll, а процедуру X оставить в EXE, то будет беда.

Дело в том, что выделением и освобождением памяти для экземпляров динамических строк занимается внутренний менеджер памяти Delphi-приложения. Использовать стандартный менеджер Windows очень накладно. Он слишком универсален, и потому медленный, а строки очень часто требуют перераспределения памяти. Вот разработчики Delphi и создали свой. Он ведет списки распределенной и свободной памяти своего приложения. Так вот, вся беда в том, что Dll будет использоваться свой менеджер памяти, а EXE свой. Друг о друге они ничего не знают. Поэтому, попытка освобождения блока памяти выделенного не своим менеджером приведёт к серьезному нарушению в его работе. Причем, это нарушение может проявиться далеко не сразу, и довольно необычным образом.

В нашем случае, память под строку ‘100’ будет выделена менеджером EXE-файла, а освобождаться она будет менеджером DLL. То же произойдет и с памятью под строку ‘100$’, только наоборот.

Для преодоления этой проблемы, разработчики Delphi создали библиотеку BORLNDMM.DLL. Она включает в себя еще один менеджер памяти :). Использование же модуля ShareMem, приводит к тому, что он заменяет встроенный в EXE (DLL) менеджер памяти на менеджер расположенный в BORLNDMM.DLL. Т.е., теперь и EXE-файл и DLL, будут использовать один, общий менеджер памяти.

Здесь важно отметить то, что если какой-либо из программных модулей (EXE или DLL) не будут иметь в списке импорта модуля ShareMem, то вся работа пойдет насмарку. Опять будут работать несколько менеджеров памяти. Опять будет бардак.

Можно обойтись и без внешнего менеджера памяти (BORLNDMM.DLL). Но для этого, надо например заменить встроенный в DLL менеджер памяти, на менеджер, встроенный в EXE. Такое возможно. Есть даже соответствующая реализация от Emil M. Santos, называемая FastShareMem. Найти ее можно на сайте http://www.codexterity.com. Она тоже требует обязательного указания ее модуля FastShareMem в списках используемых модулей EXE и DLL. Но, она по крайней мере не требует таскать за собой ни каких дополнительных DLL’лек.

Ну вот, наконец-то и все. Теперь, Вы знаете о строках почти столько же как я :).

Конечно, этим тема не исчерпывается. Например, я ничего не рассказал о мультибайтовых строках (MBCS) используемых для мультиязыковых приложений. Может и еще что-то забыл рассказать. Но, не расстраивайтесь. Я свои знания получал, изучая книги, тексты своих и чужих программ, код сгенерированный компилятором, и т.п. Т.е., все из открытых источников. Значит это все доступно и Вам. Главное, чтобы Вы были любознательными, и почаще задавали себе вопросы «Как?», «Почему?», «Зачем?». Тогда во всем сможете разобраться и сами.

Delphi/Переменные

Переменная — область оперативной памяти, в которой лежит какое-то значение. Основные типы переменных в Delphi:

  • Integer — целые числа.
  • Real — целые и дробные числа.
  • Boolean — логический тип.
  • Char — символьный тип данных.
  • String — строковой тип данных.

Переменные указываются после ключевого слова var (variable — переменная). Общий вид указывания переменных:

В Delphi есть оператор присваивания — := .Общий вид присваивания:

Но с типом string и char, особое дело. Общий вид присваивания с типом string и char:

Char — Тип Delphi

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

type FootballTeam = (Spartak, Dinamo, CSKA, Torpedo, Lokomotiv);
var MyTeam: FootballTeam;
begin
MyTeam:=Spartak;
end;

Вообще, под перечислимыми типами понимают все типы, для которых можно определить последовательность значений и их старшинство. К ним относятся:
все целочисленные типы, для которых всегда можно указать число, следующее за числом N;
символьные типы (Char): за символом ‘a’ всегда следует ‘b’, за ‘0’ следует ‘1’, и так далее;
логические типы — тип Boolean также представляет собой перечислимый тип: type Boolean = (false, true);
Структурные типы данных используются практически в любой программе. Это такие типы, как
массивы
записи
множества

Массив — это структура данных, доступ к элементам которой осуществляется по номеру (или индексу). Все элементы массива имеют одинаковый тип.
Описание массива имеет вид:

type имя_типа_массива = array [диапазон] of тип_элемента;

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

type TMyArray = array [1 .. 100] of Integer;

Теперь можно описать переменные типа TMyArray:

var A, B: TMyArray;

Вместо присвоения типа можно явно описать переменные как массивы:

var A, B : array [1..100] of Integer;

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

var N: Integer;
begin
N := 65;
A[5] := 101;
A[N] := 165;
A[N+3] := 200;
B := A;
end;

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

High() — вернёт число, являющееся верхней границей массива;
Low() — вернёт число, являющееся нижней границей массива.

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

Выражение B := A означает, что каждый элемент массива B будет равен элементу с таким же индексом массива A. Такое присвоение возможно только если оба массива объявлены через некий поименованный тип, или перечислены в одном списке. И в случае:

var A: array[1..100] of String;
B: array[1..100] of String;

его использовать невозможно (но возможно поэлементное присвоение B[1] := A[2]; и т.д.).

Массивы могут иметь несколько измерений, перечисляемых через запятую. Например, таблицу из четырёх столбцов и трёх строк:
1 2 3 4
5 6 7 8
9 10 11 12
можно описать в виде массива с двумя измерениями:

type MyTable = array[1..4, 1..3] of Integer;
var X : MyTable;
Y : Integer;
begin
Y:=X[3, 2];
end;

Теперь в результате операции присвоения Y будет равен 7.
Многомерный, например, двумерный массив можно описать как массив массивов:

type TMyArray = array [1 .. 4] of array [1 .. 3] of Integer;

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

type TDinArray = array of Integer;
var A : TDinArray;

После создания в динамическом массиве нет ни одного элемента. Необходимый размер задаётся в программе специальной процедурой SetLength. Массив из ста элементов:

begin
SetLength(A, 100);
end;

Нижняя граница динамического массива всегда равна нулю. Поэтому индекс массива A может изменяться от 0 до 99.
Многомерные динамические массивы описываются именно как массивы массивов. Например, двумерный:

type T3DinArray = array of array of Integer;
var A : T3DinArray;

В программе сначала задаётся размер по первому измерению (количество столбцов):

Затем задаётся размер второго измерения для каждого из трёх столбцов, например:

SetLength(A[0], 3);
SetLength(A[1], 2);
SetLength(A[2], 1);

Таким образом создаётся треугольная матрица: A00 A10 A20
A01 A12
A02
Чтобы освободить память, выделенную динамическому массиву, нужно массиву как целому присвоить значение nil:
A:=nil;
Ключевое слово nil в Delphi означает отсутствие значения.

Записи очень важный и удобный инструмент. Даже не применяя специальные технологии, с его помощью можно создавать собственные базы данных. Записи — это структура данных, каждый элемент которой имеет собственное имя и тип данных. Элемент записи иначе называют поле. Описание записи имеет вид:
type имя_типа_записи = record
название_поля : тип_поля ;
. . .
название_поля : тип_поля ;
end;
Названия полей, имеющих одинаковый тип, можно, как и в случае описания переменных, указывать в одну строку через запятую. Для обращения к полю записи сначала указывают имя записи, затем точку, затем имя поля. Например, данные о персонале предприятия могут быть организованы таким типом записи:

type TPers = record
Fam, Name, Par : String;
Year : Integer;
Dep : String;
end;
var Pers : TPers;
begin
Pers.Fam:=’Иванов’;
Pers.Name:=’Иван’;
Pers.Par:=’Иванович’;
Pers.Year:=1966;
Pers.Dep:=’Цех №1′;
end;

Теперь осталось записать эти данные в файл, предварительно объявив и его тип как TPers, и база данных готова. С файлом в Delphi также ассоциируется переменная, называемая файловой переменной, которая описывается так:
VFile : file of тип_файла;
В качестве типа может использоваться любой ограниченный тип Delphi. При этом не допускается тип String, так как он допускает переменный размер до 2 ГБайт. Его необходимо ограничивать: String[N], где N — количество символов. Тип TPers из предыдущего примера должен быть описан, например, так:

type TPers = record
Fam, Name, Par : String[20];
Year : Integer;
Dep : String[10];
end;

Теперь переменная такого типа занимает строго определённое место в памяти, и может быть записана в файл. Как это сделать, рассказывается во 2-й части Урока №7.

Множество — это группа элементов, объединённая под одним именем, и с которой можно сравнивать другие величины, чтобы определить, принадлежат ли они этому множеству. Количество элементов в одном множестве не может превышать 256. Множество описывается так:

type имя_множества = set of диапазон_значений_множества ;

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

type TMySet = set of 0 .. 255;
type TMySet = set of Byte;

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

var MySet : TMySet;
begin
MySet:=[1, 3 .. 7, 9];
end;

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

var Key : Char;
Str : String;
begin
if Key in [‘0’ .. ‘9’, ‘+’, ‘-‘] then Str:=’Math’;
end;

Чтобы добавить элемент во множество, используется операция сложения, удалить — вычитания:

var Digit: set of Char=[‘1’..’9′];
var Math: Set of Char;
begin
Math:=Digit+[‘+’, ‘-‘, DecimalSeparator*];
end;

Типы данных

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

  • · Порядковые типы
  • · Целые типы
  • · Символьные типы
  • · Булевы типы
  • · Перечислимые типы
  • · Под диапазонные типы
  • · Действительные типы

Любой реально существующий тип данных, каким бы сложным он ни казался на первый взгляд, представляет собой простые составляющие, которыми процессор может манипулировать. В Object Pascal эти простые типы данных разбиты на две группы: порядковые, представляющие данные разных объемов, которыми процессор может легко манипулировать, и действительные, представляющие приближенно математические действительные числа. Разделение типов на порядковые и действительные несколько условно. Точно так же простые данные можно было бы разделить на числа и не числа. Однако в языке Object Pascal порядковые и действительные данные трактуются по-разному, и такое разделение даже полезно.

Из простых типов данных порядковые — самые простые. В этих типах информация представляется в виде отдельных элементов. Связь между отдельными элементами и их представлением в памяти определяет естественные отношения порядка между этими элементами. Отсюда и название порядковые.

В Object Pascal определены три группы порядковых типов и два типа, определяемых пользователем. Группы — это целые, символьные и булевы типы. Порядковые типы, задаваемые пользователем, — это перечисления и поддиапазоны.

Все значения любого порядкового типа образуют упорядоченную последовательность, и значение переменной порядкового типа определяется его местом в этой последовательности. За исключением переменных целых типов, значения которых могут быть как положительными, так и отрицательными, первый элемент любого порядкового типа имеет номер 0, второй элемент — номер 1 и т.д. Порядковый номер целого значения равен самому значению. Отношение порядка определяет общие для данных всех порядковых типов операции. Некоторые стандартные функции такого вида встроены в Object Pascal. Они представлены в табл. 1.1.

Для всех порядковых типов в Object Pascal существует операция задания типа для преобразования целых значений в значения соответствующих порядковых типов. Если Т — имя порядкового типа, а Х — целое выражение, то Т (X) возвращает значение Т с порядковым номером X.

Программисты, работающие на С и C++, для приращения или уменьшения значений переменных привыкли использовать операторы «++» и» -», возвращающие следующее и предыдущее значения. Программисты Delphi всегда разбивают эти операции на более простые составляющие с помощью функций Pred, Succ. Dec и Inc.

Таблица 1.1. Операции над порядковыми типами

Минимальное значение порядкового типа Т

Максимальное значение порядкового типа Т

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

Предыдущее по порядку значение. Для целых выражений эквивалентно Х-1

Следующее по порядку значение. Для целых выражений эквивалентно Х+1

Уменьшает значение переменной на 1. Эквивалентно V:= Pred(V)

Увеличивает значение переменной на 1. Эквивалентно V:= Succ(V)

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

Обратите внимание, что один из этих целых типов назван именно целым (integer). Это может иногда приводить к путанице, но мы легко сможем ее избежать, применяя термин целый к группе типов, a integer — к конкретному типу, определяемому в программе этим ключевым словом. Переменные физических целых типов имеют разные диапазоны значений в зависимости от того, сколько байтов памяти они занимают (что равно значению, возвращаемому функцией SizeOf для данного типа). Диапазоны значений для всех физических типов перечислены в табл. 1.2.

Таблица 1.2. Физические целые типы

8 бит, со знаком

16 бит, со знаком

-2 147 483 648-2 147 483 647

32 бит, со знаком

8 бит, без знака

16 бит, без знака

Диапазоны значений и форматы физических целых типов не зависят от микропроцессора и операционной системы, в которых выполняется программа. Они не меняются (или, по крайней мере, не должны меняться) с изменением реализации или версии Object Pascal.

Диапазоны значений логических целых типов (Integer и Cardinal) определяются совершенно иным образом. Как видно из табл. 1.3, они никак не связаны с диапазонами соответствующих физических типов. Обратите внимание, что в Delphi по умолчанию задано 32-разрядное представление.

Таблица 1.3. Логические целые типы

16 бит, со знаком (SmallInt)

-2 147 483 648-2 147 483 647

32 бит, со знаком (Longint)

16 бит, без знака (Word)

32 бит, без знака (Longint)

В С и C++ для целых значений определены типы int, short int (или просто short) и long int (или просто long). Тип int из C/C++ соответствует типу Integer из Delphi, a long из C/C++ — Longint из Delphi. Однако Shortint из C/C++ соответствует в Delphi не Shortint, a Smallint. Эквивалент Shortint из Delphi в C/C++ — это signed char. Тип unsigned char в C/C++ соответствует типу Byte из Delphi. В C/C++ существует еще тип unsigned long, аналога которому в Delphi нет.

Над целыми данными выполняются все операции, определенные для порядковых типов, но с ними все же удобнее работать как с числами, а не с «нечисленными порядковыми типами». Как и «живые» числа, данные целых типов можно складывать (+), вычитать (-) и умножать (*). Однако некоторые операции и функции, применяемые к данным целых типов, имеют несколько иной смысл.

Возвращает абсолютное целое значение Х

Возвращает целую часть частного деления Х на Y

Возвращает остаток частного деления Х на Y

Возвращает булево True (истина), если Х — нечетное целое, и False (ложь) — в противном случае

Возвращает целый квадрат Х (т.е. Х*Х)

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

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

В реализациях языка Pascal для первых микропроцессоров была применена 7-битовая схема, названная ASCII (American Standard Code for Information Interchange — Американский стандартный код для обмена информацией). Эта схема и поныне широко распространена, но информация хранится, как правило, в 8-битовых участках памяти. Дополнительный бит удваивает число возможных представлений символов, но реализации расширенного набора символов ASCII часто бывают далеки от стандарта. В данной версии Delphi определен набор 8-битовых символов, известный как расширенный (extended) ANSI (American National Standards Institute — Американский национальный институт стандартов). Как бы то ни было, символьную схему приходится воспринимать так, как ее воспринимает операционная система. Для оконных операционных систем фирмы Microsoft это схема ANSI, включающая ограниченное число предназначенных для вывода международных знаков. В стремлении же применить более обширный набор международных знаков весь компьютерный мир переходит к 16-битовой схеме, именуемой UNICODE, в которой первые 256 знаков совпадают с символами, определенными в схеме ANSI.

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

Физические типы перечислены ниже

Однобайтовые символы, упорядоченные в соответствии с расширенным набором символов ANSI

Символы объемом в слово, упорядоченные в соответствии с международным набором символов UNICODE. Первые 256 символов совпадают с символами ANSI

Символьные типы объемом в двойное слово (32 бит) отсутствуют.

Логический символьный тип именуется char. В классическом языке Pascal char — единственный символьный тип. В Delphi char всегда соответствует физическому типу данных AnsiChar. У американских программистов ассоциация символа с однобайтовой ячейкой памяти укоренилась за долгие годы настолько, что им зачастую просто не приходит в голову, что можно использовать другие схемы кодирования. Однако дискуссии по интернационализации программ в Internet и World Wide Web могут существенно изменить их отношение к проблеме объема символьных данных. Применяя логический тип char, следует делать реализации для других микропроцессоров и операционных систем, в которых char может определяться как WideChar. При написании программ, которые могут обрабатывать строки любого размера, для указания этого размера рекомендуется применять функцию SizeOf, не задавая ее жестко постоянной. Функция Ord (С), где С — любая переменная символьного типа, возвращает целое значение, которым символ С представлен в памяти.

Преобразует целую переменную в переменную типа char с тем же порядковым номером. В Delphi это эквивалентно заданию типа Char (X)

Преобразует строчную букву в прописную

Процессор не различает типы char, определенные в C/C++ и Delphi. Однако функционально каждый из этих языков трактует данный тип совершенно по-разному. В C/C++ это целый тип, переменной которого можно присваивать целые значения. Переменной int можно присвоить символьное значение, а переменной char — целое. В Delphi символьные типы жестко отделены от численных. Для присвоения численному значению символьного здесь необходимо воспользоваться функцией Ord. В языке Basic один символ представляется так же, как и строка символов. Функция Chr из Delphi эквивалентна функции CHR$ из Basic. Функция Ord из Delphi, возвращающая код ANSI символьной переменной, подобна функции A3 С из Basic, аргумент которой представляет односимвольную строку.

На ранней стадии обучения программисты осваивают понятие бита, два состояния которого можно использовать для записи информации о чем-либо, представляющем собой одно из двух. Бит может обозначать 0 или 1, ДА или НЕТ, ВКЛЮЧЕНО или ВЫКЛЮЧЕНО, ВЕРХ или НИЗ, СТОЯТЬ или ИДТИ. В Object Pascal информация о чем-либо, что можно представить как ИСТИНА (True) или ЛОЖЬ (False), хранится в переменных булевых типов. Всего таких типов четыре, и они представлены в табл. 1.4.

Таблица 1.4. Размеры переменных булевых типов

2 байт (объем Word)

4 байт (объем Longint)

По аналогии с целыми и символьными типами, подразделяющимися на физические и логические, естественно предположить, что ByteBool, WordBool и LongBool — физические типы, Boolean — логический. Но в данном случае это не совсем так. Все четыре типа различны. Для Object Pascal предпочтителен тип Boolean, остальные определены для совместимости с другими языками программирования и операционными системами.

Переменным типа Boolean можно присваивать только значения True (истина) и False (ложь). Переменные ByteBool, WordBool и LongBool могут принимать и другие порядковые значения, интерпретируемые обычно как False в случае нуля и True — при любом ненулевом значении.

Булевы типы в Delphi можно сравнить с типом LOGICAL языка FORTRAN. В Basic, С и C++ булевы типы как таковые отсутствуют. Булевы выражения в этих языках применяются точно так же, как во всех остальных, однако результаты этих выражений интерпретируются не как значения отдельного типа, а как целые числа. Как в Basic, так и в C/C++ булевы выражения дают численные результаты, интерпретируемые как False в случае 0 и True — в случае любого ненулевого значения. Это совместимо с порядковыми значениями булевых выражений в Delphi. В C/C++ простые сравнения дают результат 1 (True) или 0 (False). Это эквивалентно булевым значениям Delphi. Только результат сравнения в Delphi выводится как булевый, а не целый. В большинстве случаев типу Boolean из Delphi соответствует тип char в C/C++. В Basic зарезервированы слова TRUE (эквивалентно константе -1) и FALSE (эквивалентно константе 0). В Basic TRUE меньше FALSE, в Delphi, наоборот, False меньше True.

Type enum_type = (first_value, value2, value3, last_value);

Обычно данные перечислимых типов содержат дискретные значения, представляемые не числами, а именами. Тип Boolean — простейший перечислимый тип в Object Pascal. Булевы переменные могут принимать два значения, выражаемые именами True и False, а сам тип определен в Object Pascal так, как будто он объявлен следующим образом:

Type Boolean = (False, True);

С помощью типа Boolean в Object Pascal выполняются сравнения, большинство же перечислимых типов — это просто списки уникальных имен или идентификаторов, зарезервированных с конкретной целью. Например, можно создать тип MyColor (мой цвет) со значениями myRed, myGreen и myBlue (мой красный, мой зеленый, мой синий). Это делается совсем просто:

Type MyColor = (myRed, myGreen, myBlue);

В этой строке объявлены четыре новых идентификатора: MyColor, myRed, myGreen и myBlue. идентификатором MyColor обозначен порядковый тип, следовательно, в синтаксисе Object Pascal можно применять этот идентификатор везде, где разрешены перечислимые типы. Остальные три идентификатора — это значения типа MyColor. Подобно символьным и булевым типам перечислимые не являются числами, и использовать их наподобие чисел не имеет смысла. Однако перечислимые типы относятся к порядковым, так что значения любого такого типа упорядочены. Идентификаторам в списке присваиваются в качестве порядковых номеров последовательные числа. Первому имени присваивается порядковый номер 0, второму — 1 и т.д.

В С и C++ есть тип enema, аналогичный перечислимому типу Delphi. Но в этих языках можно произвольно присваивать идентификаторам постоянные значения. В Delphi же соответствие имен и их значений фиксиро-вано: первому имени присваивается значение 0, каждому последующему — на единицу больше. В С тип enum применяется лишь как средство быстрого определения набора целых постоянных. В C++ объявленные в перечислимом типе идентификаторы можно присваивать только переменным того же типа.

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

Type subrange_type = low_value..high_value;

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

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

В переменных действительных типов содержатся числа, состоящие из целой и дробной частей. В Object Pascal определено шесть действительных типов. Все типы могут представлять число 0, однако они различаются пороговым (минимальным положительным) и максимальным значениями, которые могут представлять, а также точностью (количеством значащих цифр) и объемом. Действительные типы описываются в табл. 1.5.

Char — Тип Delphi

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

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

type FootballTeam = (Spartak, Dinamo, CSKA, Torpedo, Lokomotiv);
var MyTeam: FootballTeam;
begin
MyTeam:=Spartak;
end;

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

  • все целочисленные типы, для которых всегда можно указать число, следующее за числом N;
  • символьные типы (Char): за символом ‘a’ всегда следует ‘b’, за ‘0’ следует ‘1’, и так далее;
  • логические типы — тип Boolean также представляет собой перечислимый тип: type Boolean = (false, true);

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

  • массивы
  • записи
  • множества

Массив — это структура данных, доступ к элементам которой осуществляется по номеру (или индексу). Все элементы массива имеют одинаковый тип.
Описание массива имеет вид:

type имя_типа_массива = array [диапазон] of тип_элемента;

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

type TMyArray = array [1 .. 100] of Integer;

Теперь можно описать переменные типа TMyArray:

var A, B: TMyArray;

Вместо присвоения типа можно явно описать переменные как массивы:

var A, B : array [1..100] of Integer;

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

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

High() — вернёт число, являющееся верхней границей массива;
Low() — вернёт число, являющееся нижней границей массива.

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

Выражение B := A означает, что каждый элемент массива B будет равен элементу с таким же индексом массива A. Такое присвоение возможно только если оба массива объявлены через некий поименованный тип, или перечислены в одном списке. И в случае:

его использовать невозможно (но возможно поэлементное присвоение B[1] := A[2]; и т.д.).

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

1 2 3 4
5 6 7 8
9 10 11 12

можно описать в виде массива с двумя измерениями:

type MyTable = array[1..4, 1..3] of Integer;
var X : MyTable;
Y : Integer;
begin
Y:=X[3, 2];
end;

Теперь в результате операции присвоения Y будет равен 7.
Многомерный, например, двумерный массив можно описать как массив массивов:

type TMyArray = array [1 .. 4] of array [1 .. 3] of Integer;

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

type TDinArray = array of Integer;
var A : TDinArray;

После создания в динамическом массиве нет ни одного элемента. Необходимый размер задаётся в программе специальной процедурой SetLength. Массив из ста элементов:

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

type T3DinArray = array of array of Integer;
var A : T3DinArray;

В программе сначала задаётся размер по первому измерению (количество столбцов):

SetLength(A, 3);

Затем задаётся размер второго измерения для каждого из трёх столбцов, например:

SetLength(A[0], 3);
SetLength(A[1], 2);
SetLength(A[2], 1);


dEpdribeKer, добавлено 8.11.09, 21:26:28
Отличный сайт и материалы очень познавательные Захар Сенников, добавлено 28.11.09, 01:46:15
В посте много чего упрощено, в отличии от реальности. :)
Оксана, добавлено 4.12.09, 20:35:25
Спасибо, не надо тратить кучу времени на сканирование XAЛAШKA, добавлено 14.01.10, 18:43:15
Занятно написано, но как говорится, для полноты информации нужно минимум хотя бы три источника :)
Ольга, добавлено 29.01.10, 23:36:02
Захар Сенников, ВСЕ ГЕНИАЛЬНОЕ — ПРОСТО!
Зачем все усложнять? Всегда можно обойтись простыми решениями, разбивать программу на процедуры и функции, которые всегда можно вызвать. Стремиться к блочной структуре. В этом весь смысл. Ольга, добавлено 30.01.10, 13:11:44
Уважаемый Автор! Позвольте Вам задать несколько вопросов:
1. Индексы массива всегда начинаются с 1, а множества с 0?
2. В описании элемента массива A[i, j], i — номер столбца, а j — номер строки? А не наоборот?
3. Возможно ли создать массив записей, к примеру, используя такую структуру из Вашего урока:
type TPers = record
Fam, Name, Par : String[20];
Year : Integer;
Dep : String[10];
end;
var Pers: array [1..100] of TPers;

Прошу извинить, если мои вопросы не корректны. Просто хочу разобраться. Заранее благодарна. Автор (Андрей!), добавлено 30.01.10, 13:53:38
1. Почему индексы массива только с 1, из описания следует, что диапазон массива задаёт программист, за исключением динамических массивов, они — только с 0.
Множество вообще не индексируется. Можно лишь сказать, принадлежит ли элемент множеству или нет, добавить или исключить элемент из множества. То есть у конкретного элемента множества нет своего порядкового номера, это не нужно. Начертите круг, напишите внутри в произвольных местах любые например цифры. Все они элементы множества, никто не первый.

2. В массиве действительно сначала идёт индекс столбца, затем — строки. Хотя это субъективно, что Вы считаете тем или другим, это же только в нашем мозгу! Но в компоненте TStringGrid это именно так — а это уже наглядно. Так что, чтобы не путаться.

3. Всё правильно! Ольга, добавлено 30.01.10, 14:49:58
Андрей! Спасибо за ответы!
Тогда еще такой вопрос по моему предыдущему посту по пункту 3: Тогда автоматически Fam, Name, Par, Year, Dep тоже станут массивами соответствующих типов и их можно будет писать, к примеру, Fam[i]? Андрей, добавлено 30.01.10, 15:31:36
Fam и другие — в составе записи, а запись — элемент массива, поэтому обращаться к ним нужно будет так:
Pers[i].Fam Ольга, добавлено 30.01.10, 16:29:57
Андрей, Спасибо! Филипп Смирнов, добавлено 17.03.10, 18:39:37
Вот про это я почитал с превеликим интересом. И прочитал бы еще больше! Планируете и в дальнейшем писать на эту же тему? Спасибо
CTИЛЯГA, добавлено 22.03.10, 13:23:20
Данный пост на самом деле помог мне принять очень важное для себя решение. За что автору отдельное спасибо! Жду от Вас новых сообщений!
Евгений, добавлено 28.03.10, 12:56:32
Побольше-бы конкретных примеров! smersh36, добавлено 2.05.10, 16:12:18
Да, сколько людей, столько и мнений, но истина где — то рядом :) Михаил, добавлено 22.05.10, 13:15:54
Отличные уроки.
Женя, добавлено 18.08.10, 21:05:03
Всё очень интересно, но хотелось бы более конкретные примеры которые можно было написать у себя и посмотреть результат. Аноним, добавлено 12.11.10, 13:50:37
Здравствуйте. Такое дело. У меня есть множество edit, но предположим, что их 3. Так вот в эти editы вводятся параметры (числа) вводятся они каждый час. Т.е. Таймер каждый час делает enable:=true, оператор вводит параметры и нажимает кнопку OK я так думаю что при нажатии этой кнопки enable:=false у эдитов и таймер должен снова начать отсчитывать время, так вот мне надо, чтобы из эдитов информация по нажатию кнопки ок попадала в массив, наверное в 3 непомерных. Позже мне из этих массивов надо будет постоить график каждого параметра (чисел вводимых в эдит) от времени. Как мне записать эти массивы. Автор, добавлено 12.11.10, 15:17:36
Я так понял, речь идёт о динамических массивах. Вот запись:

var Array1, Array2, Array3: Array of Integer;

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

SetLength(Array1, Length(Array1)+1);
SetLength(Array2, Length(Array2)+1);
SetLength(Array3, Length(Array3)+1);

Затем можно присваивать значения последним элементам:

Array1[High(Array1)]:=StrToInt(Edit1.Text);
Array2[High(Array2)]:=StrToInt(Edit2.Text);
Array3[High(Array3)]:=StrToInt(Edit3.Text);

А если edit-ов много, я вообще организовал бы массив динамических массивов и массив edit-ов! Тогда можно было бы упростить запись, организовав эти действия в цикле. Аноним, добавлено 13.11.10, 10:54:11
Editoв много, очень много, 28 штук, но я что-то эти массивы не догоняю. Буду разбираться. Аноним, добавлено 13.11.10, 11:04:50
Спасибо большое Аноним, добавлено 13.11.10, 11:58:44
Такой вопрос еще, у меня есть 2еdita и стринггрид из 2 строчек и 3 столбцов, то есть 1 строка и 1 столбец фиксированные. Как сделать так чтобы по нажатию кнопки числа из еditов заносились в таблицу. Проблема в том-как сделать так, чтобы по нажатии кнопки добавлялась строка, которой до этого не было, то есть если я 30 раз нажму кнопку, то у меня получится 31 строка. Автор, добавлено 13.11.10, 12:05:49
Массив едитов: var edits: Array[1..28] of TEdit; А теперь нужно создать эти едиты в цикле edits[i]:=TEdit.Create(Form1); edits[i].Parent:=Form1; Настроить размеры и местоположение.
Автор, добавлено 13.11.10, 12:12:28
Добавление строки в таблицу: StringGr >Аноним, добавлено 13.11.10, 12:40:04
А как-нибудь так вариант писать: » stringgr >Автор, добавлено 13.11.10, 16:46:15
Не я пишу программу и не знаю что такое i+1. A индекс последней строки всегда равен RowCount-1. Сергей, добавлено 15.03.11, 02:56:44
Ув Автор, можете подсказать как вызывать строки у FileListBox? Автор, добавлено 15.03.11, 18:43:10
Честно признаюсь, что в моей D7 этого компонента (как и других компонентов работы с файлами) нет. Но я сильно подозреваю, что работать с FileListBox следует так же, как и с обычным ListBox. Кстати, если проясните этот вопрос, черкните сюда. Децл, добавлено 18.05.11, 19:36:09
Здравствуйте, помогите пожалуйста сложить два динамических массива(дельфи7). Массивы размещены в Стринггрид1,2 и их надо сложить и вывести в Стринггрид3, как это сделать можете помочь плз!! Размерность массивов вводится в едит1,2 и при нажатии на баттон они создается. Автор, добавлено 18.05.11, 21:38:02
Выше прочтите как создаются динамические массивы:

A, B, C: array of Real;
SetLength(A)=StrToInt(Edit1.Text)-1; ну и т.д.

Вот вам и массивы. Складывать-то умеете? Это знак +. Только нужно поэлементно. ))) Децл, добавлено 19.05.11, 20:01:58
Массивы я создаю так но выбивает ошибку

procedure TForm1.Button1Click(Sender: TObject);
begin
StringGr > StringGr >
StringGr > StringGr >
StringGr >
StringGr > StringGr >
StringGr > StringGr > StringGr >
StringGr > StringGr >
StringGr > StringGr > StringGr >end;

procedure TForm1.Button2Click(Sender: TObject);
begin
i:=strtoint(edit1.Text);
j:=strtoint(edit2.Text);
with StringGrid1 do
SetLength(mas1, i * j);
with StringGrid2 do
SetLength(mas2, i * j);
with StringGrid3 do
SetLength(mas3, i * j);
StringGr >
Автор, добавлено 19.05.11, 20:41:15
Ну что, всё верно. А какя ошибка-то выскакивает?
Недочёты есть, конечно.
1. Вот этого: with StringGrid1 do (ну и StringGrid2, StringGrid3) не нужно.
2. Массивы не описаны. Они глобальные?
3. А где собственно суммирование? Ведь это делается в двух вложенных циклах

for k:=0 to i-1 do
for n:=0 to j-1 do
StringGr >

Складывать нужно поэлементно, я уже писал об этом. Кстати, а зачем вы массивы-то создали? Они же не используются у вас. Децл, добавлено 22.05.11, 16:20:57
Большое Вам спасибо Автор все пошло только можете мне объяснить почему такой именно цикл?
for k:=0 to i-1 do
for n:=0 to j-1 do

Вы не представляете как я Вам благодарен. столько парил голову из-за своего незнания и не понимания, большое Вам спасибо. Децл, добавлено 22.05.11, 17:55:33
Я хотел еще спросить умножение матриц делается аналогично?
for k:=0 to i-1 do
for n:=0 to j-1 do
StringGr >Автор, добавлено 22.05.11, 18:58:30
Ну, чего ещё-то объяснять. Весь урок про массивы выше — обещаете прочитать? Тогда ладно:
Динамические массивы всегда имеют номер первого элемента равный нулю. Таблицы тоже отсчитывают количество строк и столбцов от нуля. Предположим, в таблице (массиве) 5 элементов. Считаем 0, 1, 2, 3, 4 — сколько элементов? Пять. А номер последнего? Пять минус 1 сколько будет — четыре (как ни странно). В таблице номер последнего столбца (ну и строки) равен всегда количество столбцов (ну или строк) минус одЫн. Вот вам и цикл, от нуля до количества столбцов (строк) -1. Только переменные цикла не перепутайте местами в Cells. А порядок вложенности циклов (по строкам сначала или по столбцам) неважно.

Во втором вопросе непонятно. Вопрос про умножение, а в примере плюс. Ну да неважно. А какая разница какое действие? Хотя, математически, умножение МАТРИЦ это не просто поэлементное умножение ячеек. Гораздо сложнее. Но, думаю, вы не это имеете в виду. Децл, добавлено 22.05.11, 19:47:22
Извините, урок уже начинаю читать))))
ну а про второй вопрос, как бы перемножить динамические массивы, если я их создаю все тем же способом:
Рrocedure TForm1.Button1Click(Sender: TObject);
begin
StringGr > StringGr >
StringGr > StringGr >
StringGr > StringGr >
StringGr > StringGr >Во втором вопросе я просто скопировал код и забыл поставить знак *, извените Автор, добавлено 22.05.11, 21:11:38
Я так и понял. Но тогда речь идёт о поэлементном умножении. В этом случае таблицы и массивы должны совпадать. Поэтому значения Edit1 и Edit3, Edit2 и Edit4 должны совпадать. Вывод: Edit3 и Edit4 не нужны, они могут служить источником ошибок, если вдруг не совпадут с Edit1 и Edit2. Децл, добавлено 23.05.11, 16:54:48
procedure TForm1.Button3Click(Sender: TObject);
var i,j,k,n:integer;
begin

if edit2.text= edit3.text then
StringGr > StringGr >
StringGr > StringGr >
i:=strtoint(edit2.Text);
j:=strtoint(edit3.Text);

with StringGrid3 do begin
for k:=0 to i-1 do
for n:=0 to j-1 do
StringGr > end;

if edit1.text=edit4.text then
StringGr > StringGr >
StringGr > StringGr >

begin
i:=strtoint(edit1.Text);
j:=strtoint(edit4.Text);
with StringGrid3 do begin
for k:=0 to i-1 do
for n:=0 to j-1 do
StringGr >end;
end;
if edit2.text<>edit3.text and edit1.text<>edit4.text then label1.Caption:=’Неправильна розмірність матриць’;
end;

Ошибка делается на второй части(после and) на знаке <>(edit1.text<>edit4.text) если елементы менять местами то на edit2.text<>edit3.text Автор, добавлено 23.05.11, 17:41:52
Ошибка, говорите? Программа вообще неправильная.
1.вместо

StringGr > StringGr >
i:=strtoint(edit2.Text);
j:=strtoint(edit3.Text);

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

2. StringGrid3.FixedRows ВСЕГДА будет равно нулю, потому что правильно так:

if edit1.text=edit4.text then
begin
StringGr > StringGr > end;
А может, end нужно и ещё дальше переместить? Посмотрите.
Всё это не ошибки, но программа может работать неверно.

3. Ну а насчёт ошибки, нужно условия в скобки заключать, как всё условие, так и его части:

if ((edit2.text<>edit3.text) and (edit1.text<>edit4.text)) then label1.Caption:=’Неправильна розмірність матриць’; Децл, добавлено 23.05.11, 18:06:06
Ладно да я офигенно ошибся написал что программа правильна потому что в ходе испытаний она все-таки коем образом заработала но потом я забыл наверное сохранить ну и все пошло в ж..пу, мне вообщем то можно все делать с квадратными матрицами просто хотел сделать умножение с матрицами разных размеров, ну ладно БОЛЬШОЕ СПАСИБО. Автор, добавлено 23.05.11, 18:55:47
Блин, вы меня путаете с умножением МАТРИЦ. То что у вас, называется поэлементное умножение. НЕ обязательно квадратные матрицы, просто количество строк и столбцов у этих двух матриц должно быть одинаково. Вот проверка:

if ((StringGr > begin
n:=StringGrid1.ColCount;
k:=StringGrid1.RowCount;
StringGr > StringGr > //Ну и дальше умножение:
for i:=0 to n do
for j:=0 to k do
StringGr > end else label1.Caption:=’Неправильна розмірність матриць’; Децл, добавлено 25.05.11, 15:55:22
Нашел в интернете умножение матриц(динамические) но заполняется только последняя клетка матрицы, можете написать в чем ошибка и как ее исправить?
if StringGr > StringGr > StringGr >
StringGr > StringGr >
j:=strtoint(edit1.Text);
i:=strtoint(edit4.Text);
with StringGrid3 do
for k:=0 to j-1 do <цикл по строкам первой матрицы>
begin
Sum :=0;
for n:=0 to j-1 do <цикл по столбцам метрицы и строкам вектора-столбца>
Sum:=Sum + StrToInt(StringGrid1.Cells[j-1,i-1]) * StrToInt(StringGrid2.Cells[0,j-1]);
StringGr > end;
end; Децл, добавлено 25.05.11, 16:06:00
То есть одна клетка StringGrid3 Автор, добавлено 26.05.11, 14:34:04
Я бы решал задачу нахождения произведения таблиц так. Во-первых, сразу переименовал бы таблицы, не StringGrid1, 2, 3 а SG1, SG2, SG3, чтобы записи были покороче, тут оператором with не отделаешься.

Далее, ввёл бы две вспомогательные переменные R1, R2 типа запись с полями Col, Row, где содержались бы размерности таблиц SG1, SG2: