Урок 19. Предварительное объявление и прототип функции

   ⁄ 

 Обновлено 25 Авг 2017

  ⁄   

Посмотрите на этот, казалось бы, невинный кусочек кода под названием add.cpp:

Вы, наверное, ожидаете увидеть примерно такой результат:

The sum of 3 and 4 is: 7

Но в действительности эта программа даже не скомпилируется. Visual Studio 2005 Express выдаст следующие ошибки:

add.cpp(6) : error C3861: 'add': identifier not found
add.cpp(10) : error C2365: 'add' : redefinition; previous definition was 'formerly unknown identifier'

Причиной провала компиляции является то, что компилятор читает код последовательно. Когда он встречает вызов функции add() в строке 6 функции main(), он даже не знает, что такое add, так как мы не определили add() до 10-ой строчки! Из-за этого получаем ошибку «identifier not found» («идентификатор не найден»).

Когда Visual Studio 2005 встречает объявление функции add() в строке 10, он также жалуется на переопределение add(). Эта ошибка до некоторой степени вводит в заблуждение, но она является следствием первой.

Правило: Всегда решайте ошибки компиляции в ваших программах по порядку, начиная с первой.

Дабы устранить эту проблему, мы должны учитывать тот факт, что компилятор не знает, что такое add(). Есть два основных решения этой проблемы.

Решение 1: Перенесите вызов функции так, чтобы add() определялся перед main():

Таким образом, когда main() вызывает add(), компилятор уже будет знать что это такое. Так как это простая программа, то внести изменения будет несложно. Тем не менее, в более крупных программах, это может быть утомительно – узнавать какие функции вызывают какие и в каком порядке, дабы соблюдать правильную последовательность.

Кроме того, этот вариант не всегда возможен. Скажем, мы пишем программу, которая имеет две функции: А и В. Если функция А вызывает функцию B, а функция B вызывает функцию А, то нет никакого способа упорядочения этих функций таким образом, чтобы они оба были счастливы. Если вы объявляете сначала А, компилятор будет жаловаться на то, что он не знает, что такое B. Если вы объявляете сначала В, то компилятор будет жаловатся, что не знает, что такое А.

Прототипы функций и предварительное объявление

Решение 2: Используйте предварительное объявление.

Предварительное объявлениеforward declaration», либо еще «опережающее объявление») позволяет сообщить компилятору о существовании идентификатора до его фактического определения.

В случае функций, мы можем сообщить компилятору о существовании функции до её фактического написания. Для осуществления предварительного объявления используется особый стейтмент — прототип функции. Прототип функции состоит из возвращаемого типа, имени, параметров, но без основного тела (части между фигурными скобками). И так, как прототип функции – это инструкция, то она заканчивается точкой с запятой.

Вот прототип функции add():

А вот та программа, которая не компилировалась, но уже с прототипом функции в качестве предварительного объявления для аdd():

Теперь, когда компилятор встретит функцию add() в main, он будет знать, что это такое и как оно выглядит (функция, которая принимает два целочисленных параметра и возвращает целое число).

Стоит отметить, что в прототипах функций можно и не указывать имена параметров. В программе выше, вы можете записать прототип и так:

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

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

Предварительно объявили, но не определили

Вот один из вопросов, который крутится у многих начинающих программистов: «Что произойдет, если мы предварительно объявим функцию, но не запишем её основную часть, т.е. определение?

Ответ: всегда по-разному. Если предварительное объявление записано, но функция никогда не вызывается, то программа будет компилироваться и работать нормально. Однако, если предварительное объявление записано, функция вызывается, но нет её основной части, то программа скомпилируется хорошо, но линкер будет жаловаться, что он не может обработать вызов функции.

Рассмотрим следующую программу:

В этой программе мы предварительно объявили add() и сделали вызов, но не записали основную часть самой функции. При попытке компиляции Visual Studio 2005 Express выдаст следующую ошибку:

Compiling...
add.cpp
Linking...
add.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
add.exe : fatal error LNK1120: 1 unresolved externals

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

Другие типы предварительных объявлений

Предварительные объявления чаще всего используются с функциями. Тем не менее, их можно использовать и с другими идентификаторами в C++, такими как: переменные и типы, определяемые пользователем. Они несколько отличаются по синтаксису, но об этом более детально мы поговорим в следующих уроках.

Объявления vs определения

В C++ вы будете часто слышать слова «объявление» и «определение». Что они значат? Сейчас у вас есть достаточное количество информации, дабы понять разницу между ними.

Определение фактически реализует или конкретизирует (вызывает выделение памяти) идентификатор. Вот некоторые примеры определений:

Вы можете иметь только одно определение для одного идентификатора. Оно необходимо для удовлетворения линкера.

Объявление – это инструкция, которая определяет идентификатор (переменную или имя функции) и его тип. Вот некоторые примеры объявлений:

Объявление это всё, что необходимо для удовлетворения компилятора. Вот поэтому использование forward declarations и достаточно для того, чтобы сделать компилятора счастливым. Тем не менее, если вы забудете записать определение (основную часть) идентификатора – линкер будет жаловаться.

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

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

Тест

1) Какая разница между прототипом функции и предварительным объявлением?

2) Напишите прототип для этой функции.

3) Для каждой из следующих программ, выясните, где они не скомпилируются, где потерпят неудачу при линкинге, а где будет провал как с компиляцией, так и с линкингом вместе. Если вы не уверены, попробуйте их скомпилировать!

4)

5)

6)

Ответы

Чтобы посмотреть ответ, нажмите мышкой на нужный номер.

Ответ 1

Прототип функции – это инструкция объявления, которая включает имя функции, возвращаемый тип и параметры. Оно не включает основную часть функции.

Предварительное объявление сообщает компилятору, что идентификатор существует до того, как он на самом деле определится.

Для функций прототип является предварительным объявлением.

Другие типы идентификаторов (например, переменные и типы, определяемые пользователем) имеют другой синтаксис для forward declarations.

Ответ 2

Ответ 3

Не скомпилируется. Компилятор будет жаловаться на то, что add(), который вызывается в main(), не имеет столько же параметров, как тот, который был объявлен сначала.

Ответ 4

Не скомпилируется. Компилятор будет жаловаться на то, что add(), который вызывается в main(), не имеет столько же параметров, как тот, который был объявлен сначала.

Ответ 5

Провал во время линкинга. Функция аdd(), которая принимает два параметра, никогда не была реализована (мы реализовали только одну, которая имеет 3 параметра), из-за этого линкер будет жаловаться.

Ответ 6

Успешная компиляция и линкинг. Вызов функции add() соответствует прототипу, который был объявлен и сама реализация функции правильна.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (36 оценок, среднее: 4,92 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *