#11 Записи сумасшедшего

Вступление

Здраствуйте!
Рассылка вновь набирает обороты - лучше это или хуже судить вам. Пока, что читайте очередной выпуск. Суть его отражает название :)

Теория

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

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

Запись объявляется таким образом:

ИМЯ = RECORD поля END
Поля записей - это переменные любого типа (в том числе и записи !). Объявляются они так же как и в разделе переменных:
type
TBook = record
           author, name : string;
           page : integer
       end;

var
  book, b1 : TBook;
Таким образом в записи TBook у нас 2 поля типа string и одно поле типа integer. Для одинаковых записей возможно применение операции := Наприер мы можем сделать так book := b1;

доступ к полям осуществляется таким образом: имя_переменной.имя_поля Т.е. через точку! Например так:

begin
   book.author := '****';
   book.name := 'Pascal-Tutorial';
   book.page := 17;
В отладчике вы можете просматривать как значения отдельных полей, так и значение всей записи сразу (отобразятся все поля сразу).

Что бы упростить доступ к полям записи можно использовать оператор with:

with переменная do оператор
Сделано это, что бы каждый раз не указывать имя переменной. Например мы можем написать так:
with book do
begin
   author := '****';
   name := 'Pascal-tutorial';
   page := 17;
   writeLn ('Author - ', author, ' Name - ', name, ' # - ', page)
end
согласитесь удобно. Однако этот способ хорош только когда либо полей действительно много, либо когда с полями нужно совершать много действий.

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

function SizeOf(X): Integer;
возвращает размер аргумента Х в байтах. Например: writeLn (sizeof (book) ) выведет на экран 514 - можно подсчитать, что это правда: сосчитайте отдельно размер каждого поля - author и name по 256 байт (так как строки это array [0 .. 255] of char - а char занимает 1 байт => размер равен числу элементов массива - 256). Размер page = 2 байта. Так мы получаем: 256 + 256 + 2 = 514.

Программа

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

  • Имя автора
  • Название книги
  • Число страниц
  • Цена
  • Дата издания
конечно можно было бы добавить и ещё что-то, но нам и этого хватит. Кстати дату тоже можно организовать в виде записи. С этого и начнём:
type
       Tdate = record
          d, m, y : integer
        end;
Чудненько! Теперь ничто нам не мешает создать запись, которая будет отвечать за книгу:
Tbook = record
          author, name : string;
          page, cost : integer;
          date : tdate
        end;
Итак заметьте, что поле date в свою очередь является записью. Пусть у нас есть переменная с именем book типа tbook. Тогда что бы обратиться к месяцу нужно написать такое: book.date.m !
Ну и что бы было совсем круто :) опишем запись библиотеки, в которой будут массив из книг (то бишь типа Tbook) и число книг в библиотеке.
Tlib = record
          book : array [1 .. Max] of Tbook;
          num : byte
        end;
где Max - константа, отвечающая за вместимость библиотеки. Напишем програмку для малюсенькой библиотеки книг этак на 50.
program homelib;

uses CRT;

const
   Max = 50;

type
   Tdate = record
             d, m, y : integer
           end;
   Tbook = record
             author, name : string;
             page, cost : integer;
             date : tdate
           end;
   Tlib = record
             book : array [1 .. Max] of Tbook;
             num : byte
           end;

function Menu : char;
begin
  ClrScr;
  writeLn (' БИБЛИОТЕКА.');
  writeLn (' 1 - добавить книгу.');
  writeLn (' 2 - показать книги.');
  writeLn ('ESC - выход.');
  Menu := readkey
end;

procedure Add (var new : tlib);
begin
  if new.num = Max then
  begin
    writeLn ('Нет места на полках :(');
    readLn;
    exit
  end;
 new.num := new.num + 1;
  with new.book[new.num] do
  begin
    Write ('Введите имя автора: ');
    readLn (author);
    write ('Введите название: ');
    readLn (name);
    write ('Введите число страниц: ');
    readLn (page);
    write ('Введите цену: ');
    readLn (cost);
    write ('Введите дату ДД.ММ.ГГГГ: ');
    with date do
    begin
      readLn (d, m, y)
    end
  end
end;

procedure Show (lib : tlib);
var
  i : integer;
begin
  for i := 1 to lib.num do
  begin
    writeLn ('===========================');
    writeLn ('Книга номер ', i);
    with lib.book[i] do
    begin
      writeLn ('Автор ', author);
      writeLn ('Назвние ', name);
      writeLn ('Число страниц ', page);
      writeLn ('Цена = ', cost);
      with date do
        writeLn ('Дата ', d, '.', m,'.', y)
    end
  end;
  readLn
end;

var
  lib : tlib;
  key : char;

begin
   lib.num := 0;

 while key <> #27 do
 begin
   key := Menu;
   case key of
     '1': Add (lib);
     '2': show (lib)
   end
 end
end.
Итак быстренько копируем запускаем... круто ошибок нет ! ... нажимаем 1, вводим все поля... жмём 2... чёрт ! А это что ещё такое:
Error 202: Stack overflow error.

Итак перед вами сообщение о переполнении стека (Stack). Где же оно возникает? давайте выполним пошагово с заходом в функции: жмём F7.... вводим данные... опа! вот оно! На входе в процедуру show.

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

Нажмите Ctrl+F4 (из меню Debug -> Evaluate/modify...) перед вами возникнет окошко. В поле Expression можно вводить выражение, которое надо подсчитать. Давайте подсчитаем размер нашей структуры tlib: введём в это поле sizeof (tlib) и посмотрим на результат (поле Result) - там будет размер записи tlib в байтах, а именно 26 101.

Какой же у нас размер стека ? Это можно узнать таким образом: Options -> Memory sizes...
Перед вами открылось окно с непонятными названиями и цифрами. Причём подсвечен именно размер стека (Stack size). Если вы ничего не меняли, то он должен быть равен 16 384. Кстати обратите внимание, что слова Stack Size упоминаются 3 раза: первый раз в разделе Real Target, второй - Protected Target, и наконец третий - Windows Target. Наши программы пока относятся к Real (ведь они такие реальные:). Про остальные две цели :) (target - цель англ.) я раскажу позже. Сейчас запомните, что мы пишем программы для реального режима (Real mode) процессора (не правда ли звучит гордо :) Поэтому нам и надо Real target. Подробности как всегда будут позже. Такой я нехороший.

Так вот вернёмся к нашим баранам. Размер стека стоит 16 384, а размер одной нашей структуры уже 26 101. Не правда ли большая разница ? Давайте увеличим размер стека. Для этого просто введите его размер. Давайте и введём 26101. Запускаем программу .... (для этого вместо привычных Ctrl+F9 нужно нажать сначала F9, а уж потом Ctrl+F9) досада! опять та же ошибка. Однако давайте вспомним, что я написал немного выше - в стеке хранятся и локальные переменные и некоторые служебные данные. Поэтому нам нужно увеличить его размер ещё немного. Давайте не пожадничаем и увеличим размер на 1 килобайт.

После этих действий программа должна работать нормально. Вернёмся теперь к сути программы.
Итак у нас программа состоит из 2-х процедур и одной функции. Функция Menu у нас "рисует" на экране меню и возвращает нажатую клавишу. В главной программе в зависимости от этого добавляем книгу, выводим данные о всех книах или выходим (этот случай когда key = #27. 27 - код клавиши ESC).

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

Как вы яхту назовёте, так она и поплывёт!

Думаю вы уже обратили внимание, что я начинаю название всех типов с буквы t. Это сокращение от слова Type. Так ведь удобнее и сразу поймёшь, где у тебя тип, а где что-то ещё. Однако некоторые пошли дальше и используют подобные сокращения у всех имён. Например названия функций начинают с букв fn, переменных целого типа с i, строк - sz и т.д. Это так называемая Венгерская нотация. Она активно используется при программирование в Windows, т.к. была разработана Чарзом Симони (программист Microsoft). Она имеет свои выгоды: вы всегда по имени переменной понимаете, какого она типа. Однако никто не заставляет ей пользоваться. Хотя я и перенял вредную привычку начинать новые типы с буквы t.

Послесловие

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


[Назад] [Содержание] [Дальше]
Hosted by uCoz