Как разбить программу на файлы c
Я пытаюсь понять, как разделить программу на несколько файлов, чтобы иметь 1 файл .h и 2 файла .c . У меня есть полная программа, но только для этого примера я хотел бы, чтобы функция печати вывода была в отдельном файле .c. Я знаю, как сделать функцию базовой арифметики, например:
но как мне сделать функцию с циклом for и только с кодом ниже?
Я понимаю, как это работает, просто не знаю, как превратить цикл for в функцию.
Проблема, с которой вы столкнулись, неясна. Пожалуйста, сделайте минимальный воспроизводимый пример , который находится в одном файле .c и использует описанный выше цикл из функции (возможно, с параметрами int count, mystruct* ptr в предположении). Затем объясните, что удерживает вас от разделения используемой функции на другой .c.
Программа, которую вы показываете, не содержит цикла for. Вы же не спрашиваете, как использовать цикл for в функции? Если да, измените название и объяснение, пожалуйста.
Цикл for предназначен для печати результатов, он находится сверху. Пример, который я показал, просто показывает, что я провел исследование. Мой вопрос в том, что мне нужно сделать несколько форматов файлов с моей программой, в частности, я собирался сделать это с помощью функции цикла for. Просто не знаю как.
@user9593492 user9593492 -- ты все еще застрял на этом? Я думал, ты делаешь успехи? Если вы суммируете a+b в отдельном источнике -- то почему вы до сих пор таскаете старый код из адресной сортировки? Если у вас есть код, над которым вы сейчас работаете, который показывает, как сумма связана с адресом, опубликуйте весь код. Людям действительно трудно помочь вам, если показана только часть вашего кода — это не покер — показывать свою руку — это нормально.
В самом простом случае вы должны преобразовать фрагмент цикла в:
Окончательное освобождение есть, потому что массив теперь не содержит полезных указателей (все они были освобождены). Вместо этого вы можете добавить ptr[i] = NULL; после . free(ptr[i]); Это говорит о том, что данных там больше нет.
Однако, как отмечено в комментариях 1 и 2 от SergeyA и неуклюжем, но точном названии функции, это не очень хорошая разбивка кода. Вам нужны две функции:
Вы не будете использовать вариант 1, если вы используете вариант 2 (хотя это не принесет реального вреда). Если вы не используете вариант 2, вам следует использовать вариант 1.
Обратите внимание, что вам, вероятно, понадобится пробел между полями адреса. Вам может понадобиться одна строка для имени, одна строка для адреса улицы и одна строка для города, штата и почтового индекса — выберите подходящий формат. Как правило, новые строки выводятся в конце вывода, а не в начале, если только вы не хотите использовать двойной интервал.
So that would be my separate function.c file and my header file would look something like …code omitted… right? How would the function call in the main program look like?
Схема заголовка будет такой:
Обратите внимание, что заголовок не включает ; никакая часть интерфейса не зависит от чего-либо, специфичного для , например, FILE * (хотя это один из заголовков, определяющих size_t ). Заголовок должен быть минимальным, но автономным и идемпотентным. Не включать — часть минимализма; включение является частью самодостаточности; и защита заголовков является ключевой частью идемпотентности. Это означает, что вы можете включить заголовок, и вам не нужно беспокоиться о том, был ли он уже включен ранее косвенно или включен снова позже, косвенно, и вам не нужно беспокоиться о том, какие другие заголовки должны быть включены — заголовок сам по себе. -содержатся и занимается этим.
В вашем main() , у вас будет что-то вроде:
Это пример довольно плохого дизайна. Печать и уничтожение не имеет большого смысла, если они выражены в виде функции. Было бы лучше иметь их как две отдельные функции, print и destroy . Также очень нелогично (особенно для новичков), что размер массива не играет никакой роли в сигнатуре функции. Из-за этого я всегда предлагаю использовать указатель, т.е. SomeType* ptr
@SergeyA: Да, но я инкапсулирую то, что было показано. Ты прав; коду печати должен быть предоставлен const SomeType массив, который он не изменяет, а коду уничтожения должен быть предоставлен модифицируемый указатель и директива для освобождения памяти.
Конечно, но я просто считаю, что механическая инкапсуляция здесь не демонстрирует лучшие принципы, OP лучше бы помог с немеханической иллюстрацией разделения обязанностей (что является одной из причин, по которой мы используем функции)
@ user9593492: Я не хочу смотреть материалы pastebin. Информация должна быть в вопросе — в вопросе содержится достаточно информации, хотя, возможно, ее можно было бы лучше представить и дополнить некоторыми, но не всеми, из того, что, вероятно, находится в pastebin. Пожалуйста, прочитайте о том, как создать MCVE ( минимальный воспроизводимый пример ). Заголовок должен содержать объявление функции, но для большинства практических целей он также должен содержать определение структуры, поскольку использующие его функции должны знать о типе (хотя вы могли бы просто указать его typedef struct SomeType SomeType; в заголовке). ……
Есть две разные проблемы (я рекомендую решить первую, если это необходимо, перед второй):
как разбить монолитную единицу перевода на несколько, но с сохранением тех же функций
как реорганизовать код, чтобы сделать его более читаемым и состоящим из «меньших» и «лучших» функций. В вашем случае это основная проблема.
Второй вопрос ( рефакторинг кода ) действительно сложный, и на него нет простого универсального ответа. Это действительно зависит от проекта. Простое (и очень спорное, и чрезмерно упрощающее) эмпирическое правило состоит в том, что вам нужны функции, "делающие только одну вещь" и не более нескольких десятков строк каждая. Поэтому, как только функция выполняет более одной функции или имеет более одного или двух десятков строк, вам следует подумать о ее разделении и рефакторинге (но вы не всегда будете это делать). Очевидно, вы main должны быть разделены на несколько вещей.
Наконец, не поддавайтесь чрезмерной привычке помещать только одну функцию в *.c файл или иметь много маленьких *.c файлов всего по сотне строк в каждом. Как правило, это бесполезно и может увеличить время сборки (поскольку препроцессор будет работать много) и, возможно, даже немного снизить производительность вашего исполняемого файла (поскольку ваш оптимизирующий компилятор не сможет встроиться, если вы не используете link- оптимизация времени). Моя рекомендация (основанная на мнении, поэтому спорная) состоит в том, чтобы иметь исходные файлы из нескольких тысяч строк, каждая из которых содержит несколько (дюжины) функций.
В вашем случае я считаю, что ваша программа (в вашем вопросе) настолько мала, что вам не нужно разбивать ее на несколько единиц перевода (если только ваш учитель не попросит вас об этом). Но на самом деле вам нужно реорганизовать его, возможно, определив какой -то абстрактный тип данных и подпрограммы, поддерживающие его (см. this ).
Изучите исходный код существующего бесплатного программного обеспечения (например, на github ), связанного с вашим проектом , для вдохновения , поскольку вам нужно будет определить и следовать многим соглашениям о кодировании (и правилам кодирования), которые имеют большое значение при программировании на C. В вашем случае новичка я считаю, что изучение исходного кода любой небольшой бесплатной программы (несколько десятков тысяч строк, см. это ) — в предметной области, которую вы понимаете или которой интересуетесь, и в языке программирования, который вы практикуете, — принесет пользу. ты много.
Когда мы пишем программу на C/C++ в одном файле, проблем обычно не возникает. Они ждут того момента, когда исходный текст необходимо разбить на несколько файлов. В этой статье я постараюсь рассказать, как это сделать правильно.
Термины
Пара слов о терминах. Ниже даны определения терминов так, как они используются в данной статье. В некоторых случаях эти определения имеют более узкий смысл, чем общепринятые. Это сделано намеренно, дабы не утонуть в деталях и лишних уточнениях.
Исходный код — программа, написанная на языке программирования, в текстовом формате. А также текстовый файл, содержащий исходный код.
Компилятор — программа, выполняющая компиляцию (неожиданно! не правда ли?). На данный момент среди начинающих наиболее популярными компиляторами C/C++ являются GNU g++ (и его порты под различные ОС) и MS Visual Studio C++ различных версий. Подробнее см. в Википедии статьи: Компиляторы, Компиляторы C++.
Компиляция — преобразование исходного кода в объектный модуль.
Объектный модуль — двоичный файл, который содержит в себе особым образом подготовленный исполняемый код, который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполняемого модуля, либо библиотеки. (подробности)
Компоновщик (редактор связей, линкер, сборщик) — это программа, которая производит компоновку («линковку», «сборку»): принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль. (подробности)
Исполняемый модуль (исполняемый файл) — файл, который может быть запущен на исполнение процессором под управлением операционной системы. (подробности)
Препроцессор — программа для обработки текста. Может существовать как отдельная программа, так и быть интегрированной в компилятор. В любом случае, входные и выходные данные для препроцессора имеют текстовый формат. Препроцессор преобразует текст в соответствии с директивами препроцессора. Если текст не содержит директив препроцессора, то текст остаётся без изменений. Подробнее см. в Википедии: Препроцессор и Препроцессор Си.
IDE (англ. Integrated Development Environment) — интегрированная среда разработки. Программа (или комплекс программ), предназначенных для упрощения написания исходного кода, отладки, управления проектом, установки параметров компилятора, линкера, отладчика. Важно не путать IDE и компилятор. Как правило, компилятор самодостаточен. В состав IDE компилятор может не входить. С другой стороны с некоторыми IDE могут быть использованы различные компиляторы. (подробности)
Объявление — описание некой сущности: сигнатура функции, определение типа, описание внешней переменной, шаблон и т.п. Объявление уведомляет компилятор о её существовании и свойствах.
Определение — реализация некой сущности: переменная, функция, метод класса и т.п. При обработке определения компилятор генерирует информацию для объектного модуля: исполняемый код, резервирование памяти под переменную и т.д.
От исходного кода к исполняемому модулю
Создание исполняемого файла издавна производилось в три этапа: (1) обработка исходного кода препроцессором, (2) компиляция в объектный код и (3) компоновка объектных модулей, включая модули из объектных библиотек, в исполняемый файл. Это классическая схема для компилируемых языков. (Сейчас уже используются и другие схемы.)
Часто компиляцией программы называют весь процесс преобразования исходного кода в исполняемы модуль. Что неправильно. Обратите внимание, что в IDE этот процесс называется построение (build) проекта.
IDE обычно скрывают три отдельных этапа создания исполняемого модуля. Они проявляются только в тех случаях, когда на этапе препроцессинга или компоновки обнаруживаются ошибки.
Итак, допустим, у нас есть программа на C++ «Hello, World!»:
Затем результат работы препроцессора передаётся компилятору. Компилятор производит весь положенный комплекс работ: от синтаксического разбора и поиска ошибок до создания объектного файла (понятно, что если имеются синтаксические ошибки, то объектный файл не создаётся). В объектном файле обычно имеется таблица внешних ссылок — некая таблица, в которой, в частности, перечислены имена подпрограмм, которые используются в объектном модуле, но код которых отсутствует в данном объектном модуле. Эти подпрограммы внешние по отношению к модулю.
Исходный код, который может быть откомпилирован, называется единицей компиляции. Наша программа содержит одну единицу компиляции.
Что бы получить нормальный исполняемый модуль, необходимо «разрешить» внешние ссылки. Т.е. добавить в исполняемый модуль код отсутствующих подпрограмм и настроить соответствующим образом все ссылки на этот код. Этим занимается компоновщик. Он анализирует таблицу внешних ссылок объектного модуля, ищет в объектных библиотеках недостающие модули, копирует их в исполняемый модуль и настраивает ссылки. После этого исполняемый модуль готов.
Библиотека (объектная библиотека) — это набор откомпилированных подпрограмм, собранных в единый файл определённой структуры. Подключение библиотеки происходит на этапе компоновки исполняемого файла из объектных файлов (т.е. из тех файлов, которые получаются в результате компиляции исходного текста программы).
Необходимые объектные библиотеки входят в комплект поставки компилятора. В комплект поставки библиотек (любых) входит набор заголовочных файлов, которые содержат объявления, необходимые компилятору.
Если исходный код программы разделён на несколько файлов, то процесс компиляции и сборки происходит аналогично. Сначала все единицы компиляции по отдельности компилируются, а затем компоновщик собирает полученные объектные модули (с подключением библиотек) в исполняемый файл. Собственно, этот процесс и называется раздельной компиляцией.
Разделение текста программы на модули
Разделение исходного текста программы на несколько файлов становится необходимым по многим причинам:
- С большим текстом просто неудобно работать.
- Разделение программы на отдельные модули, которые решают конкретные подзадачи.
- Разделение программы на отдельные модули, с целью повторного использования этих модулей в других программах.
- Разделение интерфейса и реализации.
Я намеренно использовал слово «модуль», поскольку модулем может быть как класс, так и набор функций — вопрос используемой технологии программирования.
Как только мы решаем разделить исходный текст программы на несколько файлов, возникают две проблемы:
- Необходимо от простой компиляции программы перейти к раздельной. Для этого надо внести соответствующие изменения либо в последовательность действий при построении приложения вручную, либо внести изменения в командные или make-файлы, автоматизирующие процесс построения, либо внести изменения в проект IDE.
- Необходимо решить каким образом разбить текст программы на отдельные файлы.
Первая проблема — чисто техническая. Она решается чтением руководств по компилятору и/или линкеру, утилите make или IDE. В самом худшем случае просто придётся проштудировать все эти руководства. Поэтому на решении этой проблемы мы останавливаться не будем.
Вторая проблема — требует гораздо более творческого подхода. Хотя и здесь существуют определённые рекомендации, несоблюдение которых приводит либо к невозможности собрать проект, либо к трудностям в дальнейшем развитии проекта.
Во-первых, нужно определить какие части программы выделить в отдельные модули. Что бы это получилось просто и естественно, программа должна быть правильно спроектирована. Как правильно спроектировать программу? — на эту тему написано много больших и правильных книг. Обязательно поищите и почитайте книги по методологии программирования — это очень полезно. А в качестве краткой рекомендации можно сказать: вся программа должна состоять из слабо связанных фрагментов. Тогда каждый такой фрагмент может быть естественным образом преобразован в отдельный модуль (единицу компиляции). Обратите внимание, что под «фрагментом» подразумевается не просто произвольный кусок кода, а функция, или группа логически связанных функций, или класс, или несколько тесно взаимодействующих классов.
Во-вторых, нужно определить интерфейсы для модулей. Здесь есть вполне чёткие правила.
Интерфейс и реализация
Когда часть программы выделяется в модуль (единицу компиляции), остальной части программы (а если быть точным, то компилятору, который будет обрабатывать остальную часть программы) надо каким-то образом объяснить что имеется в этом модуле. Для этого служат заголовочные файлы.
Таким образом, модуль состоит из двух файлов: заголовочного (интерфейс) и файла реализации.
Заголовочный файл, как правило, имеет расширение .h или .hpp, а файл реализации — .cpp для программ на C++ и .c, для программ на языке C. (Хотя в STL включаемые файлы вообще без расширений, но, по сути, они являются заголовочными файлами.)
Заголовочный файл должен содержать все объявления, которые должны быть видны снаружи. Объявления, которые не должны быть видны снаружи, делаются в файле реализации.
Что может быть в заголовочном файле
Правило 1. Заголовочный файл может содержать только объявления. Заголовочный файл не должен содержать определения.
То есть, при обработке содержимого заголовочного файла компилятор не должен генерировать информацию для объектного модуля.
Единственным «исключением» из этого правила является определение метода в объявлении класса. Но по стандарту языка, если метод определён в объявлении класса, то для этого метода используется инлайновая подстановка. Поэтому, такое объявление не порождает исполняемого кода — код будет генерироваться компилятором только при вызове этого метода.
Аналогичная ситуация и с объявлением переменных-членов класса: код будет порождаться при создании экземпляра этого класса.
Правило 2. Заголовочный файл должен иметь механизм защиты от повторного включения.
Защита от повторного включения реализуется директивами препроцессора:
Заголовочный файл сам по себе не является единицей компиляции.
Что может быть в файле реализации
Файл реализации может содержать как определения, так и объявления. Объявления, сделанные в файле реализации, будут лексически локальны для этого файла. Т.е. будут действовать только для этой единицы компиляции.
Правило 3. В файле реализации должна быть директива включения соответствующего заголовочного файла.
Понятно, что объявления, которые видны снаружи модуля, должны быть также доступны и внутри.
Правило также гарантирует соответствие между описанием и реализацией. При несовпадении, допустим, сигнатуры функции в объявлении и определении компилятор выдаст ошибку.
Правило 4. В файле реализации не должно быть объявлений, дублирующих объявления в соответствующем заголовочном файле.
При выполнении Правила 3, нарушение Правила 4 приведёт к ошибкам компиляции.
Практический пример
Допустим, у нас имеется следующая программа:
main.cpp
Эта программа не является образцом для подражания, поскольку некоторые моменты идеологически неправильны, но, во-первых, ситуации бывают разные, а во-вторых, для демонстрации эта программа подходит очень неплохо.
Итак, что у нас имеется?
- глобальная константа cint , которая используется и в классе, и в main ;
- глобальная переменная global_var , которая используется в функциях func1 , func2 и main ;
- глобальная переменная module_var , которая используется только в функциях func1 и func2 ;
- функции func1 и func2 ;
- класс CClass ;
- функция main .
Вроде вырисовываются три единицы компиляции: (1) функция main , (2) класс CClass и (3) функции func1 и func2 с глобальной переменной module_var , которая используется только в них.
Не совсем понятно, что делать с глобальной константой cint и глобальной переменной global_var . Первая тяготеет к классу CClass , вторая — к функциям func1 и func2 . Однако предположим, что планируется и эту константу, и эту переменную использовать ещё в каких-то, пока не написанных, модулях программы. Поэтому прибавится ещё одна единица компиляции.
Теперь пробуем разделить программу на модули.
Сначала, как наиболее связанные сущности (используются во многих местах программы), выносим глобальную константу cint и глобальную переменную global_var в отдельную единицу компиляции.
globals.h
globals.cpp
Обратите внимание, что глобальная переменная в заголовочном файле имеет спецификатор extern . При этом получается объявление переменной, а не её определение. Такое описание означает, что где-то существует переменная с таким именем и указанным типом. А определение этой переменной (с инициализацией) помещено в файл реализации. Константа описана в заголовочном файле.
С объявлением констант в заголовочном файле существует одна тонкость. Если константа тривиального типа, то её можно объявить в заголовочном файле. В противном случае она должна быть определена в файле реализации, а в заголовочном файле должно быть её объявление (аналогично, как для переменной). «Тривиальность» типа зависит от стандарта (см. описание того стандарта, который используется для написания программы).
Также обратите внимание (1) на защиту от повторного включения заголовочного файла и (2) на включение заголовочного файла в файле реализации.
Затем выносим в отдельный модуль функции func1 и func2 с глобальной переменной module_var . Получаем ещё два файла:
funcs.h
funcs.cpp
Поскольку переменная module_var используется только этими двумя функциями, её объявление в заголовочном файле отсутствует. Из этого модуля «на экспорт» идут только две функции.
Наконец выносим в отдельный модуль класс CClass :
CClass.h
CClass.cpp
Обратите внимание на следующие моменты.
(1) Из объявления класса убрали определения тел функций (методов). Это сделано по идеологическим причинам: интерфейс и реализация должны быть разделены (для возможности изменения реализации без изменения интерфейса). Если впоследствии будет необходимость сделать какие-то методы инлайновыми, это всегда можно сделать с помощью спецификатора.
(2) Класс имеет статический член класса. Т.е. для всех экземпляров класса эта переменная будет общей. Её инициализация выполняется не в конструкторе, а в глобальной области модуля.
Классы практически всегда выделяются в отдельные единицы компиляции.
В файле main.cpp оставляем только функцию main . И добавляем необходимые директивы включения заголовочных файлов.
main.cpp
Последний шаг: необходимо изменить «проект» построения программы так, что бы он отражал изменившуюся структуру файлов исходного кода. Детали этого шага зависят от используемой технологии построения программы и используемого ПО. Но в любом случае сначала должны быть откомпилированы четыре единицы компиляции (четыре cpp-файла), а затем полученные объектные файлы должны быть обработаны компоновщиком для получения исполняемого файла.
Типичные ошибки
Ошибка 1. Определение в заголовочном файле.
Эта ошибка в некоторых случаях может себя не проявлять. Например, когда заголовочный файл с этой ошибкой включается только один раз. Но как только этот заголовочный файл будет включён более одного раза, получим либо ошибку компиляции «многократное определение символа . », либо ошибку компоновщика аналогичного содержания, если второе включение было сделано в другой единице компиляции.
Ошибка 2. Отсутствие защиты от повторного включения заголовочного файла.
Тоже проявляет себя при определённых обстоятельствах. Может вызывать ошибку компиляции «многократное определение символа . ».
Ошибка 3. Несовпадение объявления в заголовочном файле и определения в файле реализации.
Обычно возникает в процессе редактирования исходного кода, когда в файл реализации вносятся изменения, а про заголовочный файл забывают.
Если необходимый заголовочный файл не включён, то все сущности, которые в нём объявлены, останутся неизвестными компилятору. Вызывает ошибку компиляции «не определён символ . ».
Ошибка 5. Отсутствие необходимого модуля в проекте построения программы.
Ошибка 6. Зависимость от порядка включения заголовочных файлов.
Не совсем ошибка, но таких ситуаций следует избегать. Обычно сигнализирует либо об ошибках в проектировании программы, либо об ошибках при разделении исходного кода на модули.
Заключение
В рамках небольшой статьи невозможно рассмотреть все случаи, возникающие при раздельной компиляции. Бывают ситуации, когда разделение программы или большого модуля на более мелкие кажется невозможным. Обычно это бывает, когда программа плохо спроектирована (в данном случае, части кода имеют сильные взаимные связи). Конечно, можно приложить дополнительные усилия и всё-таки разделить код на модули (или оставить как есть), но эту мозговую энергию лучше потратить более эффективно: на изменение структуры программы. Это принесёт в дальнейшем гораздо большие дивиденды, чем просто силовое решение.
Подскажите пожалуйста как разбить консольное приложение на два и более файла? Я хочу чтоб у меня был центральный файл который по мере надобности запускал другие файлы. То есть так же как и главная функция которая может запускать другие функции. Так как я никогда ничего подобного не делал то сразу возникает кучка вопросов:
В какой формат сохранять файлы?
Как заставить главный файл запустить другие файлы (специальная функция или команда)?
Что писать в остальных файлах (должна ли у них быть своя функция main)?
Спасибо за внимание.
Предупреждение "Эти файлы нельзя открыть. " при запуске программы через "найти программы и файлы"
Доброго времени суток! Вылазит надпись "Эти файлы нельзя открыть. параметры безопасности.
Разбитие графика
Есть график,в графике через одно и тоже значение (1000) нужно вырезать отрезок определённой.
Для Си
Пример - в программе две функции function1(), function2()
Можно описать в одном файле так:
тогда можно так
файл function.h
Добавлено через 10 минут
Суть в том что функции (классы - для С++) распределяются по файлам, в файле .h пишут сигнатуру функций или описывают класс, а в файлах .c или .cpp пишут сам код (не всегда так - можно и в .h писать код, но не советую, так намного логичнее и проще, плюс могут возникнуть проблемы).
В других файлах не должно быть функции main!
Форматы - .h, .c, .cpp (вроде еще где-то видел .hpp - но я точно не скажу, не пользовался).
Чтобы заставить главный файл использовать функции из других файлов, в нем должен быть прописано
здесь прописывается путь к файлу, если все в одной папке можно только имя файла, либо относительный путь.
И можно использовать функции из тех файлов, как-будто они описаны в главном файле)
Добавлено через 6 минут
Ой чуть не забыл, в .h файле надо написать (в небольших программах не всегда нужно - но лучше использовать всегда)
Объяснили вы понятно, но только на практике у меня что то не получается. В заголовочном файле functions.h пишу следующие:
И тут же компилятор сердится мол "expected an identifier" какой ещё идентификатор ему нужен?
Помимо этого центральный файл не хочет открывать заголовочный файл, пишет что "нет такого файла", не смотря на то что оба файла в одной и той же папке.
И ещё вопросик:
Если я объявляю глобальную переменную в главном файле сможет ли под файл с ней работать?
Одно дело, когда они в одной папке и совсем другое, когда они добавлены в проект
И ещё вопросик:
Если я объявляю глобальную переменную в главном файле сможет ли под файл с ней работать?
Нет.
В телах функций, лежащих в подфайлах, объявляйте свои переменные, ну а в главном файле вызывайте их, по надобности передавая глобальную переменную.
Либо объявите эту переменную в заголовочном файле и его подключайте к остальным. Тогда уже переменная будет доступна всем.
Спасибо за разъяснения.
Заголовочный файл объявил как вы сказали - все заработало. Только глобальные переменные объявленные в заголовочном файле капризничают, цитирую:
error LNK2005: "int x1" (?x1@@3HA) already defined in functions.obj
Как только убираю объявления переменных из заголовочного файла эта бяка исчезает, но тогда для каждого файла придётся объявлять свои переменные.
Во общем мне нужно добиться одного из двух: либо объявить глобальные переменные чтоб у всех файлов был к ним доступ. Либо заставить функции возвращать больше одного параметра. Посоветуйте пожалуйста как такое реализовать.
Во общем мне нужно добиться одного из двух: либо объявить глобальные переменные чтоб у всех файлов был к ним доступ. Либо заставить функции возвращать больше одного параметра. Посоветуйте пожалуйста как такое реализовать.
Ну и подключать их в нужных модулях.
Все же, не советую так играть с глобальными переменными. Раз такая надобность в изменении переменных, то лучше передавать их по адресу. Так надежнее, да и заморочек меньше.
Доброго времени суток.
Подскажите пожалуйста, как правильно выносить код в разные файлы в c++?
Пишу программу, она уже разрослась на 3000+ строк кода и всё это в одной cpp файле!
Как мне правильно разделить код в разные файлы?
Попутный вопрос: было бы здорово вынести код в отдельные dll, но насколько это сложно и где это почитать?
Сложность этого действия зависит от двух факторов:
- от качества кода на данный момент
- от качества того, что хотите получить на выход
По первому пункту, если у вас код грамотно разбит на классы и/или функции, то проблемы быть не должно. Как уже писали, просто вынесите все определения в хедеры, все остальное оставьте в .cpp . С другой стороны, я себе слабо представляю как написать грамотный код в одном файле.
Это приводит нас ко второму пункту. Для того, чтобы в итоге получился качественный продукт, необходимо логическое разделение на подзадачи. Если это выполнить грамотно это приведет к небольшим функциям, которые всегда делают что-то одно и/или к небольшим классам, которые отвечают за одну сущность. Такую структуру легко вынести в разные файлы.
Если же у вас много глобальных переменных и тп, то код тоже можно легко разнести в разные файлы, но с группировкой по файлам будет сложно, да и смысла в этом много не будет.
ПС. Не уверен, знаете ли вы, но не забывайте в каждом .h файле:
это убережет вас от последущих повторных включений этого файла.
Несложно.
Определитесь сначала с модулями и интерфейсами.
В .h файлы выносите сигнатуры и типы, реализация - в cpp файлах, в которые приинклюжен нужный h файл.
с дллками легко - нужнен только префикс declspec(dllexport) для экспортируемых и declspec(dllimport) для импортируемых, но есть макрос, заменяющийся на это автоматом
Выносить код в отдельную dll имеет смысл только если нужно запускать несколько экземпляров вашей программы, либо в dll можно вынести какой-то специфичный код, который зависит от типа Операционной системы или других факторов (нп. создание тестовой библиотеки и полноценной)
Про разбиение одного файла на несколько - тут @KOLANICH верно описал. Добавлю, что в заголовочные файлы (.h) желательно не размещать ничего, кроме определения типов и классов, а также описания сигнатур функций: т.е. ни глобальных переменных, ни тел реализаций функций тут быть не должно, в общем - ни какой логики.
А также стараться поменьше включать заголовочные файлы друг в друга - потом на грабли с очерёдностью компиляции наткнётесь.
Вам нужен рефакторинг. То, что Вы описали, говорит о том, что Ваш код далёк от совершенства.
Вам нужно для начала применить extract method, затем exctract class или подобные методы. После чего можно будет спокойно классы разнести по отдельным файлам.
А для чего нужно разбивать на несколько DLL ? Может просто переоформить код,т.е провести рефакторинг и оставить все как есть в рамках одной DLL?
Я бы Вам посоветовал Пока "жить" в рамках одной DLL и провести рефакторинг, тогда внешняя программа что использует вашу DLL послужит хорошим тестовым стендом и провести проверочное тестирование после рефакторинга будет значительно проще! Вторым этапом, если Вы все же решите разбить на несколько DLL Вам будет значительно проще,т.к. понятный код и он протестирован!
Разбивается путем мышления и задавание себе вопросов.
Каждый модуль обязан отвечать утвердительно на вопрос "Он действительно решает только одну задачу?". При этом надо понимать не примитивные задачи "чтение из файла" или "подсчитать энтропию", под "одной задачей" понимает один пункт взятый с уровня абстракции.
Пример:
Уровень 1: Чтение настроек
Под-уровень 1: Формирование имени файла с настройками
Под-уровень 2: Открытие и чтение из файла с настойками
Под-уровень 3: Задание глобального объекта конфигуратор соглассно прочитанным настройками
и т.д. и т.п.
В любом случае идеальных методик по разбиению нет! Вас никто не научит программировать, это процесс итеративный, сегодня лучше чем вчера, а завтра будет еще лучше чем сегодня ;)
Для начала нужно понять идею dll. В них есть нужно размещать готовые самодостаточные компоненты, которые имеют смысл и за пределами узкой специфики одного проекта или для поддержки модульности (плагины, например). Грубо говоря, нужно думать, есть ли в этом великий смысл? Если просто хочется логически разделить код программы, то для этого будет достаточно разделения на уровне исходного кода. Об остальном сказано выше.
На выходе я должен иметь возможность вызывать в Program.cs методы простым gameInit'ом, а к переменной username иметь доступ на чтение и запись из любого элемента проекта — будь то главный файл проекта или файл с математическими функциям.
Прошу помощи здесь, ибо последний оплот.
- Вопрос задан более трёх лет назад
- 9622 просмотра
возможно ли вынести некие методы и переменные в отдельные файлы? К примеру, у меня есть методы gameInit() и gameDraw(), <. >. Могу ли я вынесли эти методы в отдельные файлы<. >?
Partial класс это что надо для вашего запроса.
Можно создать 2 файла: Program.Main.cs и Program.Game.cs (названия могут быть любыми) а внутри разместить partial-класс:
Обращаться к ней можно откуда угодно как:
Это вредный совет. При ручном написании кода не используйте partial. Эта фича придумана для расширения автогенерируемого кода.
Проблема автора вопроса в том, что он пишет в ООП языке используя не ООП подход.
There are several situations when splitting a class definition is desirable:
* When working on large projects, spreading a class over separate files enables multiple programmers to work on it at the same time.
* When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio uses this approach when it creates Windows Forms, Web service wrapper code, and so on. You can create code that uses these classes without having to modify the file created by Visual Studio.
* To split a class definition, use the partial keyword modifier
Само собой разумеется, что подход не тот. Но вопрос был не об этом. Не люблю уводить нить рассуждения от темы. А то начнется: «А что ты делаешь?», «А ты не так пишешь!», «А зачем тебе это надо?». Человек спросил, как ему доехать на катке до дачи — я ответил :) А почему он едет туда на катке — пусть это он сам у себя спросит :)
Насчет пересмотра подхода я согласен. не могу представить зачем может потребоваться значение переменной username в классе с математическими функциями. Использование глобальных переменных нарушает инкапсуляцию. Если значение нужно, то передавайте в качестве параметра.
* When working on large projects, spreading a class over separate files enables multiple programmers to work on it at the same time.
Ну это опять же значит, что что-то не то с классом.
Хотя судя по декомпилированному System.Windows.Forms.DataGrid, MS активно это использует.
П.С. Допускаю такое только для nested-types, да и то…
Партиал всего лишь позволяет разместить описание класса в нескольких файлах для удобства.
Читайте также: