355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Олег Деревенец » Песни о Паскале (СИ) » Текст книги (страница 20)
Песни о Паскале (СИ)
  • Текст добавлен: 6 ноября 2017, 03:30

Текст книги "Песни о Паскале (СИ)"


Автор книги: Олег Деревенец


Жанр:

   

Драматургия


сообщить о нарушении

Текущая страница: 20 (всего у книги 29 страниц)

type TBoolSet = array [1..CSize] of Boolean;

var BS : TBoolSet;

Теперь условимся, что массив, заполненный значением FALSE, – это пустое множество. А если множество содержит числа A и B, то соответствующие им элементы массива BS[A] и BS[B] содержат TRUE. Тогда операции с этим придуманным нами типом-множеством можно выполнять так (справа показаны аналогичные операции с обычным множеством чисел S).

FillChar(BS, SizeOf(BS), false); { S:= [] – пустое множество }

FillChar(BS, SizeOf(BS), true); { S:= [1..1000] – полное множество }

BS[N]:= true;       { S:= S + [N] – добавление элемента }

BS[N]:= false;       { S:= S – [N] – удаление элемента }

if BS[N] then …       { if N in S then … – проверка вхождения }

Воспользуйтесь таким массивом для поиска простых чисел в диапазоне от 1 до 1000.

Г) Садовая ограда. Вернувшись с курорта, фермер Лефт обнаружил на своем поле чудом выросший сад. Для сохранения деревьев он обнес его прямоугольной оградой. Пусть ширина и высота поля заданы константами CX и CY, пустые места обозначены точками, а деревья – звездочками. Засадите поле случайным образом и распечатайте его. Затем найдите левый верхний и правый нижний углы для ограды и постройте её символом решетки. Ограда должна охватывать деревья, но не выходить за пределы поля (то, что выходит за пределы, не строить). Распечатайте сад с оградой.

Глава 50

Неспортивные рекорды (записи)


Кушать подано!

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

Или взять ранец со школярским добром: книгами, тетрадями, ручками и карандашами. Что, если нагрузить одного ученика всеми учебниками класса, другого – всеми тетрадями, а третьего – карандашами? Удобно им будет?

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

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

Записи

В современных языках такое средство есть. В Паскале оно называется записью, по-английски – RECORD. «Рекорд» – знакомое словцо, не так ли? – оно имеет отношение к спорту. В самом деле, то, что мы называем спортивными рекордами, изначально было лишь записью в журнале регистрации спортивных достижений: кем, когда, где и сколько. Отсюда и пошло слово «рекорд».

Но вернемся к Паскалю. Итак, запись объединяет в единый набор логически связанные, но разнородные данные и дает этому набору имя. Такое объединение обозначают парой ключевых слов RECORD-END и размещают либо в секции объявления типов, либо в секции объявления переменных. Возьмем футбольную команду и соединим её название и набранные ею очки. Объявим для команды тип данных, который так и назовем – Team – «команда». А затем учредим две переменные этого типа, вот как это выглядит.

type Team = record { тип данных «команда» }

      Aces : integer; { набранные очки }

      Name : string; { название команды }

      end;

var Team1, Team2 : Team; { две переменных типа «команда» }

Может показаться, что между ключевыми словами RECORD и END объявлены переменные Aces и Name. Так ли это? И да, и нет. Нет, – потому, что в секции TYPE переменные не объявляют. Да, – потому, что внутри переменных Team1 и Team2 (объявленных чуть ниже в секции VAR) действительно «живут» переменные с именами Aces и Name. Только называются они теперь полями переменных Team1 и Team2.

Как получить доступ к этим полям, спрятанным внутри переменных? Надо к имени переменной добавить так называемый квалификатор поля, или проще – имя этого поля. Вот так присваивают значения полям переменной Team1.

      Team1.Aces := 25;

      Team2.Name := ’Dinamo’;

Как видите, квалификатор поля отделяют от имени переменной точкой. Поля – это «кусочки» более сложных переменных (в данном случае переменных Team1 и Team2). Во всем остальном, кроме способа доступа, поля записей ничем не отличаются от обычных переменных.

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

      Team2 := Team1; { перенос всех полей из Team1 в Team2 }

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

Второй тайм

Слышен свисток к началу второго тайма, вернемся на футбольное поле, точнее к программе «P_41_3», сортирующей команды в порядке занятых ими мест. Сотворим новую версию этой программы «P_50_1», заменив два массива (набранных очков и названий команд) одним. План игры на второй тайм таков. Объявим два типа данных: запись и массив записей. Первый из них – запись – будет содержать сведения об отдельной команде.

type TTeam = record

      mAces : integer; { набранные очки }

      mName : string; { названия команд }

      end;

Напомню, что для улучшения читаемости программ мы условились начинать названия типов данных с буквы «T». По этой причине тип данных «команда» (Team) стал называться TTeam. Подобное соглашение примем и для полей записей. Чтобы отличить их от прочих переменных, будем начинать имена полей с буквы «m» (от Member – «элемент», «участник»). Поэтому поля названы здесь как mAces и mName.

Второй из объявляемых типов данных – TChamp (Champ – «чемпионат») – представляет собой массив футбольных команд.

      TChamp = array [1..CSize] of TTeam; { тип для массива команд }

Стало быть, каждый элемент этого типа содержит внутри себя два поля: число mAces и строку mName. Теперь можно объявить и переменную типа TChamp, то есть, массив команд.

var Champ : TChamp; { массив команд }

Доступ к элементам массива Champ осуществляется так:

Champ[i]       – i–й элемент массива, то есть i–я команда (тип TTeam)

Champ[i].mAces – количество набранных очков i–й командой

Champ[i].mName – название i–й команды

После всего сказанного рассмотрим программу «P_50_1» в целом.

{ P_50_1 – Футбольный чемпионат (версия 2) }

const CSize = 4; { количество команд }

      { объявление типов }

type TTeam = record

      mAces : integer; { набранные очки }

      mName : string; { названия команд }

      end;

      TChamp = array [1..CSize] of TTeam; { тип для массива команд }

var Champ : TChamp; { массив команд }

      { Процедура "пузырьковой" сортировки команд }

procedure BubbleSort(var arg: TChamp);

var i, j : Integer;

      t : TTeam; { для временного хранения при обмене }

begin

for i:= 1 to CSize-1 do { внешний цикл }

      for j:= 1 to CSize-i do { внутренний цикл }

      { если текущий элемент меньше следующего …}

      if arg[j].mAces < arg[j+1].mAces then begin

      { то меняем местами соседние элементы }

      t:= arg[j];       { временно запоминаем }

      arg[j]:= arg[j+1]; { следующий -> в текущий }

      arg[j+1]:= t;       { текущий -> в следующий }

      end;

end;

var i: integer;

begin       {– Главная программа –}

{ Вводим названия команд и набранные очки }

for i:=1 to CSize do begin

      Write('Название команды: '); Readln(Champ[i].mName);

      Write('Набранные очки: '); Readln(Champ[i].mAces);

end;

BubbleSort(Champ); { сортируем }

{ Выводим результаты }

Writeln('Итоги чемпионата:');

Writeln('Место Команда       Очки');

for i:=1 to CSize do begin

Writeln(i:3,' ':3, Champ[i].mName, Champ[i].mAces:(20-Length(Champ[i].mName)) );

end;

Readln;

end.

Процедура сортировки заметно упростилась. Ещё бы! Ведь теперь мы работаем с одним массивом, а не с двумя. Для временного хранения элемента массива (при обмене) в процедуре объявлена переменная типа TTeam. А в прежнем решении для этого нужны были две переменные. Прочие изменения в программе невелики, хотя и существенны: вместо обращений к элементам массива мы обращаемся к полям этих элементов (эти места выделены).

Напомню смысл выражения для ширины поля при печати набранных очков.

      20-Length(Champ[i].mName)

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

Дополнительное время

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

type TTeam = record

      mAces : integer; { набранные очки }

      mName : string; { названия команд }

      mWins : integer; { количество выигрышей }

      mFails: integer; { количество проигрышей }

      end;

Внутрь каждого элемента массива Champ подселены ещё два поля, осталось лишь организовать ввод и вывод этих данных. Но к процедуре сортировки BubbleSort прикасаться уже не надо, – она не изменится! Поэтому в показанной ниже программе «P_50_2» я не стал её повторять. Не стал я заниматься и обработкой поля mFails – количество проигрышей. Уверен, что вы и без меня справитесь с этим.

{ P_59_2 – Футбольный чемпионат (версия 3) }

const CSize = 4; { количество команд }

      { объявление типов }

type TTeam = record

      mAces : integer; { набранные очки }

      mName : string; { названия команд }

      mWins : integer; { количество выигрышей }

      mFails: integer; { количество проигрышей }

      end;

      TChamp = array [1..CSize] of TTeam; { тип для массива команд }

var Champ : TChamp; { массив команд }

{ Процедура пузырьковой сортировки не изменилась! }

procedure BubbleSort(var arg: TChamp);

...

end;

var i: integer;

begin       {– Главная программа –}

{ Вводим названия команд, набранные очки и прочие данные }

for i:=1 to CSize do begin

      Write('Название команды: '); Readln(Champ[i].mName);

      Write('Набранные очки: '); Readln(Champ[i].mAces);

      Write('Выигрышей: ');       Readln(Champ[i].mWins);

end;

{ сортируем }

BubbleSort(Champ);

{ Выводим результаты }

Writeln('Итоги чемпионата:');

Writeln('Место Команда       Очки Выигрышей');

for i:=1 to CSize do begin

      Write(i:3,' ':3, Champ[i].mName,

      Champ[i].mAces:(20-Length(Champ[i].mName) ));

      Writeln(Champ[i].mWins:8);

end;

Readln;

end.

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

Итоги

• Для соединения разнородных, но связанных общим смыслом данных используют записи.

• Запись заключается в пару ключевых слов RECORD-END, между которыми перечисляются имена и типы полей, входящих в запись.

• На основе записей могут быть построены как одиночные переменные, так и массивы.

• Доступ к полям записей выполняется через имя переменной и имя поля, разделяемые точкой.

А слабо?

А) Дополните программу «P_50_2» с тем, чтобы обработать все поля записи.

Б) Предложите структуру записи для полицейской базы данных. Какие данные следует, по вашему мнению, включить в неё?

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

Г) В текстовом файле тремя колонками представлены сведения о школьниках: фамилия, рост и вес. Ваша программа должна преобразовать его в три других файла, где эти же сведения отсортированы соответственно: 1) по фамилиям, 2) по росту и 3) по весу учеников.

Д) Домино. В этой игре используют 28 костяшек, каждая из которых содержит пару чисел от 0 до 6. Например: 0:0, 1:5, 6:6. Представьте костяшку записью, а игральный набор – массивом этих записей. Заполните массив костяшек и распечатайте его. «Смешайте» костяшки случайным образом и вновь распечатайте массив. Для удобства направьте распечатку в текстовый файл.

Е) Карты. Колода содержит 36 карт четырех мастей: трефы и пики – черные, а бубны и червы – красные. Относительная сила карты определяется числом от 6 до 14. Представьте карту записью, содержащей её масть, цвет и силу. Представьте колоду массивом записей, сформируйте полную колоду и распечатайте в текстовый файл. «Перетасуйте» колоду и вновь распечатайте в файл. При распечатке силу карт от 11 до 14 напечатайте их названиями: валет, дама, король, туз.

Глава 51

Указатели в море памяти

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

Погружение в оперативную память

Оперативная память содержит миллионы байтовых ячеек, – вы знаете об этом. Каждой ячейке назначен уникальный номер, иначе говоря – адрес. Уникальный – это значит, что все адреса разные, – так нумеруют дома на улицах и квартиры в домах. Первой ячейке памяти присвоен адрес 0, второй – 1 и так далее. Подобно тому, как почтальон находит дом по номеру, процессор обращается к данным по адресам ячеек, где они хранятся.

Прежде, чем «задышать», программа должна перекочевать с диска в оперативную память. Рассказать про это? С включением питания компьютера в дело вступает стартовая программа – загрузчик, прошитый в постоянной памяти материнской платы. Эта программка загружает с диска в оперативную память вашу любимую операционную систему, – ритуал сопровождают загадочный скрип, мигание и попискивание. К тому моменту, когда на экране появляется знакомая картинка, часть памяти занимает операционная система. Дальше все определяют капризы пользователя. Он волен загрузить одну или несколько программ, после чего их размещение в памяти может стать таким, как показано на рис. 115.

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

Не применяйте слов, смысла которых не знаете. Что значит «динамический»? «Динамо» – греческое слово и означает силу, мощь. Оттого и полюбилось спортивным клубам, но к нашей теме это толкование не подходит. Сила порождает движение, потому «динамический» стали употреблять в смысле «подвижный», «быстрый». А программисты придали этому слову ещё один оттенок: «изменчивый», «непостоянный», разумея под этим изменчивое размещение в памяти данных и программ, – так будем понимать это слово и мы.


Рис.115 – Распределение оперативной памяти

«Планировка» памяти

Обратимся к правой части рис. 115, где упрощенно показано распределение памяти внутри одной программы. Эта память делится на три части или секции (Section – «отделение»). Одна из них вмещает исполняемый код, то есть процедуры, функции и главную программу. Другая – секция данных – отведена для глобальных переменных, а третья – так называемый стек – для параметров процедур и локальных переменных. Сейчас надо усвоить лишь две простые вещи, а именно:

• все секции программы (как и программа в целом) имеют фиксированные, то есть постоянные размеры, определяемые при компиляции программы;

• все объекты программы – процедуры, функции, переменные – обладают своими «личными» адресами в оперативной памяти; эти адреса определяются при загрузке программы, и потому могут изменяться от одной загрузки к другой.

Рассмотрим пример. Пусть в программе объявлены четыре переменные.

var B : Boolean;       C : char; I : integer;       S : string;

После загрузки программы в оперативную память они «расселятся» в соседних ячейках памяти, начиная с некоторого начального адреса N так, как показано на рис. 116.


Рис.116 – Размещение переменных в оперативной памяти

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

Указатели, первое знакомство

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

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

var P : ^integer; { указатель на целое }

      N : integer;       { целое }

begin

      P:= @N;       { указателю назначается адрес переменной N }

      P^:= 125;       { переменной присваивается значение через указатель }

      Writeln(N);       { 125 }

end.

В первой строчке объявлен указатель P. Это сделано специальным значком – стрелка вверх «^», – эта стрелка ставится перед именем типа, с которым будет работать указатель. В данном случае указатель P предназначен для хранения адресов переменных типа INTEGER.

Первый из исполняемых операторов

      P:= @N;

заносит в указатель P адрес переменной N. С этого момента указатель P ссылается на то место в памяти, где «живет» переменная N. Обратите внимание на «почтовую собачку» перед N – это операция взятия адреса. То же самое можно сделать функцией взятия адреса.

      P:= Addr(N);

Следующий далее оператор программы

      P^:= 125;

присвоит переменной N значение 125. Из чего это следует? Ведь переменной N в этом операторе нет! Все дело в стрелочке «^», стоящей после указателя P, теперь она играет другую роль. Добавление стрелочки за указателем ведет к тому, что число 125 попадает в область памяти, на которую ссылается указатель P, то есть по месту жительства переменной N.

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

      Writeln(100+P^); { 225 }

      Writeln(100+N); { 225 }

Введите рассмотренный пример в компьютер и проверьте его в действии.

Объявление указателей

Сколько типов данных способен придумать программист? Не сосчитать! И для каждого из них можно объявить свой тип указателя, например:

var PI : ^integer; { указатель на целое }

      PC : ^Char;       { указатель на символ }

      PS : ^String; { указатель на строку }

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

type PInt = ^Integer;       { тип указателя на целое }

      PChar = ^Char;       { тип указателя на символ }

      PString = ^String;       { тип указателя на строку }

Как всегда, имя объявляемого типа выбираем по вкусу. Здесь лучше придерживаться традиций, а они рекомендуют начинать названия типов-указателей с буквы «P» (от Pointer – «указатель»).

Объявив тип, можно объявить затем переменные этого типа, например:

var p1, p2 : PInt; { два указателя на целое }

      p3 : PChar; { указатель на символ }


Копирование указателей, пустой указатель

Я сказал, что указатель содержит адрес переменной, стало быть, это число? Да, но не совсем обычное. Нас интересует не само это число, а лишь то, на что оно указывает.

Если нужны несколько указателей на одну и ту же переменную, указатели копируют, например:

      P1 := @X1; { В указатель P1 заносится адрес переменной X1 }

      P2 := P1; { Оба указателя содержат адрес переменной X1 }

Теперь оба указателя ссылаются на переменную X1 (хотя сам по себе адрес переменной X1 нас не интересует). Но копировать можно лишь указатели одного типа, – за этим соответствием следит компилятор.

А на что ссылается указатель, которому не присвоено значение? Как любая неинициализированная переменная, он содержит мусор и указывает «пальцем в небо». Для пометки временно не используемого указателя ему присваивают специальное значение NIL. Это зарезервированное слово языка – подобие нуля для чисел. Значение NIL можно присвоить указателю любого типа, например:

      p1 := nil; { p1 – пустой указатель на целое }

      p3 := nil; { p3 – пустой указатель на символ }

Указатели похожи на письма, а переменные – на дома, куда эти письма адресованы (рис. 117).


Рис.117 – Указатели подобны письмам

Судя по рис. 117, жителям первого дома повезло, – им адресованы два письма. Третий конверт – без адреса, он пуст и подобен указателю, содержащему NIL.

Сравнение и проверка указателей

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

      if p1=p2 then …

      if p1<>p2 then …

      if p3=nil then …

Сравнением с NIL выясняется, свободен ли указатель или ссылается на что-то. Но значение NIL в указатель должен занести программист, само оно там не появится!

Проверить незанятость указателя можно как сравнением с NIL, так и функцией Assigned. Она принимает указатель любого типа, а возвращает булев результат. Вот примеры её применения.

      p1 := @X; p3 := nil;

      Writeln (Assigned(p1));       { true }

      Writeln (Assigned(p3));       { false }

Функция Assigned возвращает FALSE, если указатель содержит NIL.

Разыменование указателей

Этим неуклюжим словом – разыменование – названа операция, обратная взятию адреса. Разыменование превращает указатель в переменную, на которую он ссылается. Операция обозначается следующей за указателем стрелкой вверх «^», вот пример.

      p1 := @X;       { назначение адреса указателю P1 }

      X := 25;

      Writeln (p1^);       { 25 }

      X := 100;

      Writeln (p1^);       { 100 }

Здесь показано, что с изменением переменной X меняется и значение P1^. Иначе говоря, если P1=@X, то P1^=X (а верно ли обратное?).

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

Нетипичный указатель

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

var P1, P2 : pointer;       N : integer;       S : string;

begin

      P1:= @N; P2:= @S;

      if P1=P2 then Writeln('Указатели совпадают');

      if P1<>nil then Writeln('Указатель не пустой');

end.

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

type PInt = ^integer; { тип указателя на целое }

var P : pointer;       N : integer;

      …

      P:= @N;

      Writeln( PInt(P)^ ); { печатается значение N }


Примеры с указателями

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

{ P_51_1 – Указатели }

var A, B, C : integer;       { целые числа }

p1, p2, p3 :^integer; { указатели на целые числа }

begin

{ Присвоение значений переменным }

A:= 10; B:= 20; C:= 30;

{ Последовательное переключение одного указателя на разные переменные }

p1:= @A; Writeln(p1^);

p1:= @B; Writeln(p1^);

p1:= @C; Writeln(p1^);

{ Настройка трех указателей на одну переменную }

p1:=@B; p2:=p1; p3:=p1;

Writeln(p1^:6, p2^:6, p3^:6);

{ Арифметические действия через указатели }

C:= 2 * p1^;

Writeln(C); { C= 2 * B = 40 }

Readln;

end.

Результат работы этой программы таков.

10

20

30

20 20 20

40

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

В программе «P_51_2» мы ещё раз увидим это, а вдобавок исследуем размеры указателей на переменные разных типов, – отличаются ли они?

{ P_51_2 – Указатели разных типов, размеры указателей }

type PBool= ^boolean; { Тип указателя на булевскую переменную }

      PInt = ^integer; { Тип указателя на целое число }

      PStr = ^string; { Тип указателя на строку }

var B : boolean;

I : integer;

S : string;

pB : PBool; { Указатель на булевскую переменную }

pI : PInt; { Указатель на целое число }

pS : PStr; { Указатель на строку }

begin

{ Настройка указателей на переменные }

pB := @B; pI := @I; pS := @S;

{ Присвоение значений переменным через указатели }

pB^ := true;

pI^ := 10;

pS^ := 'Hello!';

{ Распечатка значений переменных }

Writeln(B:6, I:6, S:10);

{ Исследование размеров типов и указателей на них }

Writeln('Boolean = ',SizeOf(Boolean):6, SizeOf(PBool):6);

Writeln('Integer = ',SizeOf(integer):6, SizeOf(PInt ):6);

Writeln('String = ',SizeOf(String ):6, SizeOf(PStr ):6);

Readln;

end.

Вот «продукция» этой программы.

true 10 Hello!

Boolean = 1       4

Integer = 2       4

String = 256 4

Любопытны три последних строки. Они показывают, что размеры указателей на переменные всех типов одинаковы и для 32-разрядных систем составляют 4 байта (подобно тому, как размер конверта не зависит от размера дома, куда он адресован).

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

Итоги

• Память компьютера – это последовательность ячеек, которым назначены уникальные адреса.

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

• Для хранения адресов применяют переменные особого типа – указатели. Каждому типу переменных соответствует свой тип указателя.

• Перед использованием указателя ему присваивают либо адрес переменной, либо пустое значение NIL.

• С указателями допустимы лишь три операции: копирование, сравнение и разыменование.

• Разыменованный указатель – это переменная, на которую он ссылается в данный момент; с ним можно поступать как с этой переменной.

• Указатели всех типов имеют одинаковый размер, который для 32-разрядных операционных систем составляет 4 байта.

А слабо?

А) Какие ошибки найдет компилятор в следующей программе? Объясните их.

var P1 : ^Integer; P2 : ^String;

      N : Integer;       S : String;

begin

      P1 := @S;

      P2 := @N;

end.

Б) Будет ли работать следующая программа? В чём ошибки?

var P1 : ^Integer;

begin

      P1 := 0;

      P1^ := 30;

      P1 := nil; Writeln(P1^);

end.

В) Откройте программу «P_51_1» и введите в окно обзора переменные P1 и P1^ (комбинацией Ctrl+F7). Выполняя программу по шагам, наблюдайте за переменными. Сделайте то же с программой «P_51_2».

Глава 52

Динамические переменные

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

Аппетит является к обеду

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

Но так будет не всегда. Есть немало задач, где предугадать объём данных нельзя даже приблизительно. Вот, к примеру, полицейская база данных по угнанным автомобилям, каков должен быть её размер? Тысяча или миллион элементов? В спокойной стране достаточно будет десятка, а там, где угоняют каждый третий автомобиль… Ох! Лучше не спрашивайте! Можно, конечно, объявить массив с солидным запасом, но это породит ещё две проблемы. Во-первых, большая часть массива вероятней всего будет пустовать, – разумно ли транжирить память попусту? Второй случай ещё злее: в какой-то момент не хватит даже этого запаса, и программа «рухнет».

Безупречное решение – выделять данным ровно столько памяти, сколько нужно. То есть, создавать переменные, когда нам надо, и уничтожать их, когда потребность в них отпадает. Отличная мысль! Двинемся в этом направлении!

Одолжите памяти немножко!

Вернитесь к рисунку 51-й главы, где показано размещение программ в оперативной памяти. Большая часть этой памяти остается «не вспаханной», свободной. Куча – так принято её называть (по-английски – Heap). Операционная система распоряжается кучей по своему усмотрению, и все же большие куски этой памяти простаивают без дела. Нельзя ли программе временно одолжить частичку? Оказывается, можно! Надо лишь освоить работу с указателями. В предыдущей главе мы применяли указатели на переменные, не видя в том особой пользы. Другое дело – участки памяти в куче, у которых нет имени. Здесь указатели – единственное средство для доступа к этим залежам.

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

Выделение памяти

Для получения кусочка памяти из кучи, вызывают процедуру New (что значит «новый»). Этой процедуре нужен лишь один параметр – указатель некоторого типа. Процедура бронирует в куче кусок соответствующего размера, и адрес этого куска помещает в указатель. Так рождается новая переменная в куче. Рассмотрим пример.

var York : ^Integer; { указатель на целое }


    Ваша оценка произведения:

Популярные книги за неделю