Шаг 43.
Определение функции

    На этом шаге мы разберем, как определяются функции.

    Напомним, что программа на C++ состоит из функций, среди которых обязательно должна присутствовать функция с именем main() (главная функция). Она обеспечивает задание точки входа в откомпилированную программу. Всем именам функций по умолчанию присваивается класс памяти extern, то есть каждая функция имеет внешний тип компоновки и статическую продолжительность существования. Как объект с классом памяти extern, каждая функция глобальна, то есть при определенных условиях доступна в модуле и даже во всех модулях программы. Для доступности в модуле функция должна быть в нем определена или описана до первого вызова. В заголовке функции последовательно указываются:

Таким образом, определение функции имеет следующий формат:
 <тип функции> <имя функции> (<спецификация формальных 
параметров>)
{
    <тело функции>;
}

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

     <тип параметра> <имя параметра> = <значение по умолчанию>,...

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

     return <выражение>;,

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

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

 <тип функции> <имя функции> (<спецификация формальных 
параметров>);    .

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

    Проиллюстрируем сказанное на конкретных примерах.


    Пример 1. Программа для введенного числа с плавающей точкой выводит ему противоположное. Реализация без прототипа.
#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().


    Пример 2. Программа для введенного числа с плавающей точкой выводит ему противоположное. Реализация с прототипом.
#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 включает в программу описания библиотечных классов и принадлежащих им функций для ввода и вывода данных.

    На следующем шаге мы остановимся более подробно на аргументах функции.


Предыдущий шаг Содержание Следующий шаг