Объединение javascript файлов
Эпоха тёплого лампового WEB 1.0 давно прошла, и в последнее время мы имеем дело со страницами, которые кишат так называемой динамичностью. Динамичность может быть обеспечена при помощи JavaScript, VbScript и плагинами вроде Java, Flash, Silverlight. В этой статье я хочу затронуть одну из возможных оптимизаций web-сайта — объединение всех javascript файлов в один.
Зачем?
- Повышение скорости загрузки страницы.
- Снижение нагрузки на сервер.
Касательно снижения нагрузки на сервер — всё сложнее. Снижение числа запросов в любом случае улучшает ситуацию, но вот насколько — я сказать затрудняюсь, т.к. я не админ. Я полагаю, что для снижения нагрузки можно найти массу более простых и действенных решений. Возможно, это, так называемая, экономия спичек на фоне пожара. Но в качестве побочного эффекта — сгодится.
- Обернуть все файлы анонимными функциями, которые нужно будет вызывать единожды, по мере необходимости. Либо писать модульный код, где каждый файл может содержать 1 или несколько модулей, которые сами по себе не запускаются.
- Весь код каждого файла поместить в строку, которую eval-ить по первому требованию.
Но у меня не было выбора, т.к. используемый движок кишит кодом «сомнительного качества». Такой код я не могу обернуть в анонимную функцию, потому, что:
Если обернуть оба файла в анонимные функции и после этого выполнить, то мы получим ошибку — браузер не сможет найти some. Причина кроется в том, что полученный код:
Вовсе не приводит к window.some !== undefined; Функция some определяется в области видимости (scope) анонимной функции, а вовсе не window, как это было бы, если бы она была определена в отдельном файле. Решение этой проблемы нашлось в jQuery. Дело в том, что выполнить javascript-код в глобальной области видимости можно используя:
В зависимости от браузера мы вызываем либо execScript, либо запускаем привычный нам eval, задавая ему this равным window. Такой подход используется в jQuery начиная с версии 1.6. В более ранних версиях создавался тег script, в который помещался нужный код, и этот скрипт прикреплялся к документу.
Сжатие и обфускация
Параллельно сборке всех файлов в список мы можем над ними поиздеваться. Во первых их можно сжать, во вторых испортить их читабельность. Для этого можно воспользоваться YUI Compressor-ом или любым его аналогом. В конечном итоге мы получаем несколько меньше кода без форматирования (отступы, лишние пробелы, укороченные имена локальных переменных и пр.), сжатого в одну строку.
Компоновка
- Пробегаем по списку файлов (можно воспользоваться маской, к примеру: js/*.js).
- Запоминаем дату изменения файла.
- Сверяем её с датой создания уже сжатого файла, если таковой имеется.
- Если файл обновлён, или же сжатой копии нет вовсе — сжимаем и сохраняем в отдельном каталоге (либо используем префиксы, например: min_#
. - Пробегаем по списку сжатых файлов, поочерёдно добавляя их содержимое в массив.
- Сохраняем результат в «итоговый единый javascript-файл»
Отладка
Жизнь программиста была бы прекрасна, если бы не многочисленные баги, которые имеют привычку появляться не вовремя и хорошо прятаться. Тут наша с вами затея терпит крах по всем фронтам. Наш код нечитаем, firebug на нём виснет, и ошибки указывают невесть куда. К тому же большинство переменных имеют вид a, b, c. На помощь к нам приходит Сhrome. Дело в том, что он умеет «де-обфусцировать» код до вполне читабельного состояния (контекстное меню во вкладке Scripts). Например:
Результат весьма далёк от оригинала, но такое уже можно хотя бы прочитать. К сожалению есть некоторые проблемы с постановкой точек останова и их срабатыванием. Но на безрыбье и рак рыба. Жить можно.
Финальный штрих
Если в конец кода, который будет пропущен через eval добавить конструкцию /* //@ sourceURL=#
Использоване
Локально работать с «единым файлом» чертовски неудобно, поэтому можно написать примерно такой велосипед:
Разумеется, вариантов реализации подключения скрипта может быть множество. Да и этот можно улучшить. Например, поставив движок сайта на «событийную основу». Т.е. выполнять какой-либо код только тогда, когда выполнился ряд условий, например: были загружены все требуемые модули.
Как склеить (сшить) два кода Python вместе?
Мне интересно, как лучше всего собрать коды из разных файлов Python в новый файл Python. Допустим, первый файл 1.py будет состоять из:
И еще один файл 2.py состоит из:
Теперь предположим, что у вас есть конечный файл final.py , в который вы хотите, чтобы все коды из 1.py и 2.py копировались в него слово в слово в определенном порядке операций, где он выглядит, как показано ниже , Обратите внимание, что я понимаю, что мы можем использовать функцию импорта в Python, но в этом случае я бы хотел, чтобы весь текст некоторых определений был скопирован в новый код Python. Другими словами, склейте коды из разных файлов, чтобы создать новый файл.
Перефразируйте выше: что, если файл 1 имеет 100 определений, а файл 2 имеет 100 определений, но я хочу, чтобы из каждого файла были скопированы конкретные файлы в файл 3 с полным текстом и синтаксисом в указанном порядке.
Урок №20. Многофайловые программы
Как только программы становятся больше, их следует разбивать на несколько файлов (в целях удобства и улучшения функциональности). Одним из преимуществ использования IDE является легкость в работе с n-ным количеством файлов. Мы уже знаем, как создавать и компилировать однофайловые проекты, добавление новых файлов не составит труда.
Многофайловые проекты в Visual Studio
В Visual Studio щелкните правой кнопкой мыши по имени вашего проекта в «Обозревателе решений» , затем «Добавить» > «Создать элемент. » :
Во всплывающем диалоговом окне выберите тип файла, укажите его имя, расположение, а затем нажмите «Добавить» :
Также вы можете добавлять файлы к вашему проекту через «Проект» > «Добавить новый элемент. » :
Многофайловые проекты в Code::Blocks
В Code::Blocks перейдите в «File» > «New» > «File. » :
Затем выберите «C/C++ source» и нажмите «Go» :
Затем «Next» (этого окна может и не быть):
Затем «C++» и опять «Next» :
Затем укажите имя нового файла (не забудьте расширение .cpp) и его расположение (нажмите на троеточие и выберите путь). Убедитесь, что поставлены все три галочки (они отвечают за конфигурации сборки). Затем нажмите «Finish» :
Готово! Файл добавлен.
Многофайловые проекты в GCC/G++
В командной строке вам нужно будет создать файл, указать его имя и подключить к компиляции, например:
g++ main.cpp add.cpp -o main
(где main.cpp и add.cpp — это имена файлов с кодом, а main — это имя файла-результата)
Пример многофайловой программы
Рассмотрим следующую программу, которая состоит из двух файлов.
Попробуйте запустить эту программу. Она не скомпилируется, вы получите следующую ошибку:
add: идентификатор не найден
При компиляции кода, компилятор не знает о существовании функций, которые находятся в других файлах. Это сделано специально, чтобы функции и переменные с одинаковыми именами, но в разных файлах, не вызывали конфликт имен.
Тем не менее, в данном случае, мы хотим, чтобы main.cpp знал (и использовал) функцию аdd(), которая находится в add.cpp. Для предоставления доступа main.cpp к функциям add.cpp, нам нужно использовать предварительное объявление:
Теперь, когда компилятор будет компилировать main.cpp, он будет знать, что такое add(). Попробуйте запустить эту программу еще раз.
Что-то пошло не так!
Есть много вещей, которые могут пойти не так, особенно, если вы это делаете в первый раз. Главное — не паниковать:
Пункт №1: Если вы получили ошибку от компилятора, что функция add() не определена в main(), то, скорее всего, вы забыли записать предварительное объявление функции add() в main.cpp.
Пункт №2: Если вы получили следующую ошибку от линкера:
unresolved external symbol «int __cdecl add(int,int)» (?add@@YAHHH@Z) referenced in function _main
то возможных решений есть несколько:
a) Cкорее всего, add.cpp некорректно добавлен в ваш проект. Если вы используете Visual Studio или Code::Blocks, то вы должны увидеть add.cpp в «Обозревателе решений» в списке файлов вашего проекта или в панели проекта IDE. Если добавленного файла нет, то щелкните правой кнопкой мыши по вашему проекту и добавьте файл, как это показано выше, а затем повторите попытку компиляции вашего проекта.
б) Вполне возможно, что вы добавили add.cpp к другому проекту.
в) Вполне возможно, что добавленный файл не подключен к компиляции/линкингу. Щелкните правой кнопкой мыши по имени вашего добавленного файла и выберите «Свойства» :
Убедитесь, что пункт «Исключен из сборки» оставлен пустым или выбрано значение «Нет» :
Пункт №3: Не следует писать следующую строку в main.cpp:
Наличие этой строки приведет к тому, что компилятор вставит всё содержимое add.cpp непосредственно в main.cpp вместо того, чтобы рассматривать эти файлы как отдельные.
Разделите следующую программу на два файла (main.cpp и input.cpp): main.cpp должен содержать функцию main(), а input.cpp — функцию getInteger().
Помните, что для функции getInteger() вам понадобится предварительное объявление в main.cpp.
Ответ
Урок №19. Прототип функции и Предварительное объявление
Комментариев: 56
То есть, при компиляции только проверяется исходный код на соответствие синтаксису с++, а линкинг — связывает не только разные объектные файлы, но и функции (классы, структуры, перечисления) в одном файле?
Начал изучать С++ примерно месяц назад по другому курсу. Простые програмки без организации проекта, просто как файл .cpp
Прочитал текущие темы (22, 23) и задумался, а стоит ли сейчас заострять внимание на проетах, организации многофайловых программ, заголовках…. или достаточно просто знать об этом и при необходимости изучить.
Но всё же сегодня попробовал именно создать проект с подключениями файлов и заголовка (тема 23).
Всё получилось, спасибо огромное за объяснение!
Но вот обратил внимание на пункт №3: Не следует писать следующую строку в main.cpp: #include "add.cpp"
Собственно говоря можно писать (подключать и таким образом), но только если тренируетесь/отрабатываете синтаксис не в проекте, а на изолированных файлах .cpp
Возможно это приведет к привыканию к неправильному стилю программирования?
Но наверное для обучения и если программка простая или тестируете новый синтаксис, то возможно полезно закидывать все интересные получившиеся подпрограммки в отдельный файлик "mylib.cpp" для коллекции и его линковать не через проект, а простым
#include "mylib.cpp"
Так у вас всегда под ругой будет как-бы конспект лекций.
(на самом деле даже не обязательно чтобы файл имел расширение cpp, можно назвать даже .txt хотя в этом и нет смысла)
Всё это писал для того чтобы ещё раз самому себе уяснить различия линкера проекта и директивы #include "имя_файла"
Нашел ещё один важный момент… Либо я не разобрался, но на сколько понял, режим debug возможен только в проекте.
Для отдельного файла-программки отладка не работает?
Два-три файла, это конечно, хорошо. Но приходит время когда человек начинает косится в сторону Гитхаба, а там уже этих файлов сотни, в проектах.
Найти файл с "main()" не составит труда, но остальные сотни, в ручную добавлять нереально. Попробовал добавить сами папки в настройках проекта, VS при компиляции выдала сотни ошибок.
Как "запихнуть" проект СКАЧАННЫЙ (не работа непосредственно с Гитхабом) в ВизуалСтудию. Как пример можно взять "MPC player" (https://github.com/mpc-hc/mpc-hc)
Просто компиляция проекта не интересует, нужно найти определенное "место" в коде, для его изменения.
Возможно, вы уже разобрались, но кому-нибудь другому может пригодится на будущее ответ.
Вообще, тут не всё так просто. Я бы следовал примерно такому алгоритму, когда надо было бы работать с другим репозиторием.
1. Во-первых, надо почитать файл readme.md. Зачастую там есть инструкция, как надо всё установить, чтобы можно было работать с проектом.
2. Если же в readme ничего нет, то стоит поискать файл с расширением *.sln — это файл, который является самым главным, так сказать. Солюшен (не помню, как на русском правильно будет) хранит информацию о проектах (а их может быть несколько внутри одного солюшена) и разную другую важную информацию. Если таких файлов несколько, надо выбирать тот, который максимально похож на название приложения. В случае с mp-hc — он так и называется mp.hc (хотя есть и еще парочка других). Можно кликнуть по нему два раза и Visual Studio сама откроет всё как надо. Когда она всё откроет, вы увидите уже добавленные, структурированные проекты с файлами и можно приступать к работе.
3. Если же солюшенов нет, то возможно на проекте используется какая-либо система автоматизации сборки, например CMake. В таком случае, надо запускать уже эту систему и она вам сгенерирует нужные файлы для работы с проектом, в том числе и солюшен.
Я так понимаю, что при включении в главный файл заголовочный файл библиотеки, которые были в заголовочном файле копируются, что только прибавляет веса главному файлу или все же копирует лишь функции?
Здравствуйте. А если у меня функция getInteger() будет определена в 2 и более файлах, то как файл с функцией main() поймет какой именно getInteger() мне требуется?
Я методом тыка уже определил что будет ошибка )
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Если вы подключите больше одного файла с определением одной и той же функции в проект, то получите ошибку компиляции. Функция определяется 1 раз в 1 файле.
Юрий, у меня проблема, здраствуйте. У меня консоль сразу же закрывается после выполнения задачи, но раньше все было ОК. Как исправить?
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Смотрите решение в уроке №7.
Спасибо, Юрий! Замечательные уроки! Получаю удовольствие.. Возникают вопросы.. ищу на сайтах.. нахожу.. Будут посерьёзнее, намерен обращаться, если позволите!
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Начал изучать С++ для создания игр на Unreal, но там все как то сложно.
Случайно наткнулся на ваши уроки, это же просто чудо какое то)
Спасибо за ваш труд.
Пожалуй накидаю плюсиков в карму автору)
Мне кажется стоило также указать что хорошим тоном является написать функцию в <name>.cpp, сделать её объявление в <name>.h, после чего подключить <name.h> в основном файле.
Вместо того чтобы писать объявления функций напрямую.
Можете об этом рассказать поподробнее, пожалуйста?
Первый файл с функцией int getInteger():
Может кто-нибудь знает как это все осуществить при помощи Xcode, или статью какую-нибудь по обучению использования этой среды.
А что сложного в Xcode? Всё по аналогии с VS, но со своими особенностями (если уж на то пошло). Можно нагуглить на крайний случай.
Здравствуйте Юрий! Благодарю вас за интересные и познавательные уроки! Стараюсь изучить все тонкости. Вы пожалуйста не бросайте этот сайт. Я думаю что развитие нужно направить в сторону 3D. Как вы на это смотрите?
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Здравствуйте я пишу в андроид с помощью Dcoder и хотел пройти тест в конце этого урока но у меня выдаёт ошибку.
/tmp/cc5DeaBV.o: In function main':
source.cpp:(.text.startup+0x19): undefined reference to getInteger()'
source.cpp:(.text.startup+0x20): undefined reference to `getInteger()'
collect2: error: ld returned 1 exit status
В VS 2019 все компилируется и без #include "pch.h". А если как раз использую #include "pch.h"выдает ошибки "не удается открыть источник файл "pch.h" "и "не удается открыть файл включение: pch.h: No such file or directory " стоит ли пытаться это исправить или оставить как есть?
Сначала разделил верно, потом думал насчет iostream в инпуте, вот только зря добавил в саму функцию инициализацию переменной.
Взглянул в ответ — понял сразу ошибку.
Спасибо за уроки, хороший язык оказывается.
Срочно нужна помощь.
Начал программировать на андроид с приложения Dcoder(c++: GCC compiler 6.3 (знаю, что это неудобно и т. д., но лучше так, чем никак).
С этим уроком получилась зиминка из-за моего не состояния найти способ, как связать несколько файлов именно в этом приложении. Если кто-то что с этим делать, тогда пишите (буду очень благодарен). Автору спасибо за уроки. Всё очень доходчиво написано.
Большое спасибо автору, читаю уроки — не могу оторваться (давно хотел изучить язык С++).
У меня такой вопрос: почему при определении (и описании соответственно) функции getInteger() мы оставили скобки пустыми, (ведь мы задействуем 1 переменную и возвращаем ее значение)?
Дык мы ж не собираемся в энту функцию никаких значений всовывать — вот и параметры в ней нече заявлять!
Все трудности с ошибками прошел, разобрался, помогли подсказки. Осталось не понятным — '\n', что оно означает?
Это символ переноса строки, такая управляющая последовательность(и гуглите )
Какая же чудесная находка для меня эти уроки) Автору огромное спасибо за проделанную работу за перевод и адаптацию
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Здравствуйте. у меня возникла небольшая заминка. Не могли бы вы помочь мне в её разрешении?
Я создал оба файла, как объяснялось в задании, добавил add.cpp (Добавить новый элемент —> файл.cpp). И в общем всё работает, но в файле add.cpp возникла ошибка: "IntelliSense: не удаётся открыть источник файл "stdafx.h" ".
Помогите разобраться.
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Если у вас Visual Studio 2017, то вам нужно #include <pch.h> вместо stdafx.h.
Здравствуйте. Как добавлять файлы в Visual Studio 2017 если он на русском. Просто не очень понятно, переводчик не помогает.
не получается разделить функцию main() на 2 файла (без возвратных функций)…это вообще возможно?)
Данила, функция это тот же блок, т.е. некий цельный кусок, который из своих недр выдаёт на гора некое одно значение, например — превращает в число результат неких математических вычислений. Это значение можно впихнуть в другой блок, который, используя его произведёт также одно значение, но смысл последнего м.б. другим, например — выведется какая-нибудь надпись. Смыслы закладывает кодер (как и всё остальное). А функцию main() — вообще воспринимай как оглавление книги, в котором записаны вызовы всех функций, которые, собственно, и выполняют поставленные кодером задачи.
После беглого просмотра десятка-другого уроков понял, что уроки-то неплохие (не считая некоторых грамматических ошибок), да вот возникло стойкое чувство deja vu. И правда, как оказалось, этот курс — просто перевод с минимальными изменениями курса, представленного на сайте LearnCpp.com.
И хотя я искренне считаю, что людям, не владеющим английским языком, в программировании категорически делать нечего, все же ничего не имею против переводов хороших источников.
Но только если автор честно пишет, что это не его детище, а просто перевод. Я не смог найти никакого упоминания или ссылки на оригинал. Мало того, вы, Юрий, еще и пытаетесь денег на этом заработать, продавая книжку с этим добром. Если я ошибся, прошу прощения, но если нет, то это очень грустно.
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
А где же, Александр, я писал, что это моё детище? Моё детище — это сайт Ravesli, а уроки С++ — это перевод. На странице "Уроки С++" пролистайте до конца — там источник уроков.
Насчет денег — да, пытаюсь. Только перевод, а не оригинал, да ещё и с "минимальными изменениями". Уроки по С++ останутся бесплатными на сайте до тех пор, пока я буду поддерживать этот сайт, а всё что дополнительно — то это уже на моё усмотрение.
Тогда я ошибся, и это чудесно =) Прошу прощения. Я не заметил ссылки, привык их искать где-то в аннотации.
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Ничего, без проблем
Добрый день! При попытке добавить в функцию, которая находится в отдельном файле, "stdafx.h" компилятор выдаёт ошибку: не удаётся открыть файл включения stdafx.h: No such file or directory. Без stdafx.h всё нормально работает. Visual Studio 2015.
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Скорее всего у вас отключены предварительно скомпилированные заголовки, коим и является stdafx.h. Работает без него — хорошо, работает с ним — еще лучше.
Сегодня читала статью с телефона. Очень удобный сайт)
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Можно и с телефона, и с планшета, и с компьютера, и с ноутбука — лишь бы желание было, а возможность предоставим
Когда пишу файл add.cpp постоянно не идёт,это ж линкер мешает по моему. И что ему не так,когда твою программу скопировал,также всё:
Ошибка LNK2019 ссылка на неразрешенный внешний символ _main в функции "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) add.cpp 1
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
Вы сделали всё как в уроке? Правильно добавили файл? К тому проекту, что нужно?
В уроке есть отдельный пункт, где рассказываются возможные ошибки — ваша ошибка — это та же, что и в уроке. Там есть возможные решения.
Почему в VS нужно обязательно подключать библиотеку stdafx.h? Раньше, когда я учился по видеоурокам, я писал программы и без ее подключения. Но конечно есть одно но, я тогда немного изменил параметры создания проекта: поставил галочку на "Empty project" и удалил галочку на "Security Development Lifecycle (SDL) checks ". Может быть это как-то повлияло?
alt=»Avatar photo» width=»50″ height=»50″ />Юрий :
stdafx.h — это реализация механизма «предварительно скомпилированных заголовков», который используется для ускорения процесса сборки проектов. В этом файле содержатся вызовы других библиотек и заголовочных файлов, которые нужны для корректного выполнения ваших программ. Т.е. чтобы не прописывать дополнительно подключение определенных библиотек и заголовочных файлов — эти вызовы записали в одном файле и подключают только stdafx.h (1 строчка кода), а не прописывают дополнительно 20 строчек #include. Но от этого можно увидеть толк и почувствовать реальную пользу, когда ваши проекты состоят минимум из десятка файлов.
Вы можете отключить использование предварительно скомпилированных заголовков в Visual-е в настройках и тогда вам не нужно будет прописывать в каждом файле строчку с подключением stdafx.h. Если вы будете писать простенькие программы на один-два файла, то вам что с stdafx.h, что без него — разница небольшая. Если же будете писать сложные проекты с десятками файлов, то отключив использование stdafx.h, вам придется вручную дополнительно прописывать в каждом файле подключение требуемой библиотеки или заголовочного файла, вызовы которых до этого находились в файле stdafx.h.
Отключается stdafx.h в Visual Studio так: Project (Проект) -> Properties (Свойства) -> C/C++ -> Precompiled Headers (Предварительно скомпилированные заголовки) и выбираете пункт «Not using Precompiled Files» (сокр. «Не использовать»).
По поводу того, как повлияли галочки возле «Empty Project» и «Security Development Lifecycle checks» на отключение механизма «предварительно скомпилированных заголовков» я ничего не могу сказать.
Зачем писать названия в VS (например Source Files) на английском, если в VS есть русский язык?
Как склеить код из разных файлов
В статье вы научитесь создавать программу из нескольких файлов
Содержание
Объявления и определения
Рассмотрим задачу: надо вывести числа от N до 1, при этом для нечётных чисел надо писать odd $
Ради интереса решим задачу с помощью рекурсии:
Увы программа не компилируется. В C++ каждая функция должна быть объявлена или определена до первого использования, но в нашем случае printEvenRecursive использует printOddRecursive, а printOddRecursive использует printEvenRecursive! Мы не можем поместить определение одной функции выше другой так, чтобы каждая функция была объявлена перед использованием.
Но кроме определений функций в C++ есть объявления функций
- Объявление (declaration) — это конструкция, которая зарезервирует идентификатор и опишет для компилятора его тип, но не раскроет деталей работы объявленной сущности.
- Определение (definition) — это конструкция, которая не только зарезервирует идентификатор, но и раскроет реализацию связанной с ним сущности
Объявление функции похоже на определение функции, только вместо тела стоит точка с запятой:
Теперь мы можем переписать рекурсивный пример, просто разместив определения обеих функций в начале файла.
Хорошим стилем считается заранее и в одном месте писать объявления всех функций, кроме main. Вы можете писать объявления в начале cpp-файла или в заголовке. О заголовках читайте ниже.
Пишем свой заголовок
В C/C++ заголовок — это файл с расширением *.h или *.hpp , который включается в другие файлы директивой #include .
- Заголовок может иметь и другое расширение файла, но не стоит нарушать джентльменских соглашений: используйте h или hpp
- В заголовке пишут только объявления функций, а все определения можно и нужно помещать в cpp-файл
Создайте каталог, и разместите в нём файл print.h , в котором будут объявления функций. Скопируйте туда код, приведённый ниже.
Мы могли бы убрать из заголовка printEvenRecursive и printOddRecursive, сделав его чище, но сейчас для примера оставим.
Запомните правила хорошего тона:
- в начале заголовка пишите #pragma once , чтобы не получить ошибки компиляции при ромбовидном include, когда одни и те же сущности объявляются несколько раз (подробнее о pragma once и define guards…)
- заголовок должен быть чистым, ведь он повторно используется многими файлами проекта
- поэтому не пишите в заголовке using namespace . ; , иначе вы замусорите глобальное пространство имён целого проекта
Теперь создайте файл print.cpp , в котором будут реализованы функции из заголовка print.h . Скопируйте туда код, расположенный ниже.
Обратите внимание: #include <iostream> находится в cpp-файле, а не в заголовке, потому что экспортированные из iostream сущности нужны только в реализации функций, но не нужны в объявлении.
В конце создайте файл main.cpp и скопируйте в него код:
Компоновка программы из нескольких файлов
Собирая программу из одного файла с помощью g++, вы на деле выполняли одним махом два действия:
- компиляцию, в ходе которой исходный текст файла превращается в логическую модель (AST) и затем превращается в объектный код, в котором машинные коды смешаны со ссылками на внешние функции
- компоновку, в ходе все внешние ссылки на функции заменяются на машинный код либо превращаются в ссылки на динамические библиотеки (dll/so, также известны как shared libraries)
Сейчас эти же действия мы выполним раздельно. Отройте терминал и введите последовательно две команды:
Если код в обоих cpp-файлах синтаксически правилен, то компилятор создаст два файла: main.o и print.o . Эти файлы называют объектными файлами (object files). Именно они содержат машинный код, смешанный со ссылками на внешние функции.
Вы можете дизассемблировать эти файлы, чтобы посмотреть, во что компилятор превратил ваш код. Для этого выполните команду objdump -d main.o .
Теперь мы вызовем g++ для компоновки объектных файлов. На выходе мы получим исполняемый файл print_executable.exe
На деле компилятор не будет компоновать: он передаст эту задачу утилите ld. Вызывать утилиту ld вручную мы не станем, потому что потребуются дополнительные флаги, которые включают компоновку со стандартной библиотекой C++.
Компоновка программы в CMake
CMake прячет факт раздельной компиляции файлов. Чтобы в CMake скомпоновать программу, состоящую из нескольких частей, просто перечислите эти файлы в add_executable.
Удалите файл print_recursive.exe :
Создайте файл CMakeFiles.txt с одной строкой:
Теперь выполните конфигурирование и сборку программы:
Мы почти закончили! Остался только один вопрос: почему в add_executable мы указали заголовок print.h , если он всё равно не компилируется сам по себе? Дело в том, что при любых изменениях в коде заголовка print.h вся программа должна быть пересобрана, но файл print.h сам по себе не компилируется. Добавление print.h в список исходников в CMake позволяет CMake следить за датой и временем модификации заголовка, чтобы решить, надо ли повторно собирать проект из-за изменений в заголовках.