http://sulfurzona.ru/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Wing-Thunder Game (fly simulator)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
Скачать игру Крыло-Гром (Wing-Thunder) бесплатно
 
 

Паскаль для новичков (часть 11)

Оператор цикла с параметром

 
Автор: Владислав Демьянишин
 
Паскаль для новичковДовольно часто возникает необходимость в организации циклического выполнения некоторого процесса с заданным числом повторений. Необходимую конструкцию можно собрать из уже известных операторов while- и repeat- циклов. Но тогда программисту придется позаботиться об инкременте или декременте переменной, ведущей отсчет итераций, а также задании начального и конечного значения диапазона этой переменной, когда она выступает еще и в роли индекса, например, для обращения к некоторому массиву, с которым работает организуемый процесс.
 
Для упрощения решения таких задач существует оператор for-цикла с параметром. Предположим, что необходимо перебросить значения элементов M1[100]..M1[150] массива в элементы с соответствующими номерами другого массива M2, тогда решение может выглядеть так:
 
var M1, M2 : array [0..1000] of integer;
j : integer;
begin
for j := 100 to 150 do M2[ j ] := M1[ j ];
 
Таким образом, переменная j будет изменять свое значение согласно последовательности 100,101,102,..,149,150, что обеспечит 51 выполнение тела цикла.
Аналогичную задачу можно решить, задав выполнение цикла в обратном порядке:
 
for j := 150 downto 100 do M2[ j ] := M1[ j ];
 
т.е. переменная j последовательно примет значения 150,149,..,101,100. Итак, теперь уже ясно, что переменная j используется для ведения отсчета итераций, и в дальнейшем я буду называть ее переменная-счетчик.
 
For-цикл допускает указание диапазона значений не только константами, но и выражениями, которые должны возвращать значение дискретного типа. При этом результат выражений вычисляется один раз (предварительно) и только после этого начинается выполнение цикла. Оператор цикла с параметром предусматривает указание в теле только одного оператора, так что частенько может возникать необходимость в использовании составного оператора:
 
for j := Min + 1 to Max - 1 do begin
M2[ j ] := M1[ j ];
Sum := Sum + M2[ j ];
end;
 
При использовании переменной-счетчика цикла, следует учитывать ограничения:
1) Переменная-счетчик должна быть описана в текущем блоке;
2) Переменная-счетчик должна иметь дискретный тип;
3) Значения границ диапазона должны иметь тип, совместимый с типом переменной-счетчика;
4) Категорически запрещается изменять значение переменной-счетчика в теле цикла, например, при помощи оператора присваивания.
 
Как уже, наверно, стало понятно, тело цикла выполняется один раз на каждое значение переменной-счетчика, в пределах диапазона включительно. Служебное слово to указывает компилятору, что необходимо сформировать цикл с наращиванием (инкрементом) значения переменной-счетчика. Перед началом выполнения первой итерации цикла проводится проверка, и если начальное значение больше чем конечное, то цикл не производит ни единой итерации, т.е. тело цикла ни разу не выполняется.
 
Служебное слово downto указывает обратное - на необходимость формировать цикл с убыванием (декрементом) значения переменной-счетчика. Перед началом выполнения первой итерации проводится проверка, и если начальное значение меньше чем конечное, то цикл не производит ни единой итерации.
Если же обе границы диапазона равны, то тело цикла выполняется один единственный раз, независимо от направления изменения значения переменной-счетчика.
Согласно тому, что переменная-счетчик должна иметь дискретный тип, можно сконструировать такой цикл:
 
var Simb : char;
begin
for Simb := 'A' to 'Z' do write( Simb );
end.
 
в итоге получим на экране строчку прописных букв латинского алфавита.
С таким же успехом можно создать цикл, в котором в качестве переменной-счетчика будет переменная перечислимого типа:
 
type TColor = ( clBlack, clWhite, clBlue, clYellow, clRed, clGreen );
const ColorName : array [ clBlack..clGreen ] of string = ( 'Black', 'White', 'Blue', 'Yellow', 'Red', 'Green' );
var Color : TColor;
begin
for Color := clBlack to clGreen do writeln( ColorName[Color] );
end.
 

Оператор над записями

 
Как я уже рассказывал, для применения в программе переменных комбинированного типа (записей) необходимо, при каждом обращении к полям записи указывать имя записи и имя поля, разделяя их точкой. Когда речь идет о записи с двумя или тремя полями, то тут все просто, но когда необходимо инициализировать большое количество полей записи, то творческая работа программиста превращается в чисто машинальную. Это, что касается набора исходного текста программы.
 
Теперь, что касается эффективности кода, получаемого вследствие такой громоздкой конструкции обращения к полям записи. Рассматривая имя записи, компилятор формирует адрес, указывающий на начало всей структуры записи, а считывая имя поля этой записи, компилятор добавляет к полученному адресу смещение, соответствующее данному полю. Таким образом, компилятор, формируя последовательное обращение к многочисленным полям записи в пределах одного составного оператора или блока, создает неэффективный код, так как приводит к загрузке одного и того же адреса записи многократно.
На это еще можно закрыть глаза, если такая конструкция единична и не находится в теле цикла и не выполняется многократно. А что делать, если она все-таки выполняется многократно?
 
Вот как раз для оптимизации таких ситуаций и предназначен оператор над записями. Он позволяет связать серию обращений к полям некоторой записи в единый оператор, с тем, чтобы адрес этой записи считывался только один раз, а затем программа в пределах этого оператора обращалась к полям по их смещениям. В пределах такого оператора отпадает надобность указывать имя записи для каждого поля. Например, конструкцию инициализации записи Image из следующей процедуры:
                                                                                            
                                                                                            
procedure InitImage;
type
TBuf = array [0..65000] of byte;
PBuf = ^TBuf;
TImage = record
               Width, Height, BitCount : word;
               Bits : PBuf;
               end;
var Image : TImage;
begin
Image.Width := 320;
Image.Height := 200;
Image.BitCount := 8;
Image.Bits := nil;
GetMem(Image.Bits,Image.Width*Image.Height*(Image.BitCount div 8));
end;
 
можно заменить на эквивалентную ей, но более эффективную и
компактную:
 
begin
with Image do begin
Width := 320;
Height := 200;
BitCount := 8;
Bits := nil;
GetMem(Bits, Width*Height*(BitCount div 8));
end;
end;
 
Из данного примера видно, что достаточно указать имя записи после служебного слова with, и после служебного слова do разместить составной оператор, содержащий серию манипуляций с полями этой записи.
 
Могут возникать случаи, когда нужно манипулировать полями нескольких записей, тогда в with-операторе можно перечислить имена записей через запятую:
 
with Rec1, Rec2 do begin
end;
 
что абсолютно аналогично конструкции:
 
with Rec1 do
with Rec2 do begin
end;
 
Таким образом, действует правило: если в обеих записях имеется поле с именем, например, Field, то компилятор подразумевает, что это поле принадлежит записи Rec2.
 
Аналогичным образом, если в объемлющем блоке описана переменная с именем, совпадающим с именем одного из полей записи, указанной в with-операторе, то в пределах with-оператора идентификатор такой переменной будет считаться именем поля записи. Пример:
 
procedure
type TRec = record
                    …
                    Field : word;
                    end;
var Field : integer;
      Rec2 : TRec;
begin
with Rec2 do begin
{ эквивалентно Rec2.Field := 10 }
Field := 10;
end;
end;
 
К выше сказанному хочу добавить, что применительно к статическим переменным типа запись или к статическим массивам с элементами типа запись и индексом в виде нетипизированной константы компилятор формирует достаточно эффективный код, так как способен вычислить смещение каждого поля еще на этапе компиляции. Если в качестве индекса элемента такого массива используется переменная, а не константа, то тогда эффективность на стороне with-оператора.
Чтобы добиться высокой производительности при использовании динамических переменных типа массив записей следует применять with-оператор.
 

Пустой оператор

 
Несмотря на то, что пустой оператор не выполняет никаких действий, иногда он позволяет соорудить код программы особым образом. Например, если в ходе составления case-оператора, программист точно знает, что необходимо предусмотреть обработку некоторой альтернативы, но реализацию способа этой обработки он еще не рассматривал, то в такой альтернативе можно поставить точку с запятой, т.е. заглушку для данной альтернативы:
 
case key of
VK_Tab:;
VK_Return:;
VK_Space:;
end;
 
В данном примере зарезервированы пустые операторы для реагирования на нажатия клавиш Tab, Enter и Space соответственно.
Еще пустой оператор может помочь в осуществлении ожидания нажатия на клавишу клавиатуры или мыши:
 
repeat until Ms_Click(mbLeft);
 
В данном примере считается, что repeat-цикл содержит в своем теле пустой оператор, а функция Ms_Click(mbLeft) возвращает true, если указанная клавиша мыши была нажата и отпущена.
 
Есть еще одно очень полезное применение пустого оператора. Предполагается что, например, функция SetTextMode устанавливает текстовый режим с указанным номером, и в случае успеха возвращает true. И в конце программы перед ее завершением необходимо вернуться в обычный текстовый режим с помощью вызова данной функции с номером 0, но результатом этого вызова можно пренебречь.
 
А поскольку синтаксис языка Turbo Pascal версии 6.0 требует оформлять вызов функции в виде оператора присваивания или, как в следующем примере, условного оператора:
 
begin
if not SetTextMode(5) then begin
writeln('Ошибка: режим не поддерживается');
halt;
end;
if SetTextMode (0) then;
end.
 
то достаточно в строке if SetTextMode (0) then; указать пустой оператор сразу за служебным словом then, образованный точкой с запятой, и проблема будет решена.
Хочу заметить, что в версии Turbo Pascal 7.0 имеется директива {$X+}, которая включает расширенный синтаксис языка, при котором вызов любой функции можно оформлять как вызов процедуры, например:
 
{$X+}
function One : integer;
begin
One := 1;
end;
 
begin
One;
end.
 
Если эту директиву отключить {$X-}, то придется вызывать функции в соответствии со старым синтаксисом:
 
{$X-}
function One : integer;
begin
One := 1;
end;
 
var j : integer;
 
begin
j := One;
end.
 
Только хочу предупредить, что функции SetTextMode и Ms_Click, упомянутые здесь, не являются стандартными, а взяты из моих модулей, о которых постараюсь рассказать в дальнейшем.
 
Надеюсь, после всего, что я тут нахомутал, вы смогли сохранить трезвый ум и ясную память ;O)
 
Продолжение следует…
 
© Владислав Демьянишин
 
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ.
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 11): Оператор цикла с параметром
 
 
 
 
 
 
На главную страницу На предыдущую страницу На начало страницы