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

  Юрий  | 

  |

  Обновл. 11 Сен 2021  | 

 138286

 ǀ   19 

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

Наличие проблемы

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

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

The sum of 3 and 4 is: 7

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

Тест

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

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

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

Программа №1:

Программа №2:

Программа №3:

Программа №4:

Ответы


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

Ответ №1

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

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

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

Ответ №2

Ответ №3

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

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

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

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

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

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

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

  1. Василий:

    У прототипа можно { } оставлять?

    1. Фото аватара Юрий:

      Нет, это уже будет определение функции, а не прототип.

  2. Наталья:

    Извините, но правило одного определения, насколько я знаю, не только говорит о том, что у каждого объекта должно быть только одно определение, но и уточняет ситуацию с дублированием определений, а именно:
    два определения класса, шаблона или встроенной функции приемлемы в качестве определения одной и той же сущности тогда и только тогда, когда:
    1. они находятся в различных единицах компиляции;
    2. они идентичны лексема за лексемой;
    3. значения лексем одинаковы в обеих единицах компиляции.

    1. Константин:

      эй, Наталья-красотка! А ну дай ссылочку где таким академическим языком излагают — жуть как такое любю читать!

      1. Наталья:

        У Страуструпа!

  3. Николай:

    Юрий, добрый день.
    Утверждение в ответе №1 "Для функций прототип является предварительным объявлением." понятно.

    Уточните пожалуйста, для чего тогда прототип НЕ является предварительным объявлением (переменная, объект)?

    Заранее благодарю.

  4. Egor:

    Спасибо за статью!

  5. Олег:

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

  6. Vadim:

    1. Фото аватара Юрий:

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

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

      1. Vadim:

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

        1. Фото аватара Юрий:

          Пожалуйста.

    2. Никита:

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

      1. Константин:

        ну правильно — ограничил область видимости рамками main()

  7. 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 не будет опубликован. Обязательные поля помечены *