На этом шаге мы разберем, как определяются функции.
Напомним, что программа на C++ состоит из функций, среди которых обязательно должна присутствовать функция с именем main() (главная функция). Она обеспечивает задание точки входа в откомпилированную программу. Всем именам функций по умолчанию присваивается класс памяти extern, то есть каждая функция имеет внешний тип компоновки и статическую продолжительность существования. Как объект с классом памяти extern, каждая функция глобальна, то есть при определенных условиях доступна в модуле и даже во всех модулях программы. Для доступности в модуле функция должна быть в нем определена или описана до первого вызова. В заголовке функции последовательно указываются:
<тип функции> <имя функции> (<спецификация формальных параметров>) { <тело функции>; }
Здесь тип функции - тип возвращаемого функцией значения, в том числе void, если функция никакого значения не возвращает. Имя функции - это идентификатор. Имена функций как имена внешние должны быть уникальными среди других имен из модулей, в которых используются функции. Спецификация формальных параметров - это либо пусто, либо void, либо список спецификаций отдельных параметров, в конце которого может быть поставлено многоточие. Спецификация каждого параметра в определении функции имеет вид:
<тип параметра> <имя параметра> = <значение по умолчанию>,...
Тело функции - это всегда блок или составной оператор, то есть последовательность описаний и операторов, заключенная в фигурные скобки. Возврат из функции осуществляется с помощью оператора
return <выражение>;,
где выражение определяет возвращаемое функцией значение. Если функция не возвращает никакого значения, то есть имеет тип void, то выражение в операторе return опускается. В этом случае необязателен и оператор return в теле функции.
Строгое согласование по типам между формальными и фактическими параметрами требует, чтобы в модуле до первого обращения к функции было помещено либо ее определение, либо ее описание (прототип), содержащее сведения о ее типе (о типе результата, то есть возвращаемого значения) и о типах всех параметров. Именно наличие такого прототипа либо полного определения позволяет компилятору выполнять контроль соответствия типов параметров. Прототип (описание) функции может внешне почти полностью совпадать с заголовком ее определения:
<тип функции> <имя функции> (<спецификация формальных параметров>); .
Основное различие - точка с запятой в конце описания (прототипа). Второе отличие - необязательность имен формальных параметров в прототипе даже тогда, когда они есть в заголовке определения функции.
Проиллюстрируем сказанное на конкретных примерах.
#include <iostream.h> //Функция расположена до функции main(). float protiv (float a) { return -a; } void main() { float x; do { cout << "\nЗадайте число (0 - выход из программы): "; cin >> x; cout << "Противоположным этому числу является: " << protiv(x); } while (x!=0); }
Для данного случая функция protiv() расположена в тексте программы до функции main(). Поэтому функция main() "знает", что есть такая функция protiv(), "знает", сколько у нее параметров и значение какого типа эта функция возвращает. Если же эта функция была бы расположена за функцией main(), то нужно было бы использовать прототип, который бы "подсказал" функции main() характеристики функции protiv().
#include <iostream.h> void main() { float x; float protiv (float); //Прототип функции. do { cout << "\nЗадайте число (0 - выход из программы): "; cin >> x; cout << "Противоположным этому числу является: " << protiv(x); } while (x!=0); } //Функция расположена после функции main(). float protiv (float a) { return -a; }
Обратите внимание, что при описании прототипа в функции main() можно не указывать имя переменной (формального параметра), а только ее тип (в данном случае float). Можно ограничиться типом потому, что прототип только поясняет, что есть такая функция с такими характеристиками.
Как видно из приведенных примеров, обращение к функции (вызов функции) осуществляется с использованием следующей конструкции:
<имя функции> (<список фактических параметров>); .
При наличии прототипов вызываемые функции не обязаны размещаться в одном файле (модуле) с вызывающей функцией, они могут оформляться в виде отдельных модулей либо могут находиться уже в оттранслированном виде в библиотеке объектных модулей. Сказанное относится не только к функциям, которые готовит программист для включения в свою программу, но и к функциям из стандартных библиотек используемого компилятора. В последнем случае определения библиотечных функций, уже оттранслированные и оформленные в виде объектных модулей, находятся в библиотеке компилятора, а описания функций в виде прототипов необходимо включать в программу дополнительно. Обычно это делают с помощью препроцессорной команды:
#include <имя файла> .
Здесь имя файла определяет текстовый (заголовочный) файл, содержащий прототипы той или иной группы стандартных для данного компилятора функций. Например, в текстах всех приведенных программ присутствует команда:
#include <iostream.h>
которая из файла с именем iostream.h включает в программу описания
библиотечных классов
и принадлежащих им функций для ввода и вывода данных.
На следующем шаге мы остановимся более подробно на аргументах функции.