Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЗФ_ОАиП / Лекции ГГУ Скорины - Программирование.doc
Скачиваний:
179
Добавлен:
21.03.2016
Размер:
2.27 Mб
Скачать

18.5. Объявление и определение функции: прототип функции

Определения (definition) функции следует отличать от ее объявления (declaration).

Определение функции – это ее полное описание, включающее тип возвращаемого значения, количество и типы самих параметров, и ее тело – тот код, который стоит внутри фигурных скобок после имени функции. Когда мы писали (создавали, а не вызывали) функции, мы выполняли именно определения функций.

Что же такое объявление функции, и зачем оно понадобилось? Допустим, что есть такой код:

void main() {   int i = 10;   f(i);   ... }

Что по ним можно сказать о функции f()? На первый взгляд, это функция, которой нужен один аргумент типа int. С возвращаемым значением ясности нет – либо void, либо любой другой тип, но значение в коде игнорируется.

А теперь посмотрим на определение этой функции:

void f(float x) {   ... }

Тип параметра, оказывается, не int, а float. И размеры у них разные – int занимает 2 байта, а float – 4.

Обрабатывая код с вызовом функции, транслятор решит, что ей надо передать 2 байта со значением int. И тот же транслятор, обрабатывая код с определением функции, создаст код, который использует для параметра 4 байта – размер переменной float. В результате вызванная функция получит 2 байта от целого, и еще 2 байта «мусора», и попытается с этими 4-мя байтами работать, как с нормальным числом типа float.

Именно для того чтобы избегать подобных неприятностей, в языке С помимо определений сушествуют еще и объявления (и это относится не только к функциям).

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

Чтобы избежать недоразумений при вызове функции f(), надо добавить всего лишь одну строку с ее объявлением перед функцией main():

void f(float x); // void f(float); - можно и так

void main() {   ...

}

Такое объявление не содержит тела функции – сразу за списком параметров стоит точка с запятой. Но транслятору этого вполне достаточно – он теперь будет знать, что где-то (возможно, даже в другом файле или в библиотеке) есть такая функция, и он теперь сможет или преобразовать фактический параметр к правильному типу перед вызовом, или хотя бы (в сложных случаях) сообщить о том, что переменная, которая используется при вызове функции, не подходит для вызова.

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

Вот такое объявление функции и называется прототипом функции.

В общем виде прототип функции выглядит так:

тип имя_функции(тип [имя_пар1], ..., тип [имя_парN]);

Использование имен параметров не обязательно.

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

void f(float x) {   ... }

void main() {   int i = 1;   f(i);   ... }

Транслятор, уже зная о функции f(), поймет, что надо сначала привести значение i к типу float, а уж потом передавать его в функцию. Т.е. в этом примере специальный прототип не требуется, так как функция f() определена еще до того, как она начинает использоваться в main().

Единственная функция, для которой не требуется прототип, – это main(), так как это первая функция, вызываемая в начале работы программы.

Поскольку прототипы содержат «неполную» информацию – у функций нет тела, под переменные не резервируется память, и так далее – их можно включать хоть в каждый файл программы, на ее размере и скорости работы это не скажется (лишь бы объявления в разных файлах соответствовали друг другу). И чтобы не писать, рискуя ошибиться, одно и то же многократно, такие объявления обычно собирают в заголовочный файл, а потом этот файл при необходимости включают в нужные cpp-файлы с помощью директивы #include. Этим мы уже неоднократно пользовались. Например, файл <stdio.h>, который мы включали в наши программы, среди прочего содержит прототипы функций printf() и scanf(), в то время как сами эти функции находятся совсем в другом месте – в библиотеке.