Функция — это, так же как и процедура, именованная последовательность действий. Но, в отличие от процедуры, результатом обращения к функции во время выполнения (вызова функции) является одно значение определенного типа. Поэтому обращение к функции может определять операнд в выражении. Обращение же к процедуре образует, как мы видели, отдельный оператор.
При вычислении выражения исполнитель выполняет в определенной последовательности операции над значениями, заданными операндами. Последовательность выполнения операций определяется порядком их следования в выражении, их приоритетом (старшинством) и явными указаниями в виде пар круглых скобок. Если требуемый порядок не задан посредством скобок, операции более высокого приоритета выполняются перед выполнением операции более низкого приоритета, операции же одинакового приоритета выполняются слева направо. Эти правила применяются последовательно к каждому подвыражению, входящему в состав вычисляемого выражения. Так, при вычислении выражения в приведенном примере операции будут выполняться в следующем порядке:
3 1 2 5 4
x1*(7*x2+4)-abs(x1+28)
Сведения о всех операциях языка и их приоритетах приведены в гл. 2.
Теперь остановимся на том, как операнды выражения определяют значения, используемые при вычислениях. Если операнд — константа, то значение операнда есть неизменное значение, заданное этой константой. Если операнд — переменная, то значение операнда есть текущее (на момент, когда оно требуется исполнителю для выполнения операции) значение, хранящееся в переменной. Если операнд — вызов функции, то исполнитель обращается к функции, передавая ей в качестве аргументов значения, указанные в круглых скобках за именем функции, затем полученное от функции (возвращенное функцией) значение использует в качестве операнда. Каждая функция требует определенного количества аргументов (одного, нескольких, ни одного) определенного типа и возвращает значение также определенного типа. Пример функции, не имеющей аргументов — библиотечная функция Time, возвращающая текущее (на момент обращения к функции) время дня:
Листинг 1.2. Показ текущего времени — Вариант 1
program Project2; // Отображение текущего времени дня
{$APPTYPE CONSOLE}
uses
SysUtils;
var
currentTime: TDateTime;
timeStr: string;
begin
currentTime:=Time;
timeStr:=TimeToStr(currentTime);
Writeln(timeStr);
Readln;
end.
В этой программе первый же оператор заносит в переменную currentTime значение текущего времени, возвращенное обращением к функции Time. Это значение обращением к функции TimeToStr преобразуется в текстовый формат и печатается обращением к процедуре Writeln.
Каждый аргумент в обращении к процедуре или функции задается в общем случае выражением, которое, в свою очередь, "имеет право" содержать обращения к функциям. Учтя это, мы можем написать гораздо более простой вариант программы:
Листинг 1.3. Показ текущего времени — Вариант 2
program Project3; // Отображение текущего времени дня
{$APPTYPE CONSOLE}
uses
SysUtils;
begin
Writeln(TimeToStr(Time));
Readln;
end.
После подробного рассмотрения обращений к функциям продолжим обсуждение вычислений исполнителем выражений.
Поскольку, как мы выяснили:
операции над операндами выражения выполняются в строго определенном порядке;
значение каждого операнда имеет определенный тип;
результат выполнения каждой операции над операндами определенного типа также имеет определенный тип, полученное в результате вычисления выражения значение имеет определенный тип, который называют типом данного выражения. Поэтому каждое выражение имеет определенный тип.
Заметим, что в частном случае выражение может состоять и из одного операнда какого-либо вида (константы, переменной, вызова функции) и не содержать знаков операций:
Листинг 1.4. Примеры выражений, состоящих из одного операнда
...
x1:=2;
...
x2:=x1;
...
currentTime:=Time;
...
Указание имени переменной играет разные роли в зависимости от того, в какой части и какой конструкции языка оно расположено. Так, если имя переменной встречается в выражении, являющемся правой частью оператора присваивания, то это имя обозначает текущее значение этой переменной. Если имя переменной указано в правой части оператора присваивания, то оно обозначает адрес места памяти, в которое исполнитель должен занести новое значение, полученное в результате вычисления выражения. В этом случае говорят, что указана ссылка на переменную. Например, в результате выполнения последовательности операторов
x1:=4;
x1:=x1+1;
целая переменная x1 будет иметь значение 5.
Точно так же указание переменной в качестве аргумента (параметра) при обращении к какой-либо процедуре может иметь разный смысл. Например, при выполнении оператора Readln(x1) листинга 1.1 исполнителю необходимо знать, куда поместить введенное пользователем новое значение. Следовательно, указание переменной x1 в этом случае играет роль ссылки на эту переменную. Напротив, имя переменной sum в качестве второго параметра при вызове процедуры оператором
Writeln('Sum = ',sum)
той же программы обозначает текущее значение этой переменной, которое процедура Writeln должна напечатать вслед за константной строкой, заданной первым параметром. В этом случае переменная sum — частный случай выражения, задающего значение, которое должно быть напечатано.
Необходимо ясно понимать, что в тех случаях, когда мы указываем переменную в качестве операнда в выражении, ее значение должно быть определено к моменту, когда будет вычисляться данное выражение. То есть, в ходе выполнения программы до вычисления выражения в эту переменную должно быть занесено значение или оператором присваивания или соответствующим обращением к процедуре, например, Readln. Если этого не сделать, при вычислении выражения исполнитель в качестве значения переменной использует ту случайную (с точки зрения работы программы) комбинацию битов, которая осталась в области памяти, выделенной для переменной, от предыдущего использования этого участка памяти в каких-либо других целях. В подобных случаях говорят об использовании не присвоенного значения переменной. Подобные ошибки в программе зачастую довольно трудно обнаружить.
Совет
Многие компиляторы предусматривают заполнение выделяемых для переменных участков памяти нулевыми значениями. Если даже эти значения совпадают с теми начальными значениями, которые должны быть присвоены этим переменным в соответствии с логикой программы, не полагайтесь на компилятор и явно включайте необходимые присваивания в свою программу. Это поможет вам выработать весьма полезную привычку внимательно следить за начальными присваиваниями значений переменным и убережет от ошибки в том случае, когда придется переносить вашу программу в другую систему программирования, компилятор которой может и не "очищать" память переменных.
Вот список всех допустимых операций в Object Pascal: