Прототипы функций
Прототипом функции в языке Си или C++ называется объявление функции, не содержащее тела функции, но указывающее имя функции, арность, типы аргументов и возвращаемый тип данных. В то время как определение функции описывает, что именно делает функция, прототип функции может восприниматься как описание её интерфейса.
double new_style(int a, double *x); /* прототип функции */
В прототипе имена аргументов являются необязательными, тем не менее, необходимо указывать тип вместе со всеми модификаторами (например, указатель ли это или константный аргумент).
double alt_style(int, double *); /* альтернативная форма прототипа */
Урок №19. Прототип функции и Предварительное объявление
На этом уроке мы рассмотрим прототип функции и предварительное объявление в языке С++.
Оглавление:
- Наличие проблемы
- Прототипы функций и Предварительное объявление
- Предварительно объявили, но не определили
- Объявление vs. Определение
- Тест
- Ответы
Наличие проблемы
Посмотрите на этот, казалось бы, невинный кусочек кода под названием add.cpp:
std :: cout << "The sum of 3 and 4 is: " << add ( 3 , 4 ) << std :: endl ; int add ( int x , int y ) return x + y ;
Вы, наверное, ожидаете увидеть примерно следующий результат:
The sum of 3 and 4 is: 7
Но в действительности эта программа даже не скомпилируется. Причиной этому является то, что компилятор читает код последовательно. Когда он встречает вызов функции add() в строке №5 функции main(), он даже не знает, что такое add(), так как это еще не определили! В результате чего мы получим следующую ошибку:
add: идентификатор не найден
Чтобы устранить эту проблему, мы должны учитывать тот факт, что компилятор не знает, что такое add(). Есть 2 решения.
Решение №1: Поместить определение функции add() выше её вызова (т.е. перед функцией main()):
int add ( int x , int y )
return x + y ;
std :: cout << "The sum of 3 and 4 is: " << add ( 3 , 4 ) << std :: endl ;
Таким образом, при вызове функции add() в функции main(), компилятор будет знать, что это такое. Так как это простая программа, то внести подобные изменения несложно. Однако в программах, содержащих большое количество строк кода, это может быть утомительно — узнавать кто кого вызывает и в каком порядке (чтобы соблюсти правильную последовательность).
Кроме того, этот вариант не всегда возможен. Например, мы пишем программу, которая имеет две функции: А() и В() . Если функция А() вызывает функцию В() , а функция В() вызывает функцию А() , то нет никакого способа упорядочить эти функции таким образом, чтобы они обе одновременно знали о существовании друг друга. Если вы объявите сначала А() , то компилятор будет жаловаться, что не знает, что такое В() . Если вы объявите сначала В() , то компилятор будет жаловаться, что не знает, что такое А() .
Прототипы функций и Предварительное объявление
Решение №2: Использовать предварительное объявление.
Предварительное объявление сообщает компилятору о существовании идентификатора ДО его фактического определения.
В случае функций, мы можем сообщить компилятору о существовании функции до её фактического определения. Для этого нам следует использовать прототип этой функции. Прототип функции (полноценный) состоит из типа возврата функции, её имени и параметров (тип + имя параметра). В кратком прототипе отсутствуют имена параметров функции. Основная часть (между фигурными скобками) опускается. А поскольку прототип функции является стейтментом, то он также заканчивается точкой с запятой.
Вот прототип функции add():
int add ( int x , int y ) ; // прототип функции состоит из типа возврата функции, её имени, параметров и точки с запятой
А вот вышеприведенная программа, но уже с прототипом функции в качестве предварительного объявления аdd():
Использование прототипов функции
Стандарт ANSI С расширяет концепцию предварительного описания функции. Данное расширенное описание называется прототипом функции.
Прототипы функций служат двум целям: во-первых, они определяют тип возвращаемого функцией значений, чтобы компилятор мог генерировать корректный код для возвращаемых данных; во-вторых, они определяют тип и число аргументов, используемых функциями. Прототип имеет следующий вид:
тип имя_функции (список параметров);
Прототип обычно помещается в начало программы и должен появляться перед любым вызовом функции.
Помимо этого, для информирования компилятора о типе возвращаемого функцией значения прототип функции позволяет С осуществлять качественную проверку типов, наподобие проверки, осуществляемой в таких языках, как Паскаль. Прототипы позволяют компилятору найти и сообщить информацию о всех незаконных преобразованиях типов между типами аргументов, используемых при вызове функции, и типами определенных параметров. Также компилятору разрешается сообщать о том, что функция вызывается с неправильным числом аргументов.
Если возможно, С автоматически преобразует тип аргумента в тип, получаемый параметром. Тем не менее, некоторые преобразования типов недопустимы. Если функция имеет прототип, то все нелегальные преобразования будут найдены и появится сообщение об ошибке. В качестве примера, следующая программа вызывает сообщение об ошибке, поскольку пытается вызвать func() с указателем, а не с требуемым float. (Нельзя преобразовать указатель к типу float.)
/* Данная программа использует прототипы функций для достижения строгой проверки типов при вызове func(). Программа не компилируется из-за несоответствия между типом аргументов, определенных в прототипе функции, и типом аргументов, используемых при вызове функции. */
#include
float func (int x, float у); /* прототип */
int main(void)
int x, *y;
x = 10;
у = &x;
func(x, у) ; /* несоответствие типов */
return 0;
>
float func (int x, float y)
printf(«%f», у/(float)x);
return у/(float) x;
>
Использование прототипов также позволяет компилятору выдавать сообщение в случае, если число используемых при вызове функции аргументов не соответствует числу параметров, определенных в функции. Например, следующая программа не будет компилироваться, поскольку func() вызывается с неправильным числом аргументом:
/* Программа не компилируется из-за несоответствия между числом параметров, определенных в прототипе функции, и числом аргументов, используемых при вызове функции. */
#include
float func (int x, float у); /* прототип */
int main(void)
func (2, 2.0, 4); /* неверное число аргументов */
return 0;
>
С технической точки зрения при создании прототипа функции не требуется включать настоящие имена параметров. Например, следующие два варианта абсолютно корректны и равнозначны:
char func (char *, int);
char func (char *str, int count);
Тем не менее, если включить имена параметров, компилятор использует имена для выдачи сообщений о несоответствии типов.
Некоторые функции типа printf() могут принимать переменное число аргументов. Переменное число аргументов определяется в прототипе с помощью многоточия. Например, прототип функции printf() выглядит так:
int printf(const char *fmt, . );
Для создания функции с переменным числом аргументов надо обратиться к описанию стандартной библиотечной функции va_arg().
Следует использовать прототипы функций для предотвращения ошибок, появляющихся в результате вызовов функций с неправильными аргументами. Они также помогают проверить правильность работы программы, не позволяя функциям вызываться с неправильным числом аргументов. Как ранее упоминалось, они требуются в С++.
- Прототипы стандартных библиотечных функций
- Создание прототипов функций, не имеющих параметров
Прототип функции
Прототипом функции в языке Си или C++ называется объявление функции, которое не содержит тело функции, но указывает имя функции, арность, типы аргументов и возвращаемый тип данных. В то время как определение функции описывает, что именно делает функция, прототип функции может восприниматься как описание её интерфейса.
В прототипе имена аргументов являются необязательными, тем не менее, необходимо указывать тип вместе со всеми модификаторами (например, указатель ли это или константный аргумент).
Пример
В качестве примера, рассмотрим следующий прототип функции:
int foo(int n);
Этот прототип объявляет функцию с именем «foo», которая принимает один аргумент «n» целого типа и возвращает целое число. Определение функции может располагаться где угодно в программе, но определение требуется только в случае её использования.
Использование
Уведомление компилятора
Если функция предварительно не была объявлена, а её имя встречается в выражении, следующим за открывающей скобкой, то она неявно объявляется как функция, возвращающая результат типа int и ничего не предполагается о её аргументах. В этом случае компилятор не сможет выполнить проверку типов аргументов и арность, когда функция вызывается с некоторыми аргументами. Это потенциальный источник проблем. Следующий код иллюстрирует ситуацию, в которой поведение неявно объявленной функции не определено.
#include /* * При реализации этого прототипа компилятор выдаст сообщение об ошибке * в main(). Если он будет пропущен, то и сообщения об ошибке не будет. */ int foo(int n); /* Прототип функции */ int main(void) /* Вызов функции */ printf("%d\n", foo()); /* ОШИБКА: у foo отсутствует аргумент! */ return 0; > int foo(int n) /* Вызываемая функция */ if (n == 0) return 1; else return n * foo(n - 1); >
Функция «foo» ожидает аргумент целого типа, находящийся в стеке при вызове. Если прототип пропущен, компилятор не может это обработать и «foo» завершит операцию на некоторых других данных стека (вероятно, это будет обратный адрес или значение переменной, не входящей в область допустимых значений). Включением прототипа функции вы информируете компилятор о том, что функция «foo» принимает один аргумент целого типа и вы тем самым позволяете компилятору обрабатывать подобные виды ошибок.
Создание библиотечных интерфейсов
Путем помещения прототипов функций в заголовочный файл можно описывать интерфейс для библиотек.
Объявления класса
В C++ прототипы функций также используются в определении классов.
Ссылки
- Kernighan, Brian W. & Ritchie, Dennis M. (1988), «The C Programming Language» (2nd ed.), Upper Saddle River, NJ: Prentice Hall PTR, ISBN 0131103628
См. также
- Сигнатура типа
- Язык программирования Си