Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
11
PAGE 3
Министерство образования и науки РФ
Федеральное бюджетное государственное
образовательное учреждение
высшего профессионального образования
«Тульский государственный университет»
Политехнический институт
Кафедра "Автоматизированные станочные системы"
Троицкий Д.И.
доцент, к.т.н.
МЕТОДИЧЕСКИЕ УКАЗАНИЯ
ПО ВЫПОЛНЕНИЮ ЛАБОРАТОРНОЙ РАБОТЫ №7
по дисциплине
ИНФОРМАТИКА
Направление подготовки:
230100 Информатика и вычислительная техника
Профиль подготовки:
Системы автоматизированного проектирования
Форма обучения очная, очно-заочная, заочная
Тула 2011 г.
Рассмотрено на заседании кафедры "Автоматизированные станочные системы"
протокол №1 от "31" августа 2011 г.
Зав. кафедрой________________А.Н. Иноземцев
Содержание
[1] [1.1] Создание динамических массивов [1.2] Многомерные динамические массивы [2] Процедуры и функции [3] Что такое функция? [4] Функция: туда и обратно [5] Процедуры [6] Передача данных в процедуры [7] Вложенные процедуры и функции |
Если у начальника довольное лицо,
значит, вы сделали что-то не так!
Из законов Мэрфи
Очень часто при обработке массивов данных заранее неизвестно, сколько элементов они будут содержать. Например, надо ввести с клавиатуры n значений и затем вывести их, упорядочив по возрастанию. Очевидно, такая задача требует хранения всех значений в памяти. Однако статические массивы в Delphi имеют фиксированный размер, который должен быть определен до начала компиляции. Статический размер обычных массивов связан с тем, что ячейки памяти для элементов массива при компиляции программы физически оказываются в выходном exe-файле, размер которого, разумеется, постоянен.
Выходом из положения является использование динамической памяти, расположенной за пределами exe-файла. Тогда в программе достаточно зарезервировать одну обычную статическую переменную для хранения адреса первого элемента такого массива, а сам он будет иметь произвольный и переменный размер. Динамические массивы позволяют на ходу менять их размер, добавлять и удалять элементы.
В Delphi предусмотрено использование как традиционных статических, так и динамических массивов. Давайте создадим массив для хранения произвольного количества целых чисел. Его описание будет выглядеть следующим образом:
TYPE TA=ARRAY OF INTEGER;
VAR a:TA;
Для динамических массивов размерность вообще не указывается.
Первоначальное задание или изменение размеров динамического массива выполняется функцией SetLength(массив, число_элементов). Функция Length(массив) (та же, что работает с текстовыми строками) возвращает текущее количество элементов в массиве, функции Low(массив) и High(массив) нижнее и верхнее значения индекса массивов соответственно. Нумерация индексов всегда начинается с нуля. Процедура Finalize(массив) удаляет массив целиком из памяти и делает его длину нулевой.
Пусть нам надо прочесть из файла неизвестное заранее количество текстовых строк и занести их в массив. Делается это так:
TYPE TA=ARRAY OF STRING;
VAR a:TA; f:TEXTFILE; i:byte;
BEGIN
ASSIGNFILE(f,'1.TXT');
RESET(f);
// цикл до конца файла
WHILE NOT(EOF(f)) DO
BEGIN
// увеличиваем длину массива на единицу
SetLength(a,Length(a)+1);
// считываем строку в последний элемент массива
READLN(f,a[Length(a)-1])
END;
CLOSEFILE(f)
…
Обратите внимание на строку READLN(f,a[Length(a)-1]). Функция Length(a) возвращает число элементов в массиве. Оно на единицу больше индекса последнего элемента. Так, при длине массива в три элемента индексы имеют вид 0,1,2. Поэтому необходимо вычитать единицу для получения индекса последнего элемента.
Теперь выведем содержимое динамического массива в компонент Memo1:
Memo1.Lines.Clear;
FOR i:=Low(a) TO High(a) DO
Memo1.Lines.Add(a[i]);
Создадим динамический массив в виде матрицы 53 вещественных числа:
TYPE TA=ARRAY OF ARRAY OF REAL;
VAR a:TA;
…
SetLength(a,5,3);
a[0,0]:=10.0;
…
Многомерный динамический массив описывается аналогично статическому с той лишь разницей, что индексы по размерностям (число строк и столбцов) в операторе TYPE не указываются. Соответственно в процедуре SetLength задаются два размера: число строк и столбцов.
Сложные системы имеют тенденцию противопоставлять себя своим же функциям.
Из законов Мэрфи
При написании сколько-нибудь сложных программ они в обязательном порядке разделяются на отдельные "кирпичики" процедуры и функции. Процедура или функция это маленькая программа со своими переменными, в которую можно передавать информацию и из которой можно получать результаты. Чем больше таких кирпичиков, тем легче создавать и отлаживать программу. Рекомендуется, чтобы каждая процедура целиком умещалась на экране, т.е. не превышала 20..25 строк текста.
Разделение программы на кусочки позволяет, во-первых, избежать ненужного дублирования.
ЕСЛИ В ТЕКСТЕ ПРОГРАММЫ ЧТО-ТО ПОВТОРЯЕТСЯ
ВЫ ЧТО-ТО НЕПРАВИЛЬНО ДЕЛАЕТЕ!
Все повторяющиеся фрагменты должны выносится "за скобки" в отдельные процедуры. Во-вторых, при проектировании программы методом "сверху вниз" сложная задача разделяется на более мелкие простые, которые и оформляются в виде процедур. Наконец, написание отдельных процедур можно поручить группе программистов, что резко ускорит разработку большого проекта.
Ошибка? Это не ошибка, это системная функция!
Из законов Мэрфи
Сначала рассмотрим функции, поскольку с ними вам уже приходилось сталкиваться. В Delphi предусмотрено много встроенных функций, таких, как SQR(), SQRT(), SIN(), COS() и др. Давайте создадим пользовательскую функцию, которая будет вычислять тангенс угла. Мы хотим, чтобы в любом месте программы в формуле можно было написать TAN(x), а не делить синус на косинус.
Запись TAN(x) является вызовом функции с именем TAN. Сама же функция должна быть где-то описана. По правилам Delphi все объекты в программе описываются до их первого использования. Поэтому глобальные (доступные во всей программе) процедуры и функции описываются в разделе реализации после оператора IMPLEMENTATION.
Функцию можно представить себе как "черный ящик", в который мы что-то кладем и из которого мы что-то достаем. Любая функция имеет вход и выход. У функции тангенса на входе значение угла в радианах, а на выходе значение тангенса этого угла. Входных параметров может быть несколько (например, функция вычисления длины гипотенузы по двум длинам катетов) или вообще не быть (функция, возвращающая текущее время). Возвращаемое значение у функции есть всегда.
Общий вид описания функции следующий:
FUNCTION имя_функции(арг1:тип; … аргn : тип): тип_возвр_знач;
раздел локальных описаний
BEGIN
…
Result := значение
END;
Арг1 … аргn это переменные, в которые заносятся значения, подаваемые функции на вход. Как и для любой другой переменной, после них через двоеточие указывается тип данных. кроме того, нужно указать тип значения, возвращаемого функцией.
Две интересные передачи всегда идут в одно и то же время.
Из законов Мэрфи.
Итак, в функцию при ее вызове должны попасть какие-то значения. Например, в основной программе написано:
a:=StrToFloat(Edit2.Text);
b := Tan(a);
Чтобы значение переменой a оказалось в функции, ее заголовок должен выглядеть следующим образом:
FUNCTION Tan(x: REAL):REAL;
Такое описание говорит, что передаваемое в функцию значение должно быть типа REAL и оно заносится в переменную x внутри функции. Второе слово REAL относится к типу результата, возвращаемого функцией Tan. При вызове значение переменной a будет скопировано в переменную х.
Все переменные, описанные внутри функции или процедуры, являются ЛОКАЛЬНЫМИ. Это значит, что они никак не связаны с одноименными переменными, описанными в других частях программы. Например:
IMPLEMENTATION
VAR a: REAL; { глобальная переменная а }
FUNCTION abc(a:REAL):REAL; { локальная переменная а }
BEGIN
…
END;
В примере в начале всей программы выделяется память под ГЛОБАЛЬНУЮ переменную с именем a. Глобальные переменные можно использовать в любом месте в тексте программы. Они постоянно занимают память. Большое количество глобальных переменных признак низкой квалификации программиста.
Переменная a, описанная в заголовке функции abc, существует только внутри этой функции. Она никак не связана с глобальной переменной a, это две разные ячейки памяти. Внутри функции глобальная переменная a будет не видна, возможен доступ только к локальной переменной a.
В момент вызова функции Tan значение, записанное в скобках после ее имени, будет занесено в локальную переменную a. Например:
x := Tan(2*b+pi);
Сначала вычисляется выражение 2*b+pi, а затем начинает выполняться функция и полученное при вычислении значение заносится в ее локальную переменную а.
В процедурах и функциях могут быть и обычные локальные переменные, типы данных и константы:
FUNCTION abc(a:REAL):REAL;
TYPE TA=ARRAY[1..10] OF REAL;
VAR x:REAL;
BEGIN
…
END;
В приведенном примере в переменную a заносится значение при вызове функции, а переменная x является просто ячейкой памяти. Она никак не связана с другими переменными с именем x, описанными в других местах программы. Аналогично тип данных TA в примере действует (обычно говорят "виден") только до конца текста функции, т.е. до слова END.
После того, как в функции вычислено возвращаемое значение, самое главное не забыть на самом деле вернуть его. Для возврата значения внутри функции всегда существует переменная с именем Result, а также с именем, совпадающем с именем самой функции. Например, внутри функции Tan живет переменная с именем Tan и типом REAL и переменная Result тоже типа Real (напомним, что тип возвращаемого значения указывается после заголовка функции). Этой-то переменной и нужно присвоить результат вычислений:
FUNCTION Tan(a:REAL):REAL;
BEGIN
Result:=SIN(a)/COS(a)
END;
Очень часто встречается следующая ошибка:
FUNCTION Tan(a:REAL):REAL;
VAR t:REAL;
BEGIN
t:=SIN(a)/COS(a)
END;
Такая функция возвращает неопределенное значение. И в самом деле, результат вычисления тангенса записан в переменную t. Но это локальная переменная, при выходе из функции она удаляется из памяти вместе с вычисленным значением. Соответственно и вся функция становится бесполезной. Нужно помнить простое правило:
ВНУТРИ ФУНКЦИИ ОБЯЗАТЕЛЬНО ДОЛЖНО
ПРИСВАИВАТЬСЯ ЗНАЧЕНИЕ ПЕРЕМЕННОЙ Result
Если уж вы открыли банку с червями, то единственный способ
снова их запечатать - это воспользоваться банкой большего размера.
Из законов Мэрфи.
Процедура похожа на функцию, но обычно не возвращает значения (хотя и это возможно). Процедуру можно представить себе как полноценную маленькую программу со своими константами, переменными, типами данных и пр. Описание процедуры очень похоже на описание функции:
PROCEDURE имя_процедуры (арг1:тип; … аргn : тип);
раздел локальных описаний
BEGIN
операторы
…
END;
Глобальные процедуры тоже описываются после оператора IMPLEMENTATION. Как и в случае функции, в процедуру могут передаваться данные. Вызов процедуры состоит просто из написания ее названия. Например, создадим процедуру, завершающую работу всей программы и назовем ее Stop:
PROCEDURE Stop;
BEGIN
Form1.Close
END;
Вот как выглядит ее вызов:
BEGIN
. . .
Stop;
. . .
END;
Фактически в язык Delphi мы добавили новый оператор и назвали его Stop.
Две интересные передачи всегда идут в одно и то же время.
Из законов Мэрфи.
Если нам нужно просто передать данные в процедуру, ничего из нее не возвращая, это делает точно так же, как и при передаче данных в функцию. К примеру, создадим процедуру, которая получает на вход два числа и выводит на экран их разность, если она положительна:
PROCEDURE ShowMax(a, b : REAL);
VAR r:REAL;
BEGIN
r:=a-b;
IF r>0 THEN
Form1.Label1.Caption:= FloatToStr(r)
END;
Поскольку процедура ShowMax ничего не знает про форму Form1 и компонент Label1, придется указывать полный путь к нему: Form1.Label1. Обработчики событий компонентов относятся к форме (являются ее методами) и внутри них Form1 можно не писать.
Здесь r локальная переменная процедуры, она существует только внутри нее. При вызове такой процедуры ей на вход надо подать два числа, например:
ShowMax(10, 2*a-45);
Обратите внимание, что здесь a совсем другая переменная, не имеющая отношения к переменной a в заголовке процедуры. Значение 10 будет занесено в локальную переменную a, а вычисленное значение 2*a-45 в локальную переменную b.
Такой способ передачи данных называется "по значению" (by value). У него есть два недостатка. Во-первых, он позволяет передавать информацию только в одну сторону из вызывающей программы в процедуру. Во-вторых, при передаче больших объемов данных, скажем, больших массивов, они каждый раз полностью копируются из одной области памяти в другую, на что уходит много ресурсов компьютера.
Для решения вопроса с возвратом значения из процедуры в Delphi предусмотрен второй механизм передачи данных, называемый "по ссылке" (by reference). Его смысл состоит в том, что в процедуру передается адрес переменной, которая описана в вызывающей программе. Зная адрес, процедура может напрямую занести в эту переменную требуемое значение.
Составим процедуру, вычисляющую гипотенузу треугольника по двум катетам.
PROCEDURE Hypot (a,b:REAL; VAR c:REAL);
VAR h:REAL;
BEGIN
h:= SQR(a)+SQR(b);
c:=SQRT(h)
END;
Весь фокус заключается в слове VAR, стоящем в заголовке перед именем переменной-аргумента c. Слово VAR в заголовке процедуры не имеет никакого отношения к выделению памяти1. Оно указывает на то, что указанную после него переменную можно изменять внутри процедуры и измененное значение вернется в вызывающую программу.
При обращении к процедуре Hypot третьим ее параметром обязательно должно идти имя переменной. Следующая запись правильна:
VAR h:REAL;
. . .
BEGIN
. . .
Hypot(a+b, 45, h)
…
Величина гипотенузы будет записана в переменную h.
А так делать нельзя:
Hypot(a+b, 45, h*4)
Выражение h*4 не является именем переменной и в него нельзя записать значение.
В качестве параметров процедур и функций могут быть и массивы. Рассмотрим пример программы, в которой нахождение минимального элемента массива оформлено в виде процедуры.
CONST Nmax=20;
TYPE TA=ARRAY[1..Nmax] OF REAL;
VAR a:TA; i:BYTE; min:REAL;
PROCEDURE FindMin(ar:TA; n:BYTE; VAR min:REAL);
VAR i:BYTE;
BEGIN
min:=ar[1];
FOR i:=2 TO n DO
IF ar[i]<min THEN
min := ar[i]
END;
BEGIN
FOR i:=1 TO Nmax DO
a[i]:=RANDOM;
FindMin(a,Nmax,min);
Label1.Caption:=FloatToStrF(min,ffFixed,10,4)
END.
В процедуру или функцию нужно передать и динамический массив. Напишем универсальную функцию поиска максимального элемента в массиве вещественных чисел любого размера:
FUNCTION FindMax(a:ARRAY OF REAL):REAL;
VAR i:WORD;
BEGIN
Result:=a[Low(a)];
FOR i:=Low(a)+1 TO High(a) DO
IF a[i]>Result THEN
Result:=a[i]
END;
На блок-схемах вызов процедуры или функции обозначается в виде . На каждую процедуру изображается отдельная блок-схема.
Если пятно внутри, то до него невозможно добраться.
Правило мытья стеклянной посуды.
Внутри процедур и функций могут находиться другие процедуры и функции. Они будут являться локальными по отношению к "материнской" процедуре и их можно будет вызывать только из нее самой. Например:
PROCEDURE abc;
FUNCTION cde:STRING;
BEGIN
…
END;
BEGIN
…
END;
Здесь cde локальная функция внутри процедуры abc.
1 Подобное "двойное назначение" слова VAR в Delphi представляется неудачным и вносит ненужную путаницу.
функцию cde можно вызывать
только здесь!
возможность изменения
параметра c внутри процедуры
выделение памяти под локальную переменную
переменные a, b, r существуют
только здесь!