TLDR: см. Последний абзац этого вопроса.
Я студент факультета компьютерных наук, пытаюсь закончить текст магистерской диссертации о создании транспилятора (пример).
Теперь для этой магистерской диссертации, часть моего текста посвящена сравнению задействованных языков. Одним из языков является C ++.
Теперь я пытаюсь объяснить разницу в семантике импорта / включения и историческую причину, почему C ++ сделал это таким образом. Я знаю, как это работает в C / C ++, поэтому мне не нужно техническое объяснение.
Тщательно изучив Google и Stackoverflow, я предложил несколько пояснений к stackoverflow и другие ссылки на эту тему:
Зачем нужны предварительные декларации?
Что такое предварительные объявления в C ++?
Зачем C ++ нужен отдельный заголовочный файл?
http://en.wikipedia.org/wiki/Include_directive
http://www.cplusplus.com/forum/articles/10627/
https://softwareengineering.stackexchange.com/questions/180904/are-header-files-actually-good
http://en.wikipedia.org/wiki/One-pass_compiler
Почему заголовочные файлы и файлы .cpp в C ++?
И, наконец, последняя, но не менее важная книга Бьярна Страуструпа «Дизайн и эволюция C ++ (1994)» (стр. 34 — 35).
Если я правильно понимаю, этот способ импорта / включения пришел из C и произошел по следующим причинам:
Компьютеры не были такими быстрыми, поэтому предпочтительным был компилятор One Pass. Единственный способ, которым это было возможно, — это принудительное использование декларации перед использованием идиомы. Это связано с тем, что C и C ++ являются языками программирования, которые имеют контекстно-зависимую грамматику: им нужны правильные символы, которые должны быть определены в таблице символов, чтобы устранить неоднозначность некоторых правил. Это противоречит современным компиляторам: в настоящее время обычно выполняется первый проход для построения таблицы символов, а иногда (в случае, если язык имеет контекстно-свободную грамматику) таблица символов не требуется на этапе синтаксического анализа, потому что нет двусмысленностей, которые нужно разрешить ,
Память была очень ограниченной и дорогой в те дни. Поэтому на большинстве компьютеров невозможно было сохранить всю таблицу символов в памяти. Вот почему C позволяет программистам объявлять прототипы функций и глобальные переменные, которые им действительно необходимы. Заголовки были созданы, чтобы позволить разработчикам сохранять эти объявления централизованными, чтобы их можно было легко повторно использовать в модулях, которым требовались эти символы.
Заголовочные файлы были полезным способом абстрагировать интерфейс от реализации
C ++ пытался установить обратную совместимость с программным обеспечением и программными библиотеками, написанными на C. Более важно: они фактически использовали для переноса в C (CFront), а затем использовали компилятор C для компиляции кода в машинный код. Это также позволило им с самого начала скомпилировать для множества различных платформ, поскольку каждая из этих платформ уже имела компилятор C и компоновщик C.
Вышеприведенное было иллюстрацией того, что я обнаружил, выполнив поиск в первую очередь;) Проблема в том, что я не могу найти подходящую ссылку на исторические причины для этой стратегии включения, кроме как здесь, на Stackoverflow. И я очень сомневаюсь, что мой университет будет счастлив со ссылкой на стек-поток. Самым близким из приведенных мной является справочник «Дизайн и эволюция C ++», но в нем не упоминаются аппаратные ограничения, являющиеся причиной стратегии включения. Я думаю, этого следовало ожидать, потому что дизайн этой функции исходил от C. Проблема в том, что я пока не нашел хорошего источника, который бы описывал это проектное решение на C, желательно с учетом аппаратных ограничений.
Кто-нибудь может указать мне в правильном направлении?
Спасибо!
Вы правы, потому что C ++ делает это именно потому, что
С сделал это так. Причина, по которой С сделал это, также основана
в истории; в самом начале (Б) не было
деклараций. Если вы написали f()
то компилятор предположил
тот f
была функция где-то. Который вернул слово, так как
все в Б было словом; не было типов. Когда C был
изобрел (чтобы добавить типы, так как все слово не очень
эффективный с байт-адресными машинами), основной принцип
не изменился, за исключением того, что функция должна была возвращать
int
(и принимать аргументы того типа, который вы ему дали). Если оно
не вернулся int
, то вам пришлось переслать объявить это с
тип возврата. В первые дни C это было не редкостью, чтобы увидеть
приложения, которые не использовали include
и который просто
переименовано, например char* malloc()
в каждом исходном файле, который использовал
malloc
, Препроцессор был разработан, чтобы избежать необходимости
повторить одно и то же несколько раз, и в самом начале,
его самая важная особенность была, вероятно, #define
, (В начале C,
все функции в <ctype.h>
и символьный IO
в <stdio.h>
были макросы.)
Что касается того, почему декларация должна предшествовать использованию: основной
причина, несомненно, потому что если бы это не так, компилятор
принять неявное объявление (функция, возвращающая int
, так далее.).
И в то время, компиляторы, как правило, один проход, по крайней мере, для
разбор; это считалось слишком сложным, чтобы вернуться в
«исправить» предположение, которое уже было сделано.
Конечно, в C ++ язык не так сильно ограничен
этот; C ++ всегда требовал объявления функций для
например, и в определенных контекстах (в функциях-членах класса, для
пример), не требует, чтобы объявление предшествовало использованию.
(Как правило, однако, я бы рассмотрел в функциях членов класса
быть несоответствующим, избегать по причинам читабельности.
Дело в том, что определения функций должен быть в классе в
Java является основной причиной не использовать этот язык в целом
проектов.)