Как поместить класс в отдельный файл c
Обновлено: 21.11.2024
Определение класса, структуры, интерфейса или метода можно разделить на два или более исходных файла. Каждый исходный файл содержит раздел определения типа или метода, и все части объединяются при компиляции приложения.
Частичные классы
Есть несколько ситуаций, когда желательно разделить определение класса:
- При работе над большими проектами распределение класса по отдельным файлам позволяет нескольким программистам работать над ним одновременно.
- При работе с автоматически сгенерированным исходным кодом в класс можно добавить код без повторного создания исходного файла. Visual Studio использует этот подход при создании форм Windows Forms, кода оболочки веб-службы и т. д. Вы можете создать код, использующий эти классы, без необходимости изменять файл, созданный Visual Studio.
- При использовании генераторов исходного кода для создания дополнительных функций в классе.
Ключевое слово partial указывает, что другие части класса, структуры или интерфейса могут быть определены в пространстве имен. Все части должны использовать ключевое слово partial. Все части должны быть доступны во время компиляции, чтобы сформировать окончательный тип. Все части должны иметь одинаковую доступность, например public , private и т. д.
Если какая-либо часть объявлена абстрактной, то весь тип считается абстрактным. Если какая-либо часть объявлена запечатанной, то весь тип считается запечатанным. Если какая-либо часть объявляет базовый тип, то весь тип наследует этот класс.
Все части, определяющие базовый класс, должны совпадать, но части, в которых базовый класс отсутствует, все равно наследуют базовый тип. Части могут указывать разные базовые интерфейсы, а окончательный тип реализует все интерфейсы, перечисленные во всех частичных объявлениях. Любые члены класса, структуры или интерфейса, объявленные в частичном определении, доступны для всех остальных частей. Последний тип представляет собой комбинацию всех частей во время компиляции.
Модификатор partial недоступен в объявлениях делегата или перечисления.
В следующем примере показано, что вложенные типы могут быть частичными, даже если тип, в который они вложены, сам по себе не является частичным.
Во время компиляции атрибуты определений частичного типа объединяются. Например, рассмотрим следующие объявления:
Они эквивалентны следующим объявлениям:
Следующее объединяется из всех определений частичного типа:
- XML-комментарии
- интерфейсы
- атрибуты параметров общего типа
- атрибуты класса
- участники
Например, рассмотрим следующие объявления:
Они эквивалентны следующим объявлениям:
Ограничения
При работе с частичными определениями классов необходимо соблюдать несколько правил:
- Все определения частичного типа, которые должны быть частями одного и того же типа, должны быть изменены с помощью частичного . Например, следующие объявления классов вызывают ошибку:
- Частичный модификатор может стоять только непосредственно перед ключевыми словами class , struct или interface .
- Вложенные частичные типы разрешены в определениях частичных типов, как показано в следующем примере:
- Все определения частичного типа, которые должны быть частями одного и того же типа, должны быть определены в одной сборке и в одном модуле (файле .exe или .dll). Частичные определения не могут охватывать несколько модулей.
- Имя класса и параметры универсального типа должны совпадать во всех определениях частичного типа. Универсальные типы могут быть частичными. Каждое частичное объявление должно использовать одни и те же имена параметров в одном и том же порядке.
- Следующие ключевые слова в определении частичного типа являются необязательными, но если они присутствуют в одном определении частичного типа, они не могут конфликтовать с ключевыми словами, указанными в другом частичном определении того же типа:
- модификатор базового класса (вложенные части)
- общие ограничения
Примеры
В следующем примере поля и конструктор класса Coords объявлены в одном частичном определении класса, а член PrintCoords объявлен в другом частичном определении класса.
В следующем примере показано, что вы также можете разрабатывать частичные структуры и интерфейсы.
Частичные методы
Частичный класс или структура могут содержать разделяемый метод. Одна часть класса содержит сигнатуру метода. Реализация может быть определена в той же части или в другой части. Если реализация не указана, метод и все вызовы метода удаляются во время компиляции. Реализация может потребоваться в зависимости от сигнатуры метода. Частичный метод не требуется для реализации в следующих случаях:
- У него нет никаких модификаторов доступа (включая закрытый по умолчанию).
- Возвращает значение void.
- У него нет выходных параметров.
- У него нет следующих модификаторов: виртуальный, переопределенный, запечатанный, новый или внешний.
Любой метод, который не соответствует всем этим ограничениям (например, общедоступный виртуальный метод частичной пустоты), должен иметь реализацию. Эта реализация может быть предоставлена генератором исходного кода.
Частичные методы позволяют разработчику одной части класса объявить метод. Разработчик другой части класса может определить этот метод. Это может быть полезно в двух случаях: шаблоны, генерирующие шаблонный код, и генераторы исходного кода.
- Код шаблона. Шаблон резервирует имя и подпись метода, чтобы сгенерированный код мог вызывать этот метод. Эти методы следуют ограничениям, которые позволяют разработчику решить, следует ли реализовывать метод. Если метод не реализован, компилятор удаляет сигнатуру метода и все вызовы метода. Вызовы метода, включая любые результаты, полученные в результате оценки аргументов в вызовах, не имеют никакого эффекта во время выполнения. Таким образом, любой код в разделяемом классе может свободно использовать разделяемый метод, даже если реализация не предоставляется. Если метод вызывается, но не реализуется, ошибок времени компиляции или времени выполнения не возникает.
- Генераторы исходного кода. Генераторы исходного кода обеспечивают реализацию методов. Разработчик-человек может добавить объявление метода (часто с атрибутами, считываемыми генератором исходного кода). Разработчик может написать код, вызывающий эти методы. Генератор исходного кода запускается во время компиляции и обеспечивает реализацию. В этом случае ограничения для разделяемых методов, которые могут быть не реализованы, часто не соблюдаются.
- Объявления частичного метода должны начинаться с контекстного ключевого слова partial.
- Подписи частичного метода в обеих частях частичного типа должны совпадать.
- Частичные методы могут иметь статические и небезопасные модификаторы.
- Частичные методы могут быть универсальными. Ограничения накладываются на определяющее объявление разделяемого метода и при необходимости могут повторяться на реализующем. Имена параметров и параметров типа не обязательно должны совпадать в реализующем объявлении и в определяющем.
- Вы можете сделать делегат для частичного метода, который был определен и реализован, но не для частичного метода, который был только определен.
В большинстве случаев полные программы не содержатся в одном файле. Многие небольшие программы легко написать в одном файле, но более крупные программы включают в себя отдельные файлы, содержащие разные модули. Обычно файлы разделяются по связанному содержимому.
- Заголовочный файл — содержит объявление класса (без сведений о реализации)
- Файл реализации — содержит реализации членов класса
Имена файлов:
Файлы заголовков обычно имеют формат имя файла.h, а файлы реализации обычно имеют формат < tt>имя файла.cpp. Рекомендуется использовать одно и то же базовое имя файла для соответствующих заголовочных файлов и файлов реализации. Пример. Имена файлов не обязательно должны совпадать с именем класса, но правильно подобранные имена файлов могут помочь определить содержимое или назначение файла.
Когда классы используются в программе, основная программа обычно записывается в отдельный файл.
Подборка
- Этап компиляции
- Синтаксис проверен на правильность.
- Переменные и вызовы функций проверяются, чтобы убедиться, что были сделаны правильные объявления и они совпадают. (Примечание: на этом этапе компилятору не нужно сопоставлять определения функций с их вызовами).
- Перевод в объектный код. Объектный код — это просто перевод вашего файла кода — на данный момент это не исполняемая программа. (Примечание: слово "объект" в объектном коде не относится к определению "объект", которое мы используем для определения объектно-ориентированного программирования. Это разные термины.)
- Этап связывания
- Связывает объектный код с исполняемой программой.
- Может включать один или несколько файлов объектного кода.
- Этап компоновки — это время, когда вызовы функций сопоставляются с их определениями, и компилятор проверяет наличие одного и только одного определения для каждой вызываемой функции.
- Конечным результатом связывания обычно является исполняемая программа.
Составление проекта из нескольких файлов
Все классы, которые мы написали до сих пор, были достаточно простыми, чтобы мы могли реализовать функции-члены непосредственно внутри самого определения класса. Например, вот наш вездесущий класс Date:
Однако по мере того, как классы становятся длиннее и сложнее, наличие всех определений функций-членов внутри класса может затруднить управление классом и работу с ним.Использование уже написанного класса требует понимания только его открытого интерфейса (общедоступных функций-членов), а не того, как класс работает внутри. Детали реализации функции-члена только мешают.
К счастью, в C++ есть способ отделить «декларацию» класса от «реализации». Это делается путем определения функций-членов класса вне определения класса. Для этого просто определите функции-члены класса, как если бы они были обычными функциями, но добавьте префикс имени класса к функции, используя оператор разрешения области видимости (::) (то же, что и для пространства имен).
Вот наш класс Date с конструктором Date и функцией setDate(), определенными вне определения класса. Обратите внимание, что прототипы этих функций по-прежнему существуют внутри определения класса, но реальная реализация вынесена за его пределы:
Это довольно просто. Поскольку функции доступа часто занимают всего одну строку, их обычно оставляют в определении класса, даже если их можно вынести за его пределы.
Вот еще один пример, включающий внешний конструктор со списком инициализации членов:
Помещение определений классов в заголовочный файл
На уроке, посвященном файлам заголовков, вы узнали, что объявления функций можно помещать в файлы заголовков, чтобы использовать эти функции в нескольких файлах или даже в нескольких проектах. Классы ничем не отличаются. Определения классов можно поместить в файлы заголовков, чтобы облегчить повторное использование в нескольких файлах или нескольких проектах. Традиционно определение класса помещается в файл заголовка с тем же именем, что и у класса, а функции-члены, определенные вне класса, помещаются в файл .cpp с тем же именем, что и у класса.
Вот снова наш класс Date, разбитый на файлы .cpp и .h:
Разве определение класса в заголовочном файле не нарушает правило одного определения?
Не должно. Если в вашем заголовочном файле есть надлежащие средства защиты заголовков, не должно быть возможности включить определение класса более одного раза в один и тот же файл.
Не нарушает ли определение функций-членов в заголовке правило одного определения?
Это зависит. Функции-члены, определенные внутри определения класса, считаются неявно встроенными. Встроенные функции освобождаются от одного определения на часть программы правила одного определения. Это означает, что нет проблем с определением тривиальных функций-членов (таких как функции доступа) внутри самого определения класса.
Функции-члены, определенные за пределами определения класса, обрабатываются как обычные функции и подчиняются одному определению для каждой программной части правила одного определения. Следовательно, эти функции должны быть определены в файле кода, а не в заголовке. Единственным исключением являются шаблонные функции, о которых мы поговорим в следующей главе.
Итак, что я должен определить в заголовочном файле по сравнению с файлом cpp, и что внутри определения класса, а что снаружи?
Возможно, у вас возникнет соблазн поместить все определения функций-членов в заголовочный файл внутри класса. Хотя это будет компилироваться, у этого есть несколько недостатков. Во-первых, как упоминалось выше, это загромождает ваше определение класса. Во-вторых, если вы что-то измените в коде в заголовке, вам нужно будет перекомпилировать каждый файл, содержащий этот заголовок. Это может иметь волновой эффект, когда одно незначительное изменение вызывает необходимость перекомпиляции всей программы (что может быть медленным). Если вы измените код в файле .cpp, перекомпилировать нужно только этот файл .cpp!
Поэтому мы рекомендуем следующее:
- Для классов, используемых только в одном файле, который обычно нельзя использовать повторно, определите их непосредственно в одном файле .cpp, в котором они используются.
- Для классов, используемых в нескольких файлах или предназначенных для общего повторного использования, определите их в файле .h с тем же именем, что и у класса.
- Простые функции-члены (тривиальные конструкторы или деструкторы, функции доступа и т. д.) могут быть определены внутри класса.
- Нетривиальные функции-члены должны быть определены в файле .cpp, который имеет то же имя, что и класс.
В будущих уроках большинство наших классов будут определены в файле .cpp, а все функции будут реализованы непосредственно в определении класса. Это просто для удобства и для краткости примеров. В реальных проектах гораздо чаще классы размещаются в собственном коде и файлах заголовков, и вам следует привыкнуть к этому.
Параметры по умолчанию
Библиотеки
За исключением некоторого программного обеспечения с открытым исходным кодом (где предоставляются файлы .h и .cpp), большинство сторонних библиотек предоставляют только файлы заголовков вместе с предварительно скомпилированным файлом библиотеки.Этому есть несколько причин: 1) скомпилировать предварительно скомпилированную библиотеку быстрее, чем перекомпилировать ее каждый раз, когда вам это нужно, 2) одна копия предварительно скомпилированной библиотеки может совместно использоваться многими приложениями, в то время как скомпилированный код компилируется в каждый исполняемый файл. которые его используют (увеличение размеров файлов), и 3) причины интеллектуальной собственности (вы не хотите, чтобы люди крали ваш код).
Разделение ваших собственных файлов на декларацию (заголовок) и реализацию (файл кода) — это не только хороший тон, но и упрощает создание собственных пользовательских библиотек. Создание собственных библиотек выходит за рамки этих руководств, но для этого необходимо разделить объявление и реализацию.
Мы разработали класс GradeBook настолько, насколько это необходимо с точки зрения программирования, поэтому давайте рассмотрим некоторые вопросы разработки программного обеспечения. Одним из преимуществ создания определений классов является то, что при правильной упаковке наши классы могут повторно использоваться программистами по всему миру. Например, мы можем повторно использовать тип string стандартной библиотеки C++ в любой программе на C++, включив в программу заголовочный файл (и, как мы увидим, имея возможность ссылаться на объектный код библиотеки).
когда компилятор пытается скомпилировать вторую обнаруженную функцию main. Точно так же компилятор GNU C++ выдает ошибку
Эти ошибки указывают на то, что в программе уже есть функция main. Таким образом, размещение main в одном файле с определением класса предотвращает повторное использование этого класса другими программами. В этом разделе мы покажем, как сделать класс GradeBook многоразовым, выделив его в другой файл из функции main.
В нашем следующем примере мы разделяем код с рис. 3.7 на два файла GradeBook.h (рис. 3.9) и fig03_10.cpp (рис. 3.10). . Глядя на файл заголовка на рис. 3.9, обратите внимание, что он содержит только определение класса GradeBook (строки 1141) и строки 38, которые позволяют классу GradeBook использовать < tt>cout, endl и введите string. Функция main, использующая класс GradeBook, определена в файле исходного кода fig03_10.cpp (рис. 3.10) в строках 1021. В помощь вы готовитесь к более крупным программам, с которыми вы столкнетесь позже в этой книге, и в промышленности мы часто используем отдельный файл исходного кода, содержащий функцию main, для тестирования наших классов (это называется программой-драйвером). Вскоре вы узнаете, как файл исходного кода с main может использовать определение класса, найденное в заголовочном файле, для создания объектов класса.
Рисунок 3.9. Определение класса GradeBook.
(Этот элемент отображается на страницах 96–97 в версии для печати)
Включение файла заголовка, содержащего определяемый пользователем класс
При запуске GNU C++ в Linux появляется сообщение об ошибке компоновщика, содержащее:
Эта ошибка означает, что компоновщику не удалось найти функцию программы main. Чтобы протестировать класс GradeBook (определенный на рис. 3.9), вы должны написать отдельный файл исходного кода, содержащий функцию main (такую как рис. 3.10), которая создает экземпляры и использует объекты класса.
Рисунок 3.10. Включая класс GradeBook из файла GradeBook.h для использования в main.
Вспомните из раздела 3.4, что, хотя компилятор знает, что такое фундаментальные типы данных, такие как int, компилятор не знает, что такое GradeBook, потому что это пользователь- определенный тип. На самом деле компилятор даже не знает классов из стандартной библиотеки C++. Чтобы помочь ему понять, как использовать класс, мы должны явно предоставить компилятору определение класса, поэтому, например, чтобы использовать тип string, программа должна включать заголовочный файл. Это позволяет компилятору определить объем памяти, который он должен зарезервировать для каждого объекта класса, и убедиться, что программа правильно вызывает функции-члены класса.
Для создания объектов GradeBook gradeBook1 и gradeBook2 в строках 1314 на рис. 3.10 компилятор должен знать размер объект GradeBook. В то время как объекты концептуально содержат данные-члены и функции-члены, объекты C++ обычно содержат только данные. Компилятор создает только одну копию функций-членов класса и разделяет эту копию между всеми объектами класса. Каждому объекту, конечно, нужна собственная копия элементов данных класса, потому что их содержимое может варьироваться в зависимости от объекта (например, два разных объекта BankAccount имеют два разных элемента данных balance). ). Однако код функции-члена нельзя изменить, поэтому его можно использовать совместно со всеми объектами класса. Следовательно, размер объекта зависит от объема памяти, необходимого для хранения элементов данных класса. Включив GradeBook.h в строку 7, мы даем компилятору доступ к необходимой ему информации (рис. 3.9, строка 40), чтобы определить размер объекта GradeBook и определить, правильно ли используются объекты класса (в строках 1314 и 1718 на рис. 3.10).
Как расположены файлы заголовков
Совет 3.3 по предотвращению ошибок
Дополнительные вопросы по разработке программного обеспечения
Теперь, когда класс GradeBook определен в файле заголовка, этот класс можно использовать повторно. К сожалению, размещение определения класса в заголовочном файле, как показано на рис. 3.9, по-прежнему раскрывает полную реализацию класса клиентам классаGradeBook.h — это просто текстовый файл, который любой может открыть и прочитать. Традиционная мудрость разработчиков программного обеспечения гласит, что для использования объекта класса клиентский код должен знать только, какие функции-члены вызывать, какие аргументы предоставлять каждой функции-члену и какой тип возвращаемого значения ожидать от каждой функции-члена. Коду клиента не нужно знать, как реализованы эти функции.
Если клиентский код знает, как реализован класс, программист клиентского кода может написать клиентский код на основе деталей реализации класса. В идеале, если эта реализация изменится, клиенты класса не должны измениться. Сокрытие сведений о реализации класса упрощает изменение реализации класса, минимизируя и, возможно, устраняя изменения в клиентском коде.
В разделе 3.9 мы покажем, как разбить класс GradeBook на два файла, чтобы
CLion предоставляет шаблоны файлов для большинства поддерживаемых языков. Эти шаблоны помогают создавать файлы с уже существующим исходным содержимым. Например, существует три шаблона для C/C++: класс C++ , исходный файл C++ , файл заголовка C++ .
Чтобы отредактировать шаблоны файлов, вызовите команду «Редактировать шаблоны файлов» из контекстного меню представления «Проект» или выберите «Настройки/Настройки | Редактор | Шаблоны файлов и кода .
Создать новый класс C++
В окне инструмента "Проект" выберите каталог, в который вы хотите добавить новые файлы. Щелкните его правой кнопкой мыши и выберите «Создать | Класс C++ из контекстного меню.
В открывшемся диалоговом окне:
Укажите имя класса.
Из списка выберите тип исходных файлов и файлов заголовков для нового класса, если вы хотите изменить тип по умолчанию, определенный на вкладке «Новые расширения файлов» диалогового окна «Стиль кода C/C++».< /p>
Установите флажок Создать только заголовок, если вы хотите создать только файл заголовка.
Установите флажок "Добавить в цели", чтобы добавить созданные файлы в список исходных файлов для выбранных целей.
Выберите нужную цель из списка. На панели ниже найдите переменную CMakeLists.txt, в которую CLion предлагает добавить созданные файлы.
Создать новый исходный файл C/C++
В окне инструмента "Проект" выберите каталог, в который вы хотите добавить новые файлы. Щелкните его правой кнопкой мыши и выберите «Создать | Исходный файл C/C++ из контекстного меню.
В открывшемся диалоговом окне:
Укажите имя файла.
Из списка выберите тип исходного файла, если вы хотите изменить тип по умолчанию, определенный на вкладке "Новые расширения файлов" диалогового окна "Стиль кода C/C++".
Установите флажок «Создать связанный заголовок», если вы хотите также создать соответствующий файл заголовка.
Установите флажок "Добавить в цели", чтобы добавить созданный файл в список исходных файлов для выбранных целей.
Выберите нужную цель из списка. На панели ниже найдите переменную CMakeLists.txt, в которую CLion предлагает добавить созданный файл.
Создать новый заголовок C/C++
В окне инструмента "Проект" выберите каталог, в который вы хотите добавить новые файлы. Щелкните его правой кнопкой мыши и выберите «Создать | Заголовочный файл C/C++ из контекстного меню.
В открывшемся диалоговом окне:
Укажите имя файла.
Из списка выберите тип файла заголовка, если вы хотите изменить тип по умолчанию, определенный на вкладке "Новые расширения файлов" диалогового окна "Стиль кода C/C++".
Установите флажок "Добавить в цели", чтобы добавить созданный файл в список исходных файлов для выбранных целей.
Выберите нужную цель из списка. На панели ниже найдите переменную CMakeLists.txt, в которую CLion предлагает добавить созданный файл.
Читайте также: