Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Демидов Основы программирования в примерах на языке ПАСЦАЛ 2010

.pdf
Скачиваний:
128
Добавлен:
16.08.2013
Размер:
1.28 Mб
Скачать

i: integer; List,p: pNode;

begin

List := NIL;

for i:=Low(a) to High(a) do begin OrderedInsert(List, a[i]);

end;

// переписываем упорядоченный список обратно в массив p := List;

for i:=Low(a) to High(a) do begin a[i] := p^.elem;

p := p^.next; end;

DisposeList(List); end;

begin

Sort(a);

end.

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

Low(TypeOrArray) и High(TypeOrArray). Функция Low возвращает минимальное значение индекса массива, а функция High – максимальное. Функции работают как для переменных-массивов, так и типов-массивов. Когда в процедуру сортировки передается массив а, то неявно в процедуру передаётся информация о его типе, в том числе о диапазоне значений индекса.

Задание. Реализовать процедуру OrderedInsert для вставки элемента массива в упорядоченный список.

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

Использование указателя на указатель позволяет реализовать процедуру OrderedInsert лаконично и изящно, хотя и сложнее для понимания:

161

// вставка с помощью указателя на указатель

procedure OrderedInsert2(var List: pNode; elem: integer); var

n: pNode; pp: ^pNode;

begin

New(n);

n^.elem := elem; n^.next := NIL;

// вставляем первый элемент if List = NIL then List := n else begin

pp := @List;

while (pp^ <> NIL) and (elem > pp^^.elem) do

pp:= @(pp^^.next);

//если не последний, то приставим хвост if (pp^ <> NIL) then n^.next := pp^;

//переставляем найденный указатель на новый элемент pp^ := n;

end; end;

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

162

Глава 16. Командная строка, стиль, тестирование и отладка

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

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

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

format c: /q

При этом программа анализирует все параметры и запускает быстрое форматирование нужного диска. Если в командной строке указать

format /?

то на экран будет выдана справка об использовании утилиты с описанием всех возможных параметров.

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

function ParamCount: word – возвращает количество аргументов, переданных в программу при вызове из командной строки;

function ParamStr(k: word): string – возвращает k-й аргумент,

переданный в программу из командной строки.

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

program PrintArgs; var

i,k,n: word; begin

163

n := ParamCount;

writeln('Всего аргументов в командной строке: ', n); for i := 1 to n do

writeln('№', i, ': ', ParamStr(i));

end.

Пусть исполняемый файл программы имеет имя PrintArgs.exe. Если вызвать эту программу из командной строки следующим образом:

printargs arg1 /? -275.15 "c:\program files\office"

то на экране будет распечатано

Всего аргументов в командной строке: 4 №1: arg1

№2: /?

№3: -275.15

№4: c:\program files\office

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

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

Тестирование и отладка

Цель тестирования – нахождение ошибок. Формально ошибкой можно считать несоответствие поведения программы предъявляемым требованиям. Цель отладки – нахождение причин возникновения ошибки.

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

164

чтобы всегда иметь работающую версию, постепенно усложняя программу.

Восходящий и нисходящий подходы к построению программ

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

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

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

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

При простейшем сценарии тестирования следует проверить: 1) работу программы для случайных входных данных;

165

2)работу программы для экстремальных значений входных данных (например, границы допустимых диапазонов, нулевые значения);

3)работу программы в исключительных ситуациях (внешнее окружение программы также может послужить источником сбоев);

4)«защиту от дурака» (данные могут иметь неверный

формат, отсутствовать; пользователь должен иметь право на ошибку).

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

Когда ошибка найдена, можно приступать к отладке программы. Анализ причин возникновения ошибки начинается с локализации ошибки. Следует выяснить, в каком месте программы ошибка возникает. Как только найден оператор, приводящий к ошибке, можно приступать к анализу источника (причины) ошибки.

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

Общий сценарий отладки может выглядеть следующим образом:

1)локализация ошибки;

2)выработка предположения о причине ошибки;

3)исправление программы в соответствии с выдвинутым предположением;

4)попытка воспроизведения ошибки на тех же входных данных;

5)при повторном проявлении ошибки перейти к шагу 2. После исправления ошибки имеет смысл обобщить её и защи-

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

Перечислим подходы к локализации ошибки и анализу причин её возникновения:

166

1. Комментирование подозрительных участков кода и ис-

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

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

2. Пошаговое выполнение программы (трассировка). Трас-

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

Работа отладчика управляется командами меню (Run в среде Turbo Pascal) или горячими клавишами:

Run to cursor – выполнить операторы, расположенные до текущей позиции курсора в редакторе исходного кода, и приостановить выполнение программы;

Trace Into – войти внутрь подпрограммы, вызов которой выполняется в текущей строке. Дальнейшая трассировка будет осуществляться для строк подпрограммы. После завершения работы подпрограммы трассировка продолжится со строки, следующей за вызовом подпрограммы в основной программе;

Step Over – перейти к следующей строке, исполнив код в текущей строке, и приостановить выполнение программы. При этом если в строке вызываются подпрограммы, то они выполняются за один шаг трассировки;

Run – запуск программы. Если она находится в режиме трассировки, то ее выполнение продолжится с текущего шага трассировки. Выполнение программы после команды Run уже не может быть приостановлено;

167

Program Reset – выход из режима трассировки и остановка программы.

Трассировка может быть продолжена любой из команд Run to cursor, Trace into и Step over. Если на каком-либо шаге трассировки возникает ошибка, то выполнение программы прекращается и на экран выводится сообщение об ошибке. При этом становится ясно,

вкакой строке ошибка возникла.

3.Анализ значений переменных. Переменные играют важ-

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

1)отладочной печатью значений переменных на экран или

вфайл;

2)отслеживанием значений переменных при трассировке. Первый способ наиболее прост, так как сводится к добавлению

вызовов процедур write (writeln) в нужных местах программы. Если анализируется переменная, изменяемая внутри цикла (циклов), то имеет смысл выводить ее значение на экран не на каждом витке, а при определенном условии.

Второй способ не требует изменения исходного кода. Программа запускается на трассировку и приостанавливается в нужных местах. Во время очередной остановки с помощью диалога DebugEvaluate/modify можно выяснить значение любой переменной, попадающей в текущую область видимости. Для этого в поле Expression необходимо ввести имя переменной (элемента массива или записи) или выражение и нажать кнопку Evaluate. Значение будет вычислено и помещено в поле Result. При необходимости, значение переменной можно изменить, чтобы программа продолжила работу с другим значением. Для этого необходимо ввести новое значение в поле New value и нажать кнопку Modify.

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

168

Стиль

Соглашения по идентификаторам

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

Примеры неудачных идентификаторов:

var

UU: Boolean; Kk: string; Konec: Boolean;

Shirina, Vysota: Real;

procedure Zvuk(Chast, Dlit: Word); function EstFile(Im: String): Boolean;

Примеры удачных идентификаторов:

const

Eps = 0.0001; var

Sum : Integer;

Message: String;

Done: Boolean; Width, Height: Real;

procedure Beep(Hertz, MSec: Word); function ExistFile(FName: String): Boolean;

Часто названия идентификаторов берут из математики. При этом используют следующие ассоциативные связи:

alpha, beta, gamma – углы;

i, j, k – счетчики элементов множеств, перечисление; x, y, z, p, q, r, s, t – неизвестные;

p – вероятность; f, g, h – функции;

a, b, c, d – коэффициенты или массивы коэффициентов;

n, m – верхние границы диапазонов, размерности массивов, число элементов;

eps – точность;

delta – дельта, разность; dx, dy – приращение;

169

pi, e – известные константы.

Написание идентификаторов

Зарезервированные слова языка Turbo Pascal рекомендуется записывать строчными буквами.

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

var

NextX, LastX: Real; BeepOnError: Boolean;

Не рекомендуется разделять слова в идентификаторе символами подчеркивания.

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

Типы-записи и типы-классы начинать с буквы T (сокращение от type). Типы указателей начинать с буквы P (от pointer). Например:

type

TPhoneRec

pPhoneRec = ^tPhoneRec;

Соглашения по самодокументируемости программ

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

Рекомендуется комментарии к программе писать внутри символов { и }, а (* и *) использовать при отладке программы как «заглушки» участков программного кода.

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

170

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]