Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
231112.docx
Скачиваний:
16
Добавлен:
01.03.2025
Размер:
2 Мб
Скачать
☆
  1. Синтаксис прототипа

Прототип функции является оператором, поэтому он должен завершаться точкой с запятой. Простейший способ получить прототип — скопировать заголовок функции из ее определения и добавить точку с запятой. Это, собственно, и делает программа из листинга 7.2 с функцией cube ():

double cube(double x) ;

// добавление; к заголовку для получения прототипа

Однако прототип функции не требует предоставления имен переменных-параметров; достаточно списка типов. Программа из листинга 7.2 строит прототип cheers (), используя только тип аргумента:

void cheers(int); //в прототипе можно опустить имена параметров

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

  1. Что обеспечивают прототипы

Вы увидели, что прототипы помогают компиляторам. Но чем они полезны программистам? Прототипы значительно снижают вероятность допущения ошибок в программе. В частности, они обеспечивают следующие моменты:

  • Компилятор корректно обрабатывает возвращаемое значение.

  • Компилятор проверяет, указано ли правильное количество аргументов.

  • Компилятор проверяет правильность типов аргументов. Если тип не подходит, компилятор преобразует его в правильный, когда это возможно.

Мы уже говорили о том, как корректно обработать возвращаемое значение. Теперь посмотрим, что случится, если вы зададите неверное количество аргументов.

Например, предположим, что в программе выполнен следующий вызов:

double z = cube();

Компилятор, в котором не используется прототипирование функций, пропустит это. Когда функция будет вызвана, он обнаружит, что cube () должна принимать число, и подставит любое подвернувшееся значение. Именно так работал С до того, как в ANSI С было позаимствовано прототипирование из C++. Поскольку прототипирование в ANSI С не обязательно, подобным образом некоторые программы С ведут себя и до сих пор. Но в C++ прототипирование обязательно, поэтому вы застрахованы от ошибок подобного рода.

Далее предположим, что вы предоставили аргумент, но неверного типа. В С это может приводить к возникновению странных ошибок. Например, если функция ожидает тип int (предположим, что он имеет размер 16 бит), а вы передали double (предположим, 64 бита), то функция видит только первые 16 бит из 64 и пытается интерпретировать их как значение типа int. Однако C++ автоматически преобразует переданное значение к типу, указанному в прототипе, предполагая, что оба типа арифметические. Например, в листинге 7.2 присутствуют два несоответствия типа в одном операторе:

cheers(cube(2));

Программа передает целое значение 2 функции cube () , которая ожидает тип double. Компилятор, замечая, что прототип cube () указывает тип аргумента double, преобразует 2 в 2.0, т.е. в значение типа double. Затем cube () возвращает значение 8.0 типа double, которое должно быть использовано в качестве аргумента cheers (). Опять-таки, компилятор проверяет прототип и замечает, что cheers () требует аргумента int. Он преобразует возвращенное значение в целочисленное 8. В общем случае прототипирование позволяет выполнять автоматическое приведение к ожидаемым типам.

Автоматическое преобразование типов не позволяет исключить все возможные ошибки. Например, если вы передаете значение 8.33Е27 в функцию, ожидающую аргумента int, то такое большое значение не может быть корректно преобразовано в обычный тип int. Некоторые компиляторы предупреждают о возможных потерях данных при автоматическом преобразовании больших типов в малые.

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

Прототипирование происходит во время компиляции и называется статическим контролем типов. Статический контроль типов, как вы уже видели, обнаруживает многие ошибки, которые было бы трудно перехватить во время выполнения.

Аргументы функций и передача по значению

Наступило время внимательнее взглянуть на аргументы функций. В C++ они обычно передаются по значению. Это означает, что числовое значение аргумента передается в функцию, где присваивается новой переменной.

Например, в листинге 7.2 присутствует следующий вызов функции:

double volume = cube(side);

Здесь side — переменная, которая в примере запуска получает значение 4. Вспомним, что заголовок функции cube () был таким:

double cube(double х)

Когда эта функция вызывается, она создает новую переменную типа double по имени х и инициализирует ее значением 4. Это позволяет изолировать данные в main () от того, что происходит в cube (), т.к. cube () работает с копией side, а не с исходными данными.

Вскоре вы увидите пример такой защиты. Переменная, которая используется для приема переданного значения, называется формальным аргументом или формальным параметром. Значение, переданное функции, называется фактическим аргументом или фактическим параметром. Чтобы немного упростить ситуацию, в стандарте C++ слово аргумент используется для обозначения фактического аргумента или параметра, а слово параметр — для обозначения формального аргумента или параметра. Применяя эту терминологию, можно сказать, что передача аргумента инициализирует параметр значением этого аргумента (рис. 7.2).

Переменные, включая параметры, объявленные в функции, являются приватными по отношению к этой функции. Когда функция вызывается, компьютер выделяет память, необходимую для этих переменных. Когда функция завершается, компьютер освобождает память, которая была использована этими переменными. (В некоторых источниках по C++ это выделение и освобождение памяти для переменных называется созданием и уничтожением переменных.) Такие переменные называются локальными переменными, потому что они локализованы в пределах функции.

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

Множественные аргументы

Функция может принимать более одного аргумента. При вызове функции такие аргументы просто отделяются друг от друга запятыми:

n_chars( 'R', 25) ;

Это передает два аргумента функции n_chars (), определение которой будет приведено чуть позже.

Аналогично, при определении функции используется разделенный запятыми список параметров в ее заголовке:

void n_chars(char с, int n) // два параметра

Этот заголовок устанавливает, что функция n_chars () принимает один параметр типа char; и один типа int. Параметры с и n инициализируются значениями, переданными функции. Если функция имеет два параметра одного и того же типа, то типы каждого параметра должны указываться по отдельности. Комбинирование объявлений аргументов, как это делается с обычными переменными, не допускается:

void fifi (float a, float b)

// объявляет каждую переменную отдельно

void fufu (float a, b)

// не допускается

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

void n_chars(char с, int n) ; // прототип, стиль 1

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

Также можно опускать имена переменных в прототипе:

void n_chars(char, int); // прототип, стиль 2

Тем не менее, указание имен переменных часто помогает прояснить прототип, особенно если два параметра имеют один и тот же тип. Впоследствии имена параметров будут напоминать, какой аргумент для чего предназначен:

double melon_density(double weight, double volume);

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

// twoarg.срр -- функция с двумя аргументами

#include <iostream>

using namespace std;

void n_chars(char, int);

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]