[БЕЗ_ЗВУКА] В предыдущем видео мы с вами разделили один большой монолитный файл с решением и тестами для задачи «синонимы» на несколько файлов. И мы сказали, что несмотря на кажущуюся корректность в выполнении этих операций, на самом деле, у нас есть немало проблем. И давайте посмотрим, какие это проблемы. Во-первых, давайте для примера закомментируем вот этот вот include — подключение библиотеки set — и скомпилируем нашу программу. И она не компилируется, и мы видим, что не компилируется с сообщением, что функция AddSynonyms где-то была незадекларирована, она была незадекларирована в нашем файле с решениями, хотя вроде бы всего лишь убрали заголовок set. И на самом деле причина, по которой наша программа не компилируется другая, но из сообщения об ошибках мы это не можем легко понять. Так что вот такая вот проблема — мы перестали включать заголовочный файл set в файл с решением, а где-то в файле tests.h у нас вдруг перестало компилироваться обращение к функции AddSynonyms. Хорошо. Вернем #include <set>, и давайте, допустим, такую еще вещь посмотрим. Вот у нас есть #include <string> и #include <map>. Мы можем взять поменять местами эти два include. Вот мы поменяли их, и наша программа компилируется, все нормально. Но если мы возьмем и #include "tests" сделаем не третьим, а вторым, и соберем нашу программу, то она опять не скомпилируется и опять с сообщением, что AddSynonyms был незадекларирован, вот эта вот функция в файле tests.h. Мы просто поменяли порядок include, и у нас программа перестала компилироваться. Получается, что наши файлы, вот эти отдельные, они какие-то не очень независимые. И мы поменяли порядок — у нас уже все перестало компилироваться. Хорошо, давайте вернем опять. И сделаем еще такое изменение. Вот у нас сейчас как-то некрасиво, у нас идет подключение стандартных файлов, потом написано using namespece std:, а потом идет подключение файлов нашего проекта. Как-то некрасиво, хочется, чтобы все include были вместе. И давайте мы возьмем и перенесем include наших проектных файлов в начало. Скомпилируем нашу программу, и она не скомпилируется, тут тоже много ошибок. И опять же AddSynonyms не был задекларирован, но теперь уже в файле coursera.cpp. В общем, явно что-то с нашим проектом не то — мы меняем порядок include, и у нас все разваливается. Явно что-то не так мы сделали, когда выносили код в отдельные файлы. И чтобы понять, почему так происходит, почему у нас проект оказался таким хрупким, давайте мы разберемся, как работает директива include, что она делает вот с тем файлом, который указан в ее аргументе. Директива include работает очень просто: если у нас в программе написано #include "file.h", то во время компиляции, первым шагом во время компиляции — эта директива берет file.h, берет его содержимое и вставляет в файл, в котором эта директива находится. То есть если вот мы переключимся обратно в Eclipse, то когда мы компилируем наш проект, то берется вот содержимое вот этого файла, tests.h, и прямо вставляется в файл в данном случае coursera.cpp. И после того как для всех директив include было выполнено это действие, содержимое файла было вставлено в целевой файл, вот этот большой файл, который получился после всех включений, вот он подается на вход компилятору и компилируется. Давайте посмотрим на примере, как это происходит. Вернемся в Eclipse, и у меня здесь специально заготовлен маленький проект, на котором нам будет проще посмотреть, как все как работает директива include. Мой маленький проект состоит всего из двух файлов. Есть функция main, которая в переменную k присваивает значение функции сумм от аргументов 3 и 4. И есть отдельный файл sum.h, в котором есть, собственно, наша функция Sum, которая считает сумму двух чисел. И файл sum.h подключается с помощью директивы include. Давайте мы скомпилируем наш код, убедимся, что наш маленький проект корректен, он компилируется. Итак, да, он компилируется. У нас есть только Warning, что мы не используем переменную k. Хорошо. Теперь давайте посмотрим, как же работает директива include. Для этого мы переключимся в консоль операционной системы, потому что в Eclipse напрямую это увидеть достаточно сложно. Итак, мы в консоли операционной системы, мы находимся в папке с вот эти вот проектом, который я только что показал. Давайте перейдем в папку с исходниками, и вот мы видим, что у нас мы видим вот, вот они исходники, которые я вам только что показал: основной файл и файл sum.h. И давайте вызовем вот такую команду компилятора: g++ -E — и передадим ей файл how_include_works.cpp. Когда мы вызываем компилятор, когда мы вызываем программу g++ с ключиком -E, это значит мы просим его не выполнить полную сборку проекта, а просто выполнить вот эту вот первую стадию, на которой выполняется вставка, на которой исполняются директивы include. Кстати, сразу замечу, что эта стадия называется pre-processing. И мы на выход получили, собственно, файл, который получается в результате препроцессинга. Вот этот вот файл, вот это вот, значит, этот вот текст, вот этот вот, который у нас получился — вот он как раз таки подается на вход компилятору, который уже его компилирует. И что мы здесь видим? Мы видим, что в этом файле у нас есть наша функция main, вот она функция main, а выше вставилась функция Sum из файла sum.h. А за символом решетки тут какие-то служебные строки, которые нужны уже компилятору, для того чтобы он делал свою работу. Таким образом, мы видим, что директива include работает именно так, как я вам сказал — она вставляет содержимое файла. Хорошо, давайте теперь перейдем к нашему большому проекту. Маленький проект пока закроем, он нам пока не нужен. Перейдем к нашему большому проекту, который у нас пока что не компилируется. Вернем все, как было, проверим, что он у нас компилируется. Отлично. И опять же вернемся в консоль операционной системы и посмотрим, как на большом нашем проекте работает препроцессинг. Я перехожу в папку с исходниками нашего большого проекта, вот они: coursera.cpp, synonyms.h, tests.h и testrunner.h. И делают здесь то же самое: g++ -E coursera.cpp. И результат препроцессинга я уже выведу в файл coursera.i. Программа наша выполнилась. И вот здесь вот у нас в Eclipse должен появиться этот файл, который мы только что создали. Вот он: coursera.i. Откроем его. Мы здесь видим какие-то странные письмена, которые на первый взгляд нам непонятны. Но что нас интересует в первую очередь — это размер этого файла. Посмотрите. Размер файла у нас получился 37981 строка. Вот такой вот огромный файл у нас получается, после того как отработали вот эти вот все директивы include. То есть содержимое стандартных модулей — string, set, vector и так далее, — оно точно так же было вставлено в файл с наших исходником. И вот мы видим здесь в конце нашего файла наше решение. Вот они наши тесты, значит, вот тесты. Вот функции, которые мы тестируем. Выше идет вот он тестовый фреймворк. То есть вот так вот они друг за дружкой были вставлены в наше решение. А перед ними мы здесь вот какие-то нелегко читаемые письмена — это, собственно, модули стандартной библиотеки. Теперь мы можем понять, почему у нас возникали проблемы с компиляцией. Когда мы, например, убирали set, #include <set>, вот так вот, то в получившемся файле после препроцессинга нигде не было никакой информации о том, что такое set. Нигде вот в нашем этом большом файле после препроцессинга не было написано, что set — это множество, какие у него есть операции. И поэтому у нас возникала ошибка компиляции вот этой вот части. Видите: даже сейчас Eclipse map подкрашивает как известный ему тип, а set он не подкрашивает, потому что он не знает, что это такое. И, собственно, у нас не компилировалась программа именно из-за этого. Какая у нас еще была проблема? У нас была проблема в том, что мы вот так вот переносили tests выше, чем файл synonyms, и у нас опять не компилировалось. Почему? А потому что функции в файле tests.h используют функцию, например AddSynonyms, а она, судя вот по этому порядку включения, включалась позже. И, соответственно, когда компилятор видел AddSynonyms, он не знал, что это такое. И наша компиляция не завершалась успешно. Наконец, когда мы переносили вот таким образом заголовки в начало нашей программы, то когда компилятор компилировал наши решения, он не видел ни одного определения стандартных объектов. Он не знал, что такое map, что такое string. И вот опять же мы это можем видеть вот здесь вот, в файле synonyms, у нас Eclipse перестал подкрашивать и map, и string — он больше не знает, что это такое, потому что определение этих типов идет после функции нашего фреймворка и нашего решения. Таким образом, в этом видео мы с вами узнали, как работает директива include, мы узнали, что такое препроцессинг, и увидели, какие проблемы есть в нашем проекте, после того как мы разбили его на файлы. В следующем видео мы эти проблемы начнем решать.