Простые типы данных. Одной из ключевых особенностей языка Object Pascal является жесткая типизация данных
Основные типы данных
Одной из ключевых особенностей языка Object Pascal является жесткая типизация данных. Именно благодаря строгости в подходе к объявлению переменных, процедур и функций, Delphi может похвастаться одним из самых совершенных компиляторов. Что такое типизация? Все достаточно просто. Представьте себе педантичного джентльмена, любящего находить все свои вещи в отведенных им местах: зубную щетку – в шкафчике над умывальником, а смокинг – в платяном шкафу, и никак не наоборот. Если вдруг произойдет обратное, то Delphi потеряет к нам всякий интерес, отправив сообщение об ошибке.
Более того, размеры каждой вещи нашего педанта (другими словами, объем памяти, занимаемый переменной или объектом) соответствуют четко установленным правилам – не больше и не меньше. Однако тип данных не только накладывает ограничения на размер объекта, скажем переменной, но и строго определяет перечень операций, которые можно производить с этим объектом. И это правило весьма логично и последовательно – ведь совсем не стоит, например, в переменную, предназначенную для хранения целого числа, помещать пусть даже очень хорошую строку «Hello, Word!».
На рис. 1.1. предложен вариант классификации типов данных, применяемых в Delphi.
Не надо отчаиваться при виде такой паутины (это, кстати, только верхушка айсберга). Нашими общими усилиями узелок за узелком она будет распутана. Итак, каждый тип данных предназначен для хранения информации определенного вида, и в самом общем случае можно говорить о существовании шести основных типов данных языка Delphi:
• простой
• структурный
• процедурный
• строковый
• указательный
• вариантный
Простые типы данных
Самым большим из представленных типов по праву считается простой. Он предназначен для хранения данных в форме чисел или некоторых упорядоченных последовательностей. Этот тип логически разделяется на две ветви: порядковые и действительные типы. К порядковым типам относятся:
• целые числа
• перечислимые типы
• символьные типы
• поддиапазоны
• логические типы
Если первые три группы в языке Delphi описаны самым жестким образом и не опускают каких-либо изменений, то два последних типа (перечислимый и поддиапазон) могут определяться пользователем непосредственно во время процесса разработки программы. Отношения между элементами любого из порядковых типов складываются вполне ординарно. Все элементы внутри одного типа могут располагаться в виде упорядоченной последовательности (следующий больше предыдущего). Для всех простых типов (за исключением целых чисел) справедливо утверждение, что первый (самый младший) элемент последовательности имеет индекс 0, второй – 1 и т. д. Целые же числа допускают хранение и отрицательных значений, поэтому здесь самый младший элемент последовательности может начинаться не с нуля. К еще одной особенности порядковых типов данных стоит отнести отсутствие знаков после запятой.
Целые числа
В табл. 1.1 представлены порядковые целочисленные значения. В первую очередь типы данных, описывающие целые числа, характеризуются пределом границ диапазона хранимых значений и возможностью описывать отрицательные величины. Чем больше предел допустимых значений, тем больший объем памяти будет занимать переменная этого типа. Как видно из таблицы, самым серьезным из предлагаемых типов является Int64, «пожирающий» целых 8 байт (64 бит) ОЗУ. Такой тип способен хранить величины, сопоставимые с количеством звезд во Вселенной. Как правило, для решения «земных» задач программисту достаточно диапазона значений типа Integer.
Таблица 1.1. Целые числа
Тип | Диапазон значений | Размер в байтах |
Int64 Integer (Longint) Smallint Shortint Byte Word Cardinal (LongWord) | –263 .. 263–1 –2147483648 .. 2147483647 –32768 .. 32767 –128 .. 127 0 .. 255 0 .. 65535 0 .. 4294967295 | |
Символьные типы
Основное назначение символьного типа данных – организация вывода информации на экран компьютера и принтер. В Windows обеспечена поддержка трех наиболее важных наборов символов:
1. OEM – набор символов по умолчанию для MS-DOS.
2. ANSI – набор символов по умолчанию для Windows 9.x.
3. Unicode – набор символов по умолчанию для Windows NT/2000.
Фундаментом наборов символов OEM и ANSI служит код ASCII, в котором каждый символ представлен значением от 0 до 127 (соответственно символ занимает 7 бит памяти). Кодам от 0 до 31 и 127 стандартный 8-битный набор ставит в соответствие управляющие символы (например, символы забоя, табуляции, конца строки и возврата каретки); остальные символы могут быть выведены на экран. Исторически сложилось, что оставшиеся символы были закреплены за латинскими буквами.
Вскоре был задействован и восьмой бит кода, что позволило расширить код ASCII до 256 символов («расширенный набор символов»). Этот набор символов был разработан производителями IBMPC и получил название OEM. Здесь коды от 32 до 126 унаследованы от ASCII, а оставшиеся коды включают дополнительные символы, в частности символы псевдографики для программ DOS.
В большинстве случаев Windows и приложения под Win32 используют «набор символов ANSI». Коды данного набора от 32 (0х20) до 127 (0х7F) соответствуют коду ASCII. Сравнительно недавно появилась еще одна кодировка, получившая название UNICODE. Один символ в такой кодировке занимает целыхдва байта, и благодаря этому он может принимать одно из 65535 значений.
Итак, для работы с отдельными символами Delphi предоставляет следующие типы данных:
Таблица 1.2. Символьные типы
Тип | Диапазон значений | Размер в байтах |
Char (AnsiChar) WideChar | ANSI UNICODE |
Логические (булевы) типы
Логический тип применяется для хранения логических данных, способных принимать только два значения: 1 (true/истина) и 0 (false/ложь).
Таблица 1.3. Логические типы
Тип | Диапазон значений | Размер в байтах |
Boolean ByteBool WordBool LongBool | 0 – false; 1 – true; от 0 до 255, где 0 – false, 1..255 – true от 0 до 65535, где 0 – false, 1..65535 – true от 0 до 4294967295, где 0 – false, 1..4294967295 – true |
Перечислимые типы
Перечислимые типы относятся к типу данных, определяемых программистом. Перечислимый тип данных задается списком имен.
type TypeName = (Value1, Value2,..., Value19);
Числа, а также логические и символьные константы не могут быть элементами перечислимого типа. В качестве примера представим перечислимый тип, соответствующий дням недели:
type TypeWeekDay =(Mon, Tu, We, Th, Fr, Sa, Su);
. . .
var WDay1, WDay2 : TypeWeekDay;
Begin
WDay1 : = Mon;
WDay2 : = Tu;
end;
Особенность перечислимого типа в том, что каждому его элементу соответствует порядковый номер, начиная с 0. Наличие порядкового номера позволяет проводить операции сравнения:
if WDay1<WDay2 then …
Совместно с данными перечислимого типа зачастую используют следующие функции:
function Pred(X); // возвращает предшествующее значение аргумента
function Succ(X); // возвращает следующее значение аргумента
Поддиапазоны
Переменная, входящая в поддиапазон, может принимать значения только в пределах границ диапазона.
type SubIntegerRange = 10 .. 100;
type SubCharRange = ‘A’ .. ‘Z’;
. . .
var IntValue : SubIntegerRange;
CharValue : SubCharRange;
. . .
MyValue : = 50;
CharValue : = 'X';
При попытке присвоить переменной IntValue значение вне диапазона SubIntegerRange компилятор Delphi откажется иметь с нами дело.
Операции с порядковыми типами
Отношения порядка определяют для переменных простого типа перечень простейших допустимых операций.
Таблица 1.4. Допустимые операции
Операция | Содержание |
Порядковые функции | |
function Ord(X): Longint; function Odd(X: Longint): Boolean function Succ(X); function Pred(X); procedure Inc(var X [ ; N: Longint ] ); procedure Dec(var X[ ; N: Longint]); function Low(X); functionHigh(X function Chr(X: Byte): Char; | Возврат порядкового значения X Если X – нечетное число, то true, иначе false Следующее по порядку значение X Предыдущее значение X Приращение X на N, аналог X:=X + N Уменьшение X на N, аналог X:=X – N Минимальное порядковое значение X Максимальное порядковое значение X Возвращает символ таблицы ASCII, соответствующий порядковому значению Х |
Для всех порядковых типов допустима операция задания (приведения) типа. Ее смысл – в приведении преобразования переменной к определенному типу. Для этого саму переменную заключают в круглые скобки, а передними ставят название типа, к которому мы хотим привести переменную:
var C: Cardinal;
I: Integer;
B: Byte;
Begin
…
B:=Byte(C);
B:=Integer(I);
end;
Поясню, что происходит при задании типа. Допустим, что мы приводим переменную типа Integer к типу данных Byte. Если реальное значение, содержащееся в этой переменной, не выходит за границы, допустимые в типе данных Byte (0...255), то значение изменениям не подвергается. Но если значение превысит 255 или станет меньше 0, то операция задания типа включит ограничения и не допустит выхода значения за пределы диапазона Byte.
B:=Byte(-1); {результат B=255}
B:=Byte(-2); {результат B=254}
B:=Byte(510); {результат B=254}
B:=Byte(511); {результат B=255}
B:=Byte(-255); {результат B=1}
B:=Byte(257); {результат B=1}
B:=Byte(-256); {результат B=0}
B:=Byte(256); {результат B=0}
Читатель, имеющий некоторый опыт программирования, наверное, уже выявил закономерность преобразования числа при операции задания типа. Например, для типа Byte действие
B:=Byte(I);
является аналогом операции
B:=I mod 256; //результат = остаток от деления I на 256
Действительные типы
Действительные (вещественные) типы данных предназначены для работы со значениями, содержащими не только целую, но и дробную часть.
Таблица 1.5. Действительные числа
Тип | Диапазон значений | Количество знаков | Размер в байтах |
Real48 Single Double (Real) Extended Comp Currency | 2.9 × 10-39 .. 1.7 × 1038 5 × 10–45 .. 3.4 × 1038 5.0 × 10–324 .. 1.7 × 10308 3.6 × 10–4951 .. 1.1 × 104932 –263+1 .. 263 –1 –922337203685477.5808.. 922337203685477.5807 | 11-12 7-8 15-16 19-20 19-20 19-20 |
Если в программе необходимо производить вычисления действительных чисел, то по возможности объявляйте переменные как Single (если, конечно, вас устраивает диапазон данного типа данных).
При осуществлении математических операций с переменными действительного типа будьте готовы к незначительным ошибкам округления.
var S : Single; R,F : Real;
Begin
S:=1/3; R:=1/3;
F:=S-R; //результат F = 0.000000009934107
end;
Предложенный листинг демонстрирует ситуацию, когда компьютер «ошибся» в элементарных операциях вычитания. Даже второклассник знает, что 1/3 – 1/3 = 0, а наш кремниевый друг насчитал что-то около 0.000000009934107. На самом деле в ошибке виноваты мы, а не «бестолковый» компьютер. Ведь в программе мы использовали различные типы данных. Переменная S объявлена как Single, поэтому она способна точно хранить лишь 7–8 знаков после запятой, а переменная R объявлена как Real, т. е. способна хранить 15–16 знаков после запятой. В результате имеем S = 0.333333343267441, R = 0.333333333333333.
Строковый тип
Строковый тип данных предназначен для хранения последовательности букв, цифр и других символов. Обычная строка представляет собой не что иное, как массив символьных значений плюс некоторая служебная информация. В Delphi реализовано четыре основных строковых типа (табл. 1.6).
Таблица 1.6. Строковые типы
Тип | Максимальная длина | Размер в памяти |
ShortString String AnsiString WideString | 255 символов | 2 .. 256 байт |
определяется директивой компилятора $H. Если она включена, то соответствует AnsiString, иначе ShortString | ||
около 231 символов около 230 символов | 4 байт .. 2 Гбайт 4 байт .. 2 Гбайт |
Исторически в Delphi 1.0 появился тип данных ShortString. В памяти компьютера она представляет собой цепочку байтов, причем в первом байте содержится значение длины текстовой строки, а в остальных – непосредственно информация. Другими словами, если вас зовут «Петя» (что составляет 4 символа), то в служебном байте окажется четверка, а в оставшихся четырех байтах – соответственно символы «П», «е», «т» и «я».
Физический формат строки AnsiString значительно сложнее. Официальная информация о том, каким образом создатели Delphi организовали хранение данных в этом типе строки, отсутствует. (Это говорит о том, что Borland оставляет за собой право изменять внутренний формат строки такого типа.) Однако серьезный программист обязан знать о следующих особенностях физического формата AnsiString.
Во-первых, это строка, заканчивающаяся нулем, – в самом последнем байте этой строки окажется символ #0. Во-вторых, форматом строки предусмотрена область, хранящая данные о количестве ссылок на эту строку. Благодаря тому что строка завершается нулевым символом, она прекрасно взаимодействует с функциями Windows API, а из-за наличия счетчика ссылок (хранящего данные о том, сколько строковых переменных ссылаются на одно и то же место в памяти) значительно упрощается операция копирования строки из переменной в переменную. В этом случае просто копируется указатель и осуществляется приращение счетчика ссылок.
Строки WideString предназначены для работы с 16-битными символами, т. е. здесь на каждый символ отводится два байта. Таким образом, тип данных WideString способен работать с символами из таблицы Unicode (UCS-2). Unicode – стандарт, рожденный в недрах Apple и Xerox в 1988 г. Спустя три года для совершенствования и внедрения Unicode был создан консорциум, в состав которого вошли более десятка ключевых компаний, в том числе и Microsoft.
Поскольку на каждый символ отводится два байта, Unicode позволяет кодировать 65 536 символов, что более чем достаточно для работы с любым языком. Поэтому разработчики Unicode решили определить местоположение символов каждого из ключевых мировых языков (табл. 1.7) и расширить набор символов огромным количеством технических символов. На сегодняшний день определено около 35 тысяч кодовых позиций и еще около 30 тысяч позиций свободны.
Таблица 1.7. Unicode
16-битный код | Символы | 16-битный код | Символы |
0000-007F 0080-00FF 0100-017F 01 80-01FF 0250-02AF 02BO-02FF | ASCII Символы Latin 1 Европейские латинские Расширенные латинские Стандартные фонетические Модифицированные литеры | 0300-U36F 0400-04FF 0530-058F 0590-05FF 0600-06FF 0900-097F | Общие диакритические Кириллица Армянский Еврейский Арабский Деванагари |
При объявлении переменной строкового типа допускается явным образом ограничить ее длину, для чего в квадратных скобках указывают количество символов в строке:
var Name : string[40];
Объявленная таким образом переменная занимает в памяти количество байт, равное длине строки + 1 байт.
Структурные типы
Основное назначение структурных типов – совместное хранение множества однотипных или разнотипных значений. Различают следующие разновидности структурных типов:
• массивы
• множества
• классы
• записи
• файлы
• указатели на классы
Первые три типа будут рассмотрены в этой главе, а файлы, классы и указатели на классы – несколько позже.
Массивы
Представим, что перед программистом поставлена задача разработки программного модуля для хранения фамилий студентов или что-нибудь похожее, связанное с обработкой большого количества однотипных данных. Использование обычных переменных для хранения упорядоченных наборов данных одного типа не является эффективным решением подобных задач. Явное неудобство состоит в объявлении десятков, сотен или даже тысяч переменных, а сложность обращения к каждой из них тем более не нуждается в доказательствах. Выходом из такой ситуации является применение массивов (array).
Массив, как и переменную, необходимо объявить. Для этого нужно указать размерность массива и тип хранимых данных:
var <имя_массива>: array [<нижняя граница> .. <верхняя граница>] of <тип_элементов>;
Если в программе будут применяться несколько однотипных массивов, то предварительно стоит определить тип массива, а затем создавать массивы на базе объявленного типа:
type TMyArray = Array [0..9] of integer; // массив из 10 элементов
var Array1, Array2 : TMyArray;
Для обращения к элементам массива с целью чтения или записи достаточно указать индекс элемента в массиве:
NewArray[0]:=199; // 0-му элементу массива присваивается значение 199
I:=NewArray[9]; // в переменную I записано содержимое 9-го элемента массива
При обращении к элементам массива внимательно следите за тем, чтобы передаваемый индекс не выходил за границы массива, в противном случае вы получите сообщение об ошибке.
Иногда полезно задавать массив в виде константы. В следующем примере 12 ячеек массива используются для хранения количества дней в месяцах високосного года:
const
DaysInMonth: array [1..12] of byte = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
Вполне реально объявлять «квадратные» и «кубические» массивы, а также массивы более высоких размерностей. Например, объявление двумерного массива размерностью 10 на 10 ячеек выглядит следующим образом:
var MyArray : Array[0..9,0..9] of cardinal;
или
varMyArray : Array[0..9] of Array[0..9] of cardinal;
Но теперь, для того чтобы обратиться к интересующей нас ячейке двумерного массива, потребуется указать 2 индекса:
MyArray[3,6]:=56;
или
MyArray[3][6]:=56;
У рассмотренного выше способа хранения данных есть один существенный недостаток – объявив размерность массива (сделав его статическим), мы не сможем выйти за его границы. А что делать, если заранее даже приблизительно неизвестно, сколько элементов может оказаться в массиве? В таких случаях используют динамические массивы, которые отличаются от статических тем, что их границы могут изменяться во время работы приложения.
Естественно, объявление динамического массива выглядит несколько иначе:
var MyArray: array of INTEGER;
Как видите, при объявлении массива мы не определяем его размерность. Но перед заполнением массива нам все-таки придется это сделать с помощью метода SetLength():
SetLength(MyArray, 10); //распределяем память для 10 элементов массива
Отсчет элементов динамического массива всегда начинается с нуля. При работе с многомерным динамическим массивом, например следующего вида:
var MyArray : Array of Array of Char;
все размерности массива можно задавать одновременно:
SetLength(MyArray, 10, 5); //распределили память для 2-мерного массива или последовательно для каждого индекса. Массивы с переменной длиной по разным индексам называют динамическими разреженными массивами.
SetLength(MyArray,3); //массив состоит из 3 строк
SetLength(MyArray[0],3); //в нулевой строке 3 элемента
SetLength(MyArray[1],2); //в первой строке 2 элемента
SetLength(MyArray[2],10); //во второй строке 10 элементов
При работе с однотипными динамическими массивами наиболее эффективным способом копирования данных из одного массива в другой считается вызов функции Copy(). Метод умеет копировать как массив целиком, так и некоторые его элементы.
var Arr1, Arr2 : array : of integer;
…
SetLength(Arr1,4)
for i:=0 to High(Arr1) do Arr1[i]:=i; //заполнение массива данными
Arr2:=Copy(Arr1); //полное копирование
Arr2:=Copy(Arr1, 0, 2); //копирование части массива
Заметьте, что мы не задаем длину второго массива, она будет определена автоматически с вызовом метода Copy(). Кроме того, динамические массивы понимают механизм ссылок.
var Arr1, Arr2 : array : of integer;
…
Arr2:=Arr1;
Обратите внимание: при простом копировании массивы хранят данные в разных областях ОЗУ, а при использовании оператора присваивания оба массива будут ссылаться на одну и ту же область памяти. И если теперь нулевому элементу первого массива присвоить значение 10, то это же значение окажется в нулевой ячейке второго массива.
Массив можно передавать и как параметр метода, правда, с некоторыми ограничениями. Если параметр представляет собой статический массив, то массив предварительно должен быть типизирован.
typeTMyArray = Array [0..5] of Byte;
procedure Proc1(Arr : TMyArray);
vari : integer;
Begin
for i:=Low(Arr) to High(Arr) do Arr[i]:=0;
end;
Объявление процедуры с аргументом в виде динамического массива несколько проще:
procedure Proc1(Arr : Array of Byte);
Записи
Рассмотрим небольшую задачу. Необходимо организовать учет сотрудников фирмы. Учету подлежат: фамилия работника, его заработная плата и стаж работы на предприятии в годах. На первый взгляд решение лежит на ладони – берем три переменные типа String, Currency и Byte соответственно и с их помощью обрабатываем данные. Но эта задача решается элегантнее с помощью механизма записей. Объявляем тип TPeople:
Type
TPeople = Record
Surname : String;
Money : Currency;
Experience : Byte;
end;
Запись TPeople определяет единый тип данных, содержащий три разнотипных элемента, называемых полями записи. Доступ к полям записи осуществляется по их имени. Вот пример заполнения такой записи:
var People : TPeople; //объявление переменной на основе типа данных TPeople
Begin
People.Surname := 'Петров';
People.Money := 1500.55;
People.Experience := 10;
end;
Впрочем, для определения одной единственной переменной-записи совсем необязательно ее типизировать. Приведем пример объявления такой переменной:
Var S : record
Name : string;
Age : Integer;
Experience : byte;
end;
Стоит пойти дальше – объединить достоинства массива (хранилища множества однотипных данных) и записи, позволяющей хранить разнотипные данные.
var MyArray : Array[0..99] of TPeople; //объявлен массив записей
Begin
MyArray[0].Family:='Иванов';
MyArray[0].Money:=1500.55;
MyArray[0].Experience:=10;
end;
В представленном выше листинге мы объявили массив MyArray, содержащий 100 элементов типа TPeople, и заполнили нулевой элемент массива. Современный синтаксис позволяет создавать записи с различными вариантами полей. Будем называть такую запись записью с вариантным полем. Синтаксис объявления выглядит следующим образом:
type <имя_типа_записи> = record
<поле_1>: <тип_данных_1>;
<поле_2>: <тип_данных_2>;
...
Дата добавления: 2016-03-15; просмотров: 1784;