Между формальными и фактическими параметрами при вызове

advertisement
§9. Функции
Мощность языка СИ во многом определяется легкостью и гибкостью в
определении и использовании функций в СИ-программах. В отличие от
других языков программирования высокого уровня в языке СИ нет деления
на процедуры, подпрограммы и функции, здесь вся программа строится
только из функций.
Функция - это совокупность объявлений и операторов, обычно
предназначенная для решения определенной задачи. Каждая функция должна
иметь имя, которое используется для ее объявления, определения и вызова. В
любой программе на СИ должна быть функция с именем main (главная
функция), именно с этой функции, в каком бы месте программы она не
находилась, начинается выполнение программы.
При вызове функции ей при помощи аргументов (формальных
параметров) могут быть переданы некоторые значения (фактические
параметры), используемые во время выполнения функции. Функция может
возвращать некоторое (одно !) значение. Оператором возврата из функции в
точку ее вызова является оператор return. Он имеет вид
return <выражение>;
Это возвращаемое значение и есть результат выполнения функции,
который при выполнении программы подставляется в точку вызова функции,
где бы этот вызов ни встретился. Допускается также использовать функции
не имеющие аргументов и функции не возвращающие никаких значений.
Действие таких функций может состоять, например, в изменении значений
некоторых переменных, выводе на печать некоторых текстов и т.п..
С использованием функций в языке СИ связаны три понятия определение функции
(описание действий,
выполняемых
функцией),
объявление функции (задание формы обращения к функции) и вызов
функции.
Определение функции задает тип возвращаемого значения, имя функции,
типы и число формальных параметров, а также объявления переменных и
операторы, называемые телом функции, и определяющие действие функции.
В определении функции также может быть задан класс памяти.
Пример 1:
int rus (unsigned char r)
{ if (r>='А' && c<=' ') return 1; else return 0; }
В данном примере определена функция с именем rus, имеющая один
параметр с именем r и типом unsigned char. Функция возвращает целое
значение, равное 1, если параметр функции является буквой русского
алфавита, или 0 в противном случае.
В языке СИ нет требования, чтобы определение функции обязательно
предшествовало ее вызову. Определения используемых функций могут
следовать за определением функции main, перед ним, или находится в
другом файле.
Однако для того, чтобы компилятор мог осуществить проверку
соответствия
типов
передаваемых
фактических
параметров
типам
формальных параметров до вызова функции нужно поместить объявление
(прототип) функции.
Объявление функции имеет такой же вид, что и определение функции, с
той лишь разницей, что тело функции отсутствует, и имена формальных
параметров тоже могут быть опущены. Для функции, определенной в
примере 1, прототип может иметь вид
int rus (unsigned char r); или rus (unsigned char);
В программах на языке СИ широко используются, так называемые,
библиотечные функции, т.е. функции предварительно разработанные и
записанные в библиотеки. Прототипы библиотечных функций находятся в
специальных заголовочных файлах, поставляемых вместе с библиотеками в
составе систем программирования, и включаются в программу с помощью
директивы #include.
Если объявление функции не задано, то по умолчанию строится прототип
функции на основе анализа первой ссылки на функцию, будь то вызов
функции или определение. Однако такой прототип не всегда согласуется с
последующим определением или вызовом функции. Рекомендуется всегда
задавать прототип функции. Это позволит компилятору либо выдавать
диагностические сообщения, при неправильном использовании функции,
либо
корректным
образом
регулировать
несоответствие
аргументов
устанавливаемое при выполнении программы.
При вызове функции указываются ее имя и фактические параметры
<имя_функции>(<список_фактических_праметров>)
Между формальными и фактическими параметрами при вызове функции
должны соблюдаться правила соответствия по последовательности и по
типам. Фактический параметр – это выражение того же типа, что и у
соответствующего ему формального параметра. Стандарт языка Си
допускает
автоматическое
преобразование
значений
фактических
параметров к типу формальных параметров. В Си++ такое преобразование не
предусмотрено. Поэтому в дальнейшем мы будем строго следовать принципу
соответствия типов.
В Си/Си++: передача параметров при вызове функции происходит
только по значению. Поэтому выполнение функции не может изменить
значения переменных, указанных в качестве фактических параметров.
Пример 2. Рассмотрим два варианта функции, меняющей местами значения
двух переменных
Вариант 1.
void swap (int a, int b)
{ int r=a;
a=b; b=r;
}
Обращение: int x=10, y=20;
Вариант 2
void swap (int *a, int *b)
{ int r=*a;
*a=*b; *b=r;
}
Обращение: swap (&x,&y);
swap (x,y);
В первом варианте после обращения к функции значения переменных x и y
не изменятся так как в функцию передаются значения этих переменных. Во
втором варианте в функцию передаются адреса областей памяти, отведенных
под эти переменные. Изменения происходят с содержимым этих областей. В
этом примере формальные параметры являются указателями. Указатель —
это переменная, значением которой является адрес области памяти.
В Си возможны функции с переменным числом параметров. Примером
таких функций являются библиотечные функции printf() и scanf().
Рекурсивные определения функций. В языках Си/Си++ допускается
рекурсивное
определение
функций.
Проиллюстрируем
определение
рекурсивной функции на классическом примере вычисления факториала
целого положительного числа.
long Factor(int n)
{ if (n<0) return 0;
if (n==0) return 1;
return n*Factor(n-1);
}
В случае если при вызове функции будет задан отрицательный аргумент, то
функция вернет нулевое значение – признак неверного обращения.
Классы памяти. Под всякую переменную, используемую в программе,
должно быть выделено место в памяти ЭВМ. Выделение памяти может
происходить либо на стадии компиляции (компоновки) программы, либо во
время ее выполнения. Существуют 4 класса памяти, выделяемой под
переменные:
- автоматическая (ключевое слово auto),
- внешняя (extern),
- статическая (static),
- регистровая (register).
Под глобальные переменные выделяется место во внешней памяти (не
нужно думать, что речь идет о магнитной памяти; это оперативная память
класса extern). Глобальную переменную можно объявить либо вне
программных блоков, либо внутри блока с ключевым словом exter. Обычно
это делается в тех случаях, когда программный модуль хранится в отдельном
файле и, следовательно, отдельно компилируется.
Пусть, например, основная и вспомогательная функции хранятся в
разных файлах:
Пример 3.
Файл 1:
…….
int var
void main()
{var=5;
func();
cout<<var;
}
Файл 2:
void func()
{ extern int var;
var=10*var;
}
Здесь обмен значениями между основной и вспомогательной функцией
func() происходит через общую глобальную переменную var, для которой во
время компиляции выделяется место во внешнем разделе памяти. В
результате выполнения данной программы на экран выведется число 50.
Локальные переменные, объявленные внутри блоков, распределяются в
автоматической памяти, работающей по принципу стека. Выделение
памяти происходит при входе выполнения программы в блок, а при выходе
из блока память освобождается. Ключевое слово auto писать не обязательно
(подразумевается по умолчанию).
Статическая память выделяется под
переменные, локализованные
внутри блока, но, в отличие от автоматической памяти, не освобождается при
выходе из блока. Таким образом, при повторном вхождении в блок
статическая
переменная
сохраняет
свое
прежнее
значение.
Пример
объявления статической переменной:
f()
{static int schet=10; …}
Инициализация статической переменной происходит только при первом
вхождении в блок. Если инициализация явно не указана, то переменной
автоматически присваивается нулевое начальное значение. Статические
переменные можно использовать, например, для организации счетчика числа
вхождений в блок.
Регистровая память выделяется под локальные переменные. Регистры
процессора – самый быстрый и самый маленький вид памяти. Они
задействованы при выполнении практически всех операций в программе.
Поэтому возможность распоряжаться регистровой памятью лучше оставить
за компилятором.
Наконец, рассмотрим пример, в котором используются различные
способы описания переменных.
Пример 4.
int var
//описана внешняя переменная var
main()
{extern int var; // та же внешняя переменная var
…
}
func1()
{extern int var1;//новая внешняя переменная var1
…
//внешняя var здесь также видна
}
func2()
{…
//здесь переменная var видна, а переменная
}
// var1 – не видна
int var1; //Глобально описана переменная var1
func3()
{ int var; //здесь var – локальная переменная,
…
//видна внешняя переменная var1
}
func4()
{auto int var1; //здесь var1 – локальная переменная,
…
//видна внешняя глобальная var
Упражнения
1. Определить результат выполнения программы:
#include <iostream.h>
void mul(int, int);
int S;
void main()
{int a=2, b=3;
mul(a,b); a=2*S; mul(a,b);
cout<<S;
}
void mul(int x, int y)
{S=x*y;}
2. Составить функцию, определяющую, является ли ее целый аргумент
простым числом. Использовать эту функцию для подсчета количества
простых чисел в последовательности из десяти целых чисел, вводимых с
клавиатуры.
3. Даны натуральные числа n и m; найти НОД(n,m). Составить
рекурсивную функцию вычисления НОД, основанную на соотношении
НОД(n, m) = НОД(m, r), где r –– остаток от деления n на m (n>m).
Related documents
Download