Урок №20. Многофайловые программы

  Юрий  | 

  |

  Обновл. 11 Сен 2021  | 

 183206

 ǀ   56 

Как только программы становятся больше, их следует разбивать на несколько файлов (в целях удобства и улучшения функциональности). Одним из преимуществ использования 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.cpp:

main.cpp:

Попробуйте запустить эту программу. Она не скомпилируется, вы получите следующую ошибку:

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.

Ответ

input.cpp:

main.cpp:

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (1 135 оценок, среднее: 4,88 из 5)
Загрузка...

Комментариев: 56

  1. Edman:

    То есть, при компиляции только проверяется исходный код на соответствие синтаксису с++, а линкинг — связывает не только разные объектные файлы, но и функции (классы, структуры, перечисления) в одном файле?

  2. Сергей:

    Начал изучать С++ примерно месяц назад по другому курсу. Простые програмки без организации проекта, просто как файл .cpp
    Прочитал текущие темы (22, 23) и задумался, а стоит ли сейчас заострять внимание на проетах, организации многофайловых программ, заголовках…. или достаточно просто знать об этом и при необходимости изучить.
    Но всё же сегодня попробовал именно создать проект с подключениями файлов и заголовка (тема 23).
    Всё получилось, спасибо огромное за объяснение!

    Но вот обратил внимание на пункт №3: Не следует писать следующую строку в main.cpp: #include "add.cpp"

    Собственно говоря можно писать (подключать и таким образом), но только если тренируетесь/отрабатываете синтаксис не в проекте, а на изолированных файлах .cpp
    Возможно это приведет к привыканию к неправильному стилю программирования?

    Но наверное для обучения и если программка простая или тестируете новый синтаксис, то возможно полезно закидывать все интересные получившиеся подпрограммки в отдельный файлик "mylib.cpp" для коллекции и его линковать не через проект, а простым
    #include "mylib.cpp"
    Так у вас всегда под ругой будет как-бы конспект лекций.
    (на самом деле даже не обязательно чтобы файл имел расширение cpp, можно назвать даже .txt хотя в этом и нет смысла)

    Всё это писал для того чтобы ещё раз самому себе уяснить различия линкера проекта и директивы #include "имя_файла"

    1. Сергей:

      Нашел ещё один важный момент… Либо я не разобрался, но на сколько понял, режим debug возможен только в проекте.
      Для отдельного файла-программки отладка не работает?

  3. Влад:

    Два-три файла, это конечно, хорошо. Но приходит время когда человек начинает косится в сторону Гитхаба, а там уже этих файлов сотни, в проектах.
    Найти файл с "main()" не составит труда, но остальные сотни, в ручную добавлять нереально. Попробовал добавить сами папки в настройках проекта, VS при компиляции выдала сотни ошибок.
    Как "запихнуть" проект СКАЧАННЫЙ (не работа непосредственно с Гитхабом) в ВизуалСтудию. Как пример можно взять "MPC player" (https://github.com/mpc-hc/mpc-hc)
    Просто компиляция проекта не интересует, нужно найти определенное "место" в коде, для его изменения.

    1. icelandic_one:

      Возможно, вы уже разобрались, но кому-нибудь другому может пригодится на будущее ответ.

      Вообще, тут не всё так просто. Я бы следовал примерно такому алгоритму, когда надо было бы работать с другим репозиторием.

      1. Во-первых, надо почитать файл readme.md. Зачастую там есть инструкция, как надо всё установить, чтобы можно было работать с проектом.

      2. Если же в readme ничего нет, то стоит поискать файл с расширением *.sln — это файл, который является самым главным, так сказать. Солюшен (не помню, как на русском правильно будет) хранит информацию о проектах (а их может быть несколько внутри одного солюшена) и разную другую важную информацию. Если таких файлов несколько, надо выбирать тот, который максимально похож на название приложения. В случае с mp-hc — он так и называется mp.hc (хотя есть и еще парочка других). Можно кликнуть по нему два раза и Visual Studio сама откроет всё как надо. Когда она всё откроет, вы увидите уже добавленные, структурированные проекты с файлами и можно приступать к работе.

      3. Если же солюшенов нет, то возможно на проекте используется какая-либо система автоматизации сборки, например CMake. В таком случае, надо запускать уже эту систему и она вам сгенерирует нужные файлы для работы с проектом, в том числе и солюшен.

  4. Тимур:

    Я так понимаю, что при включении в главный файл заголовочный файл библиотеки, которые были в заголовочном файле копируются, что только прибавляет веса главному файлу или все же копирует лишь функции?

  5. Андрей:

    Здравствуйте. А если у меня функция getInteger() будет определена в 2 и более файлах, то как файл с функцией main() поймет какой именно getInteger() мне требуется?

    1. Андрей:

      Я методом тыка уже определил что будет ошибка )

    2. Фото аватара Юрий:

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

  6. Пастор Егор:

    Юрий, у меня проблема, здраствуйте. У меня консоль сразу же закрывается после выполнения задачи, но раньше все было ОК. Как исправить?

    1. Фото аватара Юрий:

      Смотрите решение в уроке №7.

  7. Рахим Умурзаков:

    Спасибо, Юрий! Замечательные уроки! Получаю удовольствие.. Возникают вопросы.. ищу на сайтах.. нахожу.. Будут посерьёзнее, намерен обращаться, если позволите!

    1. Фото аватара Юрий:

      Пожалуйста))

      1. Дмитрий:

        Начал изучать С++ для создания игр на Unreal, но там все как то сложно.
        Случайно наткнулся на ваши уроки, это же просто чудо какое то)
        Спасибо за ваш труд.
        Пожалуй накидаю плюсиков в карму автору)

  8. Никита:

    Мне кажется стоило также указать что хорошим тоном является написать функцию в <name>.cpp, сделать её объявление в <name>.h, после чего подключить <name.h> в основном файле.

    Вместо того чтобы писать объявления функций напрямую.

    1. Никита:

      Можете об этом рассказать поподробнее, пожалуйста?

  9. Андрей:

    Первый файл с функцией int getInteger():

    Второй файл(главный):

  10. Фродо:

    Может кто-нибудь знает как это все осуществить при помощи Xcode, или статью какую-нибудь по обучению использования этой среды???

    1. Петр:

      Скачай VisualStudio

    2. Kirill:

      А что сложного в Xcode? Всё по аналогии с VS, но со своими особенностями (если уж на то пошло). Можно нагуглить на крайний случай.

  11. Александр:

    Здравствуйте Юрий! Благодарю вас за интересные и познавательные уроки! Стараюсь изучить все тонкости. Вы пожалуйста не бросайте этот сайт. Я думаю что развитие нужно направить в сторону 3D. Как вы на это смотрите?

    1. Фото аватара Юрий:

      Пожалуйста)

  12. Somon:

    Здравствуйте я пишу в андроид с помощью 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

    А вот коды

  13. Tor:

    В VS 2019 все компилируется и без #include "pch.h". А если как раз использую #include "pch.h"выдает ошибки "не удается открыть источник файл "pch.h" "и "не удается открыть файл включение: pch.h: No such file or directory " стоит ли пытаться это исправить или оставить как есть?

  14. Алексей:

    Сначала разделил верно, потом думал насчет iostream в инпуте, вот только зря добавил в саму функцию инициализацию переменной.
    Взглянул в ответ — понял сразу ошибку.

    Спасибо за уроки, хороший язык оказывается.

  15. Bohdan:

    Срочно нужна помощь.
    Начал программировать на андроид с приложения Dcoder(c++: GCC compiler 6.3 (знаю, что это неудобно и т. д., но лучше так, чем никак).
    С этим уроком получилась зиминка из-за моего не состояния найти способ, как связать несколько файлов именно в этом приложении. Если кто-то что с этим делать, тогда пишите (буду очень благодарен). Автору спасибо за уроки. Всё очень доходчиво написано.

  16. Дмитрий:

    Большое спасибо автору, читаю уроки — не могу оторваться (давно хотел изучить язык С++).
    У меня такой вопрос: почему при определении (и описании соответственно) функции getInteger() мы оставили скобки пустыми, (ведь мы задействуем 1 переменную и возвращаем ее значение)?

    1. Константин:

      Дык мы ж не собираемся в энту функцию никаких значений всовывать — вот и параметры в ней нече заявлять!

  17. Андрей:

    Все трудности с ошибками прошел, разобрался, помогли подсказки. Осталось не понятным — '\n', что оно означает?

    1. Павел:

      Это символ переноса строки, такая управляющая последовательность(и гуглите 🙂 )

  18. Артем:

    Какая же чудесная находка для меня эти уроки) Автору огромное спасибо за проделанную работу за перевод и адаптацию

    1. Фото аватара Юрий:

      Пожалуйста 🙂

  19. Иван:

    Здравствуйте. у меня возникла небольшая заминка. Не могли бы вы помочь мне в её разрешении?
    Я создал оба файла, как объяснялось в задании, добавил add.cpp (Добавить новый элемент —> файл.cpp). И в общем всё работает, но в файле add.cpp возникла ошибка: "IntelliSense: не удаётся открыть источник файл "stdafx.h" ".
    Помогите разобраться.

    1. Фото аватара Юрий:

      Если у вас Visual Studio 2017, то вам нужно #include <pch.h> вместо stdafx.h.

  20. Станислав:

    Здравствуйте. Как добавлять файлы в Visual Studio 2017 если он на русском. Просто не очень понятно, переводчик не помогает.

  21. данила:

    не получается разделить функцию main() на 2 файла (без возвратных функций)…это вообще возможно?)

    1. Константин:

      Данила, функция это тот же блок, т.е. некий цельный кусок, который из своих недр выдаёт на гора некое одно значение, например — превращает в число результат неких математических вычислений. Это значение можно впихнуть в другой блок, который, используя его произведёт также одно значение, но смысл последнего м.б. другим, например — выведется какая-нибудь надпись. Смыслы закладывает кодер (как и всё остальное). А функцию main() — вообще воспринимай как оглавление книги, в котором записаны вызовы всех функций, которые, собственно, и выполняют поставленные кодером задачи.

  22. Александр:

    После беглого просмотра десятка-другого уроков понял, что уроки-то неплохие (не считая некоторых грамматических ошибок), да вот возникло стойкое чувство deja vu. И правда, как оказалось, этот курс — просто перевод с минимальными изменениями курса, представленного на сайте LearnCpp.com.
    И хотя я искренне считаю, что людям, не владеющим английским языком, в программировании категорически делать нечего, все же ничего не имею против переводов хороших источников.
    Но только если автор честно пишет, что это не его детище, а просто перевод. Я не смог найти никакого упоминания или ссылки на оригинал. Мало того, вы, Юрий, еще и пытаетесь денег на этом заработать, продавая книжку с этим добром. Если я ошибся, прошу прощения, но если нет, то это очень грустно.

    1. Фото аватара Юрий:

      А где же, Александр, я писал, что это моё детище? Моё детище — это сайт Ravesli, а уроки С++ — это перевод. На странице "Уроки С++" пролистайте до конца — там источник уроков.

      Насчет денег — да, пытаюсь. Только перевод, а не оригинал, да ещё и с "минимальными изменениями". Уроки по С++ останутся бесплатными на сайте до тех пор, пока я буду поддерживать этот сайт, а всё что дополнительно — то это уже на моё усмотрение.

      1. Александр:

        Тогда я ошибся, и это чудесно =) Прошу прощения. Я не заметил ссылки, привык их искать где-то в аннотации.

        1. Фото аватара Юрий:

          Ничего, без проблем 🙂

  23. Леонид:

    Добрый день! При попытке добавить в функцию, которая находится в отдельном файле, "stdafx.h" компилятор выдаёт ошибку: не удаётся открыть файл включения stdafx.h: No such file or directory. Без stdafx.h всё нормально работает. Visual Studio 2015.

    1. Фото аватара Юрий:

      Скорее всего у вас отключены предварительно скомпилированные заголовки, коим и является stdafx.h. Работает без него — хорошо, работает с ним — еще лучше.

  24. Марина:

    Сегодня читала статью с телефона. Очень удобный сайт)

    1. Фото аватара Юрий:

      Можно и с телефона, и с планшета, и с компьютера, и с ноутбука — лишь бы желание было, а возможность предоставим 🙂

  25. Илья:

    Когда пишу файл add.cpp постоянно не идёт,это ж линкер мешает по моему???И что ему не так,когда твою программу скопировал,также всё:

    Ошибка LNK2019 ссылка на неразрешенный внешний символ _main в функции "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) add.cpp 1

    1. Фото аватара Юрий:

      Вы сделали всё как в уроке? Правильно добавили файл? К тому проекту, что нужно?

      В уроке есть отдельный пункт, где рассказываются возможные ошибки — ваша ошибка — это та же, что и в уроке. Там есть возможные решения.

  26. Максим():

    Почему в VS нужно обязательно подключать библиотеку stdafx.h? Раньше, когда я учился по видеоурокам, я писал программы и без ее подключения. Но конечно есть одно но, я тогда немного изменил параметры создания проекта: поставил галочку на "Empty project" и удалил галочку на "Security Development Lifecycle (SDL) checks ". Может быть это как-то повлияло?

    1. Фото аватара Юрий:

      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» на отключение механизма «предварительно скомпилированных заголовков» я ничего не могу сказать.

  27. Андрей Оганесян:

    Зачем писать названия в VS (например Source Files) на английском, если в VS есть русский язык?

    1. Фото аватара Юрий:

      Это уже дело вкуса, кому как привычнее.

      1. Андрей Оганесян:

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

        1. Фото аватара Юрий:

          Хотите вы этого или нет, но английский, даже самый базовый, знать вам придется. Любые команды, ключевые слова и прочее в коде — это обычные английские слова, которые имеют определенные значения. Писать транслитом (русские слова английской раскладкой) названия файлов, переменных, функций, классов — это, как минимум, некрасиво и неудобно. Что тут делать? Учить английский.

        2. Данила:

          +)).. тоже в гугл лез)))

      2. Андрей Оганесян:

        Согласен, спасибо

        1. Фото аватара Юрий:

          Понимаю, полным новичкам могут быть непонятны некоторые слова, но от этого никуда не деться. Там, где можно упростить — я упрощаю, что можно перевести — я перевожу, но всё перевести нельзя. Сам синтаксис любого языка программирования — это английский. И тут уже нужно подстраиваться.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *