2.6 – Предварительные объявления и определения
Взгляните на этот, казалось бы, правильный пример программы:
Вы ожидаете, что эта программа даст результат:
Но на самом деле она вообще не компилируется! Visual Studio выдает следующую ошибку компиляции:
Причина, по которой эта программа не компилируется, заключается в том, что компилятор последовательно компилирует содержимое исходных файлов. Когда компилятор достигает вызова функции add в строке 5 в функции main , он не знает, что такое add , потому что мы определили add только в строке 9! Это вызывает ошибку, « identifier not found » (идентификатор не найден).
Более старые версии Visual Studio выдали бы дополнительную ошибку:
Это несколько вводит в заблуждение, учитывая, что add в первый раз вообще не была определена. Несмотря на это, в целом полезно отметить, что довольно часто одна ошибка вызывает множество повторяющихся или связанных ошибок или предупреждений.
Лучшая практика
При устранении ошибок компиляции в своих программах всегда сначала устраняйте первую возникшую ошибку, а затем снова попробуйте скомпилировать программу.
Чтобы решить эту проблему, нам нужно разобраться с тем фактом, что компилятор не знает, что такое add . Есть два распространенных способа решения этой проблемы.
Вариант 1. Изменение порядка определений функций
Один из способов решения проблемы – переупорядочить определения функций так, чтобы add была определена перед main :
Таким образом, к моменту вызовов функции add из main компилятор уже будет знать, что такое add . Поскольку это очень простая программа, сделать это изменение относительно легко. Однако в более большой программе может быть утомительно пытаться выяснить, какие функции вызывают какие другие функции (и в каком порядке), чтобы их можно было определять последовательно.
Кроме того, этот вариант не всегда возможен. Допустим, мы пишем программу, которая имеет две функции A и B . Если функция A вызывает функцию B , а функция B вызывает функцию A , то нет способа упорядочить функции таким образом, чтобы компилятор был доволен. Если вы сначала определите A , компилятор пожалуется, что не знает, что такое B . Если вы сначала определите B , компилятор пожалуется, что не знает, что такое A .
Вариант 2. Использование предварительного объявления
Мы также можем исправить это, используя предварительное объявление.
Предварительное объявление позволяет нам сообщить компилятору о существовании идентификатора до его фактического определения.
В случае функций это позволяет нам сообщить компилятору о существовании функции до того, как мы определим тело функции. Таким образом, когда компилятор встретит вызов функции, он поймет, что мы выполняем вызов функции, и сможет проверить, правильно ли мы вызываем функцию, даже если он еще не знает, как и где эта функция определена.
Чтобы написать предварительное объявление для функции, мы используем инструкцию объявления, называемую прототипом функции. Прототип функции состоит из типа возвращаемого значения функции, имени и параметров, но не содержит тела функции (фигурные скобки и всё, что между ними), и заканчивается точкой с запятой.
Вот прототип функции для функции add :
Итак, вот наша исходная программа, которая не компилировалась, использующая прототип функции в качестве предварительного объявления для функции add :
Теперь, когда компилятор дойдет до вызова add в main , он будет знать, как выглядит add (функция, которая принимает два целочисленных параметра и возвращает целое число), и не будет жаловаться.
Стоит отметить, что в прототипах функций не нужно указывать имена параметров. В приведенном выше коде вы также можете предварительно объявить эту функцию следующим образом:
Однако мы предпочитаем давать имена параметрам (используя те же имена, что и реальная функция), потому что это позволяет вам понять, какие параметры использует функция, просто взглянув на ее прототип. В противном случае вам нужно будет найти определение функции.
Лучшая практика
При определении прототипов функций сохраняйте имена параметров. Вы можете легко создавать предварительные объявления, используя копирование/вставку из определения функции. Не забудьте в конце поставить точку с запятой.
Забываем о теле функции
Программисты-новички часто задаются вопросом, что произойдет, если они дадут предварительное объявление функции, но не дадут ее определение.
Ответ: это зависит от обстоятельств. Если предварительное объявление сделано, но функция никогда не вызывается, программа будет компилироваться и работать нормально. Однако если предварительное объявление сделано, и функция вызывается, но программа никогда не определяет эту функцию, программа будет компилироваться нормально, но компоновщик (линкер) будет жаловаться, что не может разрешить вызов функции.
Рассмотрим следующую программу:
В этой программе мы даем предварительное объявление add и вызываем add , но нигде не определяем add . Когда мы пытаемся скомпилировать эту программу, Visual Studio выдает следующее сообщение:
Как видите, программа скомпилировалась нормально, но проблема возникла на этапе компоновки, потому что int add(int, int) никогда не определялась.
Другие типы предварительных объявлений
Предварительные объявления чаще всего используются с функциями. Однако предварительные объявления в C++ также могут использоваться и с другими идентификаторами, такими как переменные и пользовательские типы. Переменные и пользовательские типы имеют отличающийся синтаксис для предварительного объявления, поэтому мы рассмотрим их в будущих уроках.
Объявления против определений
В C++ вы часто будете слышать слова «объявление» и «определение», часто взаимозаменяемые. Что они имеют в виду? Теперь у вас достаточно знаний, чтобы понять разницу между ними.
Определение фактически реализует (для функций или типов) или создает экземпляр (для переменных) идентификатора. Вот несколько примеров определений:
Определение требуется, чтобы удовлетворить компоновщик (линкер). Если вы используете идентификатор без определения, компоновщик выдаст ошибку.
Правило одного определения (или сокращенно ODR, one definition rule) – это хорошо известное правило в C++. ODR состоит из трех частей:
- В заданном файле функция, объект, тип или шаблон могут иметь только одно определение.
- В заданной программе объект или обычная функция может иметь только одно определение. Это выделено потому, что программы могут иметь более одного файла (мы рассмотрим это в следующем уроке).
- Типы, шаблоны, встроенные функции и переменные могут иметь идентичные определения в разных файлах. Мы еще не рассмотрели большинство из этих вещей, поэтому не беспокойтесь об этом сейчас – мы вернемся к ним, когда это будет уместно.
Нарушение пункта 1 правила одного определения приведет к тому, что компилятор выдаст ошибку переопределения. Нарушение пункта 2 правила одного определения может привести к тому, что компоновщик выдаст ошибку переопределения. Нарушение пункта 3 правила одного определения приведет к неопределенному поведению.
Вот пример нарушения пункта 1:
Поскольку указанная выше программа нарушает пункт 1 правила одного определения, компилятор Visual Studio выдает следующие ошибки компиляции:
Для продвинутых читателей
Функции, которые имеют общий идентификатор, но имеют разные параметры, считаются отдельными функциями. Мы обсудим это далее в уроке «8.9 – Перегрузка функций».
Объявление – это инструкция, которая сообщает компилятору о существовании идентификатора и информацию о его типе. Вот несколько примеров объявлений:
Объявление – это всё, что нужно компилятору. Вот почему мы можем использовать предварительное объявление, чтобы сообщить компилятору об идентификаторе, который на самом деле не будет определен позже.
В C++ все определения также служат объявлениями. Вот почему int x появляется в наших примерах как для определений, так и для объявлений. Поскольку int x – это определение, оно же и объявление. В большинстве случаев определение служит нашим целям, поскольку оно удовлетворяет и компилятор, и компоновщик. Явное объявление нам нужно предоставить только тогда, когда мы хотим использовать идентификатор до того, как он будет определен.
Хотя верно, что все определения являются объявлениями, обратное неверно: все объявления не являются определениями. Примером этого является прототип функции – он удовлетворяет компилятор, но не компоновщик. Объявления, которые не являются определениями, называются чистыми объявлениями. Другие типы чистых объявлений включают в себя предварительные объявления для переменных и объявления типов (вы столкнетесь с ними в будущих уроках, сейчас о них не нужно беспокоиться).
ODR не применяется к чистым объявлениям (это правило одного определения, а не правило одного объявления), поэтому вы можете иметь столько чистых объявлений для идентификатора, сколько хотите (хотя наличие более одного является избыточным).
Примечание автора
В обычном языке термин «объявление» обычно используется для обозначения «чистого объявления», а «определение» используется для обозначения «определения, которое также служит объявлением». Таким образом, мы обычно называем int x; определением, хотя это и определение, и объявление.
Небольшой тест
Вопрос 1
Что такое прототип функции?
Прототип функции – это инструкция объявления, которая включает в себя имя функции, тип возвращаемого значения и параметры. Он не включает в себя тело функции.
Вопрос 2
Что такое предварительное объявление?
Предварительное объявление сообщает компилятору, что идентификатор существует до того, как он будет фактически определен.
Вопрос 3
Как мы даем предварительное объявление для функций?
Для функций предварительным объявлением служит прототип функции.
Другие типы идентификаторов (например, переменные и пользовательские типы) имеют для предварительного объявления отличающийся синтаксис.
Вопрос 4
Напишите прототип для этой функции (используйте предпочтительную форму с именами):
Вопрос 5
Для каждой из следующих программ укажите, завершится ли их компиляция неудачей, завершится ли их линковка неудачей, или будут успешными и компиляция, и линковка. Если не уверены, попробуйте их скомпилировать!
Не компилируется. Компилятор будет жаловаться, что add() , вызываемая в main() , не имеет того же количества параметров, что и та, что была предварительно объявлена.
Не компилируется. Компилятор будет жаловаться, что не может найти подходящую функцию add() , которая принимает 3 аргумента, потому что функция add() , которая была предварительно объявлена, принимает только 2 аргумента.
Не линкуется. Компилятор сопоставит предварительно объявленный прототип add с вызовом функции add() в main() . Однако функция add() , которая принимает два параметра, никогда не была реализована (мы реализовали только ту, которая принимает 3 параметра), поэтому компоновщик будет жаловаться.
Компилируется и линкуется. Вызов функции add() соответствует прототипу, который был предварительно объявлен, реализованная функция также совпадает.
Прототип функции
- Прототипом функции в языке Си или C++ называется объявление функции, не содержащее тела функции, но указывающее имя функции, арность, типы аргументов и возвращаемый тип данных. В то время как определение функции описывает, что именно делает функция, прототип функции может восприниматься как описание её интерфейса.
Связанные понятия
Стиль о́тступов (индентация) — правила форматирования исходного кода, в соответствии с которыми отступы программных блоков проставляются в удобочитаемой манере.
В программировании, ассемблерной вставкой называют возможность компилятора встраивать низкоуровневый код, написанный на ассемблере, в программу, написанную на языке высокого уровня, например, Си или Ada. Использование ассемблерных вставок может преследовать следующие цели.
Парсер (англ. parser; от parse – анализ, разбор) или синтаксический анализатор — часть программы, преобразующей входные данные (как правило, текст) в структурированный формат. Парсер выполняет синтаксический анализ текста.
В языках программирования объявле́ние (англ. declaration) включает в себя указание идентификатора, типа, а также других аспектов элементов языка, например, переменных и функций. Объявление используется, чтобы уведомить компилятор о существовании элемента; это весьма важно для многих языков (например, таких как Си), требующих объявления переменных перед их использованием.
Прототип функции
Прототипом функции в языке Си или C++ называется объявление функции, которое не содержит тело функции, но указывает имя функции, арность, типы аргументов и возвращаемый тип данных. В то время как определение функции описывает, что именно делает функция, прототип функции может восприниматься как описание её интерфейса.
В прототипе имена аргументов являются необязательными, тем не менее, необходимо указывать тип вместе со всеми модификаторами (например, указатель ли это или константный аргумент).
Содержание
Пример
В качестве примера, рассмотрим следующий прототип функции:
Этот прототип объявляет функцию с именем «foo», которая принимает один аргумент «n» целого типа и возвращает целое число. Определение функции может располагаться где угодно в программе, но определение требуется только в случае её использования.
Использование
Уведомление компилятора
Если функция предварительно не была объявлена, а её имя встречается в выражении, следующим за открывающей скобкой, то она неявно объявляется как функция, возвращающая результат типа int и ничего не предполагается о её аргументах. В этом случае компилятор не сможет выполнить проверку типов аргументов и арность, когда функция вызывается с некоторыми аргументами. Это потенциальный источник проблем. Следующий код иллюстрирует ситуацию, в которой поведение неявно объявленной функции не определено.
Функция «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
См. также
- Сигнатура типа
- Язык программирования Си
Wikimedia Foundation . 2010 .
Полезное
Смотреть что такое «Прототип функции» в других словарях:
Прототип — (от др. греч. πρῶτος первый и τύπος отпечаток, оттиск; прообраз, образец), Prototype: Прототип (когнитивная психология) абстрактный образ, воплощающий множество сходных форм одного и того же объекта или паттерна, наиболее… … Википедия
прототип — ПРОТОТИП (от греч. prototypon прообраз) в когнитивистике лучший представитель («лучший пример») когнитивной или языковой категории. Теория прототипов разрабатывалась параллельно в когнитивной психологии (Л.С. Выготский, Э. Рош),… … Энциклопедия эпистемологии и философии науки
strcpy — strcpy функция стандартной библиотеки языка программирования Си, для копирования нуль терминированной строки (включая нуль терминатор) в заданный буфер . Содержание 1 Прототип функции 2 Возвращаемое значение … Википедия
WinMain — функция, в которой программист пишет основной код, который будет выполнять программа под Windows для подсистемы GUI. Эта функция вызывается из функции WinMainCRTStartup (находящейся в CRT), которая по умолчанию является точкой входа в программу… … Википедия
Объявление (информатика) — Возможно, эта статья содержит оригинальное исследование. Добавьте ссылки на источники, в противном случае она может быть выставлена на удаление. Дополнительные сведения могут быть на странице обсуждения. (25 мая 2011) … Википедия
Strcpy — функция стандартной библиотеки языка программирования Си, для копирования нуль терминированной строки в заданный буфер. Содержание 1 Прототип функции 2 Возвращаемое значение … Википедия
Экспериментальная модель — Прототип (от греч. protos первый и typos отпечаток, оттиск) прообраз, образец, оригинал. Прототип м. греч. первообраз, начальный, основной образец, истинник. Прототипный, типический, первообразный, первообразцовый (Словарь Даля). Прототип в… … Википедия
D (язык программирования) — У этого термина существуют и другие значения, см. D. D Семантика: мультипарадигменный: императивное, объектно ориентированное, обобщённое программирование Тип исполнения: компилятор Появился в: 1999 Автор(ы) … Википедия
Feof — feof функция стандартной библиотеки языка Си, объявленная в заголовочном файле stdio.h. Ее основное назначение отличать случаи, когда операции потока достигают конца файла, от случаев, когда возвращается код ошибки EOF («конец файла» … Википедия
DLL — (англ. Dynamic link library динамически подключаемая библиотека) понятие операционных систем Microsoft Windows и IBM OS/2; динамическая библиотека, позволяющая многократное применение различными программными приложениями. K DLL… … Википедия
Прототип функции
Прототипом функции в языке Си или C++ называется объявление функции, не содержащее тела функции, но указывающее имя функции, арность, типы аргументов и возвращаемый тип данных. В то время как определение функции описывает, что именно делает функция, прототип функции может восприниматься как описание её интерфейса.
В прототипе имена аргументов являются необязательными, тем не менее, необходимо указывать тип вместе со всеми модификаторами (например, указатель ли это или константный аргумент).