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

  Юрий  | 

    | 

  Обновл. 6 Мар 2019  | 

 21625

 ǀ   11 

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

Предыстория

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

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

The sum of 3 and 4 is: 7

Но в действительности эта программа даже не скомпилируется. Причиной тому является то, что компилятор читает код последовательно. Когда он встречает вызов функции add() в строке №5 функции main(), он даже не знает, что такое add(), так как мы его ещё не определили! Из-за этого мы получим следующую ошибку:

add: идентификатор не найден

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

Решение №1: Поместить определение функции add() выше её вызова (т.е. перед функцией main()):

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


В C++ вы будете часто слышать слова «объявление» и «определение». Что это такое?

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

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

В C++ есть правило одного определения, которое состоит из трёх частей:

   Внутри файла: функция, объект, тип или шаблон могут иметь только одно определение.

   Внутри программы: объект или обычная функция могут иметь только одно определение.

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

Нарушение первой части правила приведёт к ошибке компиляции. Нарушение второй и третьей части правила приведёт к ошибке линкинга.

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

Объявление — это всё, что необходимо для удовлетворения компилятора, но недостаточно для удовлетворения линкера. Определение — это то, что сделает счастливым как компилятора, так и линкера.

Тест

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

2. Запишите прототип следующей функции:

3. Выясните, какие из следующих программ не пройдут этап компиляции, какие не пройдут этап линкинга, а какие не пройдут и то, и другое?

4.

5.

6.

Ответы


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

Ответ №1

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

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

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

Ответ №2

Ответ №3

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

Ответ №4

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

Ответ №5

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

Ответ №6

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

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (286 оценок, среднее: 4,90 из 5)
Загрузка...

Комментариев: 11

  1. Аватар Олег:

    Юрий. огромное спасибо за Ваш труд и за ваше терпение в разборе ошибок.

  2. Аватар Vadim:

    1. Юрий Юрий:

      Вы предыдущие уроки читали? Решение вашей ошибки находится сразу в двух уроках — 5 и 7.

      #include «stdafx.h» всегда прописывается в коде первой строчкой, всегда первой (не второй и не третьей).

      1. Аватар Vadim:

        Спасибо за помощь в поиске ошибки.

        1. Юрий Юрий:

          Пожалуйста.

    2. Аватар Никита:

      Ты конечно сверхразум написать using namespace std в функции main()

  3. Аватар Vadim:

    "Решение 1" не работает. Пишет: "непредвиденный конец файла во время поиска предкомпилированного заголовка.Возможно вы забыли добавить директиву "#include "stdafx.h"" в источник." Как исправить ошибку?

    1. Юрий Юрий:

      Добавить директиву #include "stdafx.h" в код.

      1. Аватар Vadim:

        После добавления возникают новые ошибки:
        1. Не удаётся открыть источник
        файл "stdafx.h".

        2. cout: необъявленный
        идентификатор (строка11).

        3. endl: необъявленный
        идентификатор (строка 11).

        1. Юрий Юрий:

          Скиньте сюда ваш код, в котором возникают ошибки.

        2. Аватар Эрик:

          Он в визуал студион 15, создал не проект видимо, а просто файл. Поэтому у него не создался заголовочный файл stdafx.h. И когда он обьявляет её в начале, ошибка выходит, так как в проекте нет этого файла. Ему нужно создать именно проект, а не просто файл.

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

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