В компьютерном программировании предварительное объявление — это объявление идентификатора (обозначающего сущность , такую как тип, переменная, константа или функция), для которого программист еще не дал полного определения .
Компилятору необходимо знать определенные свойства идентификатора (размер для выделения памяти , тип данных для проверки типа, например, сигнатуру типа функций), но не другие детали, такие как конкретное значение, которое он содержит (в случае переменных или констант) или определение (в случае функций). Это особенно полезно для однопроходных компиляторов и раздельной компиляции.
Прямое объявление используется в языках, требующих объявления перед использованием; оно необходимо для взаимной рекурсии в таких языках, поскольку невозможно определить такие функции (или структуры данных) без прямой ссылки в одном определении: одна из функций (соответственно, структур данных) должна быть определена первой. Также полезно разрешить гибкую организацию кода, например, если требуется разместить основное тело наверху, а вызываемые функции под ним.
В других языках предварительные объявления не нужны, что обычно требует многопроходного компилятора и для некоторых компиляций, отложенных до времени компоновки . В этих случаях идентификаторы должны быть определены (переменные инициализированы, функции определены) до того, как их можно будет использовать во время выполнения без необходимости предварительного определения в исходном коде для компиляции или интерпретации: идентификаторы не должны быть немедленно разрешены для существующей сущности.
Вот простой пример на языке C:
void printThisInteger ( int );
В C и C++ строка выше представляет собой предварительное объявление функции и является прототипом функции . После обработки этого объявления компилятор позволит программному коду ссылаться на сущность printThisInteger
в остальной части программы. Определение функции должно быть предоставлено где-то (в том же файле или другом, где компоновщик должен будет правильно сопоставить ссылки на определенную функцию в одном или нескольких объектных файлах с определением, которое должно быть уникальным, в другом):
void printThisInteger ( int x ) { printf ( "%d \n " , x ); }
Переменные могут иметь только предварительное объявление и не иметь определения. Во время компиляции они инициализируются правилами, специфичными для языка (неопределенными значениями, 0, указателями NULL, ...). Переменные, которые определены в других исходных/объектных файлах, должны иметь предварительное объявление, указанное с помощью ключевого слова extern
:
int foo ; //foo может быть определен где-то в этом файле extern int bar ; //bar должен быть определен в каком-то другом файле
В Pascal и других языках программирования Wirth есть общее правило, что все сущности должны быть объявлены перед использованием, и поэтому предварительное объявление необходимо для взаимной рекурсии, например. В C применяется то же самое общее правило, но с исключением для необъявленных функций и неполных типов. Таким образом, в C возможно (хотя и неразумно) реализовать пару взаимно рекурсивных функций следующим образом:
int first ( int x ) { if ( x == 0 ) return 1 ; else return second ( x -1 ); // переслать ссылку на second } int second ( int x ) { if ( x == 0 ) return 0 ; else return first ( x -1 ); // обратная ссылка на first }
В Pascal та же реализация требует предварительного объявления second
перед его использованием в first
. Без предварительного объявления компилятор выдаст сообщение об ошибке, указывающее на то, что идентификатор был second
использован без объявления.
В некоторых объектно-ориентированных языках, таких как C++ и Objective-C , иногда необходимо предварительное объявление классов. Это делается в ситуациях, когда необходимо знать, что имя класса является типом, но когда необязательно знать структуру.
В C++ классы и структуры можно предварительно объявить следующим образом:
класс MyClass ; структура MyStruct ;
В C++ классы могут быть предварительно объявлены, если вам нужно использовать только тип указателя на этот класс (поскольку все указатели объектов имеют одинаковый размер, и это то, что заботит компилятор). Это особенно полезно внутри определений классов, например, если класс содержит член, который является указателем (или ссылкой) на другой класс.
Forward-declaration используется для избежания ненужного связывания, что помогает сократить время компиляции за счет уменьшения количества включений заголовков. Это имеет тройное преимущество:
Предварительного объявления класса недостаточно, если вам нужно использовать фактический тип класса, например, если у вас есть член, типом которого является этот класс напрямую (не указатель), или если вам нужно использовать его в качестве базового класса, или если вам нужно использовать методы класса в методе.
В Objective-C классы и протоколы можно предварительно объявить следующим образом:
@class МойКласс ; @protocol МойПротокол ;
В Objective-C классы и протоколы могут быть предварительно объявлены, если вам нужно использовать их только как часть типа указателя объекта, например MyClass * или id<MyProtocol> . Это особенно полезно внутри определений классов, например, если класс содержит член, который является указателем на другой класс; чтобы избежать циклических ссылок (т. е. этот класс также может содержать член, который является указателем на этот класс), мы просто предварительно объявляем классы.
Предварительного объявления класса или протокола недостаточно, если вам необходимо создать подкласс этого класса или реализовать этот протокол.
Термин «прямая ссылка» иногда используется как синоним « прямого объявления» . [1] Однако чаще его используют для обозначения фактического использования сущности до любого объявления; то есть первая ссылка на second
в коде выше является прямой ссылкой. [2] [3] Таким образом, можно сказать, что поскольку прямые объявления являются обязательными в Pascal, прямые ссылки запрещены.
Пример (допустимой) прямой ссылки в C++ :
класс C { public : void mutator ( int x ) { myValue = x ; } int accessor () const { return myValue ; } private : int myValue ; };
В этом примере есть две ссылки на myValue
до того, как она объявлена. C++ обычно запрещает прямые ссылки, но они разрешены в особом случае членов класса. Поскольку функция-член accessor
не может быть скомпилирована, пока компилятор не узнает тип переменной- члена myValue
, компилятор должен запомнить определение accessor
до тех пор, пока он не увидит myValue
объявление .
Разрешение прямых ссылок может значительно увеличить сложность и требования к памяти компилятора и, как правило, не позволяет реализовать компилятор за один проход .