Что такое предварительные объявления в C ++? - PullRequest
191 голосов
/ 21 января 2011

В: http://www.learncpp.com/cpp-tutorial/19-header-files/

Упоминается следующее:

add.cpp:

int add(int x, int y)
{
    return x + y;
}

main.cpp:

#include <iostream>

int add(int x, int y); // forward declaration using function prototype

int main()
{
    using namespace std;
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
    return 0;
}

Мы использовали предварительное объявление, чтобы компилятор знал, что было "add" при компиляции main.cpp.Как упоминалось ранее, написание предварительных объявлений для каждой функции, которую вы хотите использовать и которая находится в другом файле, может быстро стать утомительным.В чем проблема, если мы используем ее в функции main()?

Ответы [ 8 ]

350 голосов
/ 21 января 2011

Почему предварительное объявление необходимо в C ++

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

Это на самом деле просто позволяет компилятору лучше выполнять проверку кодаи позволяет ему убирать свободные концы, чтобы он мог создать аккуратный объектный файл.Если вам не нужно пересылать объявленные вещи, компилятор создаст объектный файл, который должен будет содержать информацию обо всех возможных предположениях относительно того, какой может быть функция add.И компоновщик должен был бы содержать очень умную логику, чтобы попытаться определить, какое «add» вы на самом деле намеревались вызвать, когда функция «add» может находиться в другом объектном файле, который компоновщик объединяет с тем, который использует add для созданияDLL или Exe.Возможно, что компоновщик может получить неправильное добавление.Скажем, вы хотели использовать int add (int a, float b), но случайно забыли написать его, но компоновщик нашел уже существующий int add (int a, int b) и подумал, что он правильный, и использовал его вместо этого.Ваш код будет компилироваться, но не будет делать то, что вы ожидали.

Итак, просто для того, чтобы все было ясно и избегали догадок и т. Д., Компилятор настаивает на том, чтобы вы объявляли все перед использованием.* Разница между декларацией и определением

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

Каким образом предварительные объявления могут значительно сократить время сборки

Вы можете получить объявлениефункции в ваш текущий файл .cpp или .h путем # включения заголовка, который уже содержит объявление функции.Тем не менее, это может замедлить компиляцию, особенно если вы #include заголовка в .h вместо .cpp вашей программы, так как все, что #include .h вы пишете, в конечном итоге # include'ing все заголовкиВы тоже написали #includes для.Внезапно компилятор имеет #included страницы и страницы кода, которые ему нужно скомпилировать, даже если вы хотите использовать только одну или две функции.Чтобы избежать этого, вы можете использовать предварительную декларацию и просто ввести объявление функции в верхней части файла.Если вы используете только несколько функций, это действительно может сделать ваши компиляции быстрее, чем всегда, включая заголовок.Для действительно больших проектов разница может составлять час или больше времени компиляции, сокращенного до нескольких минут.

Прерывание циклических ссылок, когда оба определения используют друг друга

Кроме того, предварительные декларации могут помочь вам разорвать циклы.Здесь две функции пытаются использовать друг друга.Когда это происходит (и это совершенно правильно), вы можете #include один заголовочный файл, но этот заголовочный файл пытается #include заголовочный файл, который вы сейчас пишете .... который затем #include другой заголовок, который включает в себя тот, который вы пишете.Вы застряли в ситуации с курицей и яйцом, когда каждый заголовочный файл пытается включить другой.Чтобы решить эту проблему, вы можете заранее объявить нужные детали в одном из файлов и оставить #include в этом файле.

Например:

Файл Car.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car
{
    std::vector<Wheel> wheels;
};

Файл Wheel.h

Хм ... здесь требуется объявление Car, так как Wheel имеет указатель на Car, но Car.h не может быть включен здесь, так как это приведет к ошибке компилятора.Если был включен Car.h, то он попытался бы включить Wheel.h, который включал бы Car.h, который включал бы Wheel.h, и это продолжалось бы вечно, поэтому вместо этого компилятор вызывает ошибку.Решение состоит в том, чтобы вместо этого объявить Car вперед:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Если у класса Wheel есть методы, которые должны вызывать методы car, эти методы могут быть определены в Wheel.cpp, и теперь Wheel.cpp может включать Car.h не вызывая цикл.

25 голосов
/ 21 января 2011

Компилятор ищет каждый символ, используемый в текущей единице перевода, ранее объявленной или нет в текущей единице. Это просто вопрос стиля, предоставляющий все сигнатуры методов в начале исходного файла, а определения будут предоставлены позже. Значительное использование этого происходит, когда вы используете указатель на класс как переменную-член другого класса.

//foo.h
class bar;    // This is useful
class foo
{
    bar* obj; // Pointer or even a reference.
};

// foo.cpp
#include "bar.h"
#include "foo.h"

Поэтому, когда это возможно, используйте предварительные объявления в классах. Если ваша программа просто имеет функции (с заголовочными файлами ho), то предоставление прототипов в начале - это просто вопрос стиля. Это было бы так или иначе, если бы файл заголовка присутствовал в обычной программе с заголовком, который имеет только функции.

12 голосов
/ 21 января 2011

Поскольку C ++ анализируется сверху вниз, компилятору необходимо знать о вещах, прежде чем они будут использованы. Итак, когда вы ссылаетесь:

int add( int x, int y )

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

Итак, « Форвардная декларация » - это то, что написано на банке. Он объявляет что-то перед использованием.

Обычно вы включаете предварительные объявления в заголовочный файл, а затем включаете этот заголовочный файл так же, как включается iostream .

10 голосов
/ 21 января 2011

Термин " предварительное объявление " в C ++ в основном используется только для объявлений класса . См. (Конец) этот ответ , почему «прямое объявление» класса действительно является простым объявлением класса с причудливым именем.

Другими словами, «вперед» просто добавляет балласт к термину, так как любое объявление может рассматриваться как прямое, поскольку оно объявляет некоторый идентификатор до используемый.

(Что касается декларации в отличие от определения , снова см. В чем разница между определение и декларация? )

2 голосов
/ 21 января 2011

Когда компилятор видит add(3, 4), он должен знать, что это значит. С помощью предварительного объявления вы в основном сообщаете компилятору, что add - это функция, которая принимает два целых числа и возвращает int. Это важная информация для компилятора, потому что он должен поместить 4 и 5 в правильном представлении в стек и должен знать, какого типа вещь, возвращаемая add, является.

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

1 голос
/ 21 января 2011

одно быстрое дополнение относительно: обычно вы помещаете эти прямые ссылки в заголовочный файл, принадлежащий файлу .c (pp), где реализована функция / переменная и т. Д.в вашем примере это будет выглядеть так: add.h:

extern int add(int a, int b);

ключевое слово extern указывает, что функция фактически объявлена ​​во внешнем файле (также может быть библиотекой и т. д.).Ваш main.c будет выглядеть так:

#include 
#include "add.h"

int main()
{
.
.
.

1 голос
/ 21 января 2011
int add(int x, int y); // forward declaration using function prototype

Можете ли вы объяснить «предварительную декларацию» более подробно?В чем проблема, если мы используем ее в функции main ()?

Это то же самое, что и #include"add.h".Если вы знаете, препроцессор разворачивает файл, который вы упомянули в #include, в файл .cpp, где вы пишете директиву #include.Это означает, что если вы напишите #include"add.h", вы получите то же самое, как если бы вы делали «предварительное объявление».

Я предполагаю, что add.h имеет следующую строку:

int add(int x, int y); 
0 голосов
/ 21 января 2011

Одна проблема в том, что компилятор не знает, какое значение доставляет ваша функция; is предполагает, что функция возвращает int в этом случае, но это может быть как правильно, так и неправильно. Другая проблема заключается в том, что компилятор не знает, какого рода аргументы ожидает ваша функция, и не может предупредить вас, если вы передаете значения неправильного типа. Существуют специальные правила «продвижения», которые применяются при передаче, скажем, значений с плавающей запятой в необъявленную функцию (компилятор должен расширить их до типа double), что часто не соответствует ожиданиям функции, что приводит к трудностям поиска ошибок во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...