Как сделать класс в отдельном файле c

Обновлено: 07.07.2024

Можно разделить определение класса, структуры, интерфейса или метода между двумя или более исходными файлами. Каждый исходный файл содержит часть определения класса или метода, а во время компиляции приложения все части объединяются.

Разделяемые классы

Существует несколько ситуаций, когда желательно разделение определения класса.

  • При работе над большими проектами распределение класса между различными файлами позволяет нескольким программистам работать с ним одновременно.
  • При работе с использованием автоматически создаваемого источника код можно добавлять в класс без повторного создания файла источника. Visual Studio использует этот подход при создании форм Windows Forms, кода оболочки веб-службы и т. д. Можно создать код, который использует эти классы, без необходимости изменения файла, созданного в Visual Studio.
  • При использовании генераторов источников для создания дополнительных функциональных возможностей в классе.

Чтобы разделить определение класса, используйте модификатор ключевого слова partial, как показано ниже:

Ключевое слово partial указывает, что другие части класса, структуры или интерфейса могут быть определены в пространстве имен. Все части должны использовать ключевое слово partial . Для формирования окончательного типа все части должны быть доступны во время компиляции. Все части должны иметь одинаковые модификаторы доступа, например public , private и т. д.

Если какая-либо из частей объявлена абстрактной, то весь тип будет считаться абстрактным. Если какая-либо из частей объявлена запечатанной, то весь тип будет считаться запечатанным. Если какая-либо из частей объявляет базовый тип, то весь тип будет наследовать данный класс.

Все части, указывающие базовый класс, должны быть согласованы друг с другом, а части, не использующие базовый класс, все равно наследуют базовый тип. Части могут указывать различные базовые интерфейсы, и окончательный тип будет реализовывать все интерфейсы, перечисленные во всех разделяемых объявлениях. Любые члены класса, структуры или интерфейса, объявленные в разделяемом объявлении, доступны для всех остальных частей. Окончательный тип представляет собой комбинацию всех частей, выполненную во время компиляции.

Модификатор partial недоступен в объявлениях делегатов или перечислений.

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

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

Они эквивалентны следующим объявлениям:

Следующие элементы объединяются из всех определений разделяемого типа:

  • XML-комментарии
  • интерфейсы
  • атрибуты параметров универсального параметра
  • атрибуты классов
  • члены

В качестве примера рассмотрим следующие объявления:

Они эквивалентны следующим объявлениям:

Ограничения

Имеется несколько правил, которые необходимо выполнять при работе с определениями разделяемого класса.

Дополнительные сведения см. в разделе Ограничения параметров типа.

Примеры

В следующем примере поля и конструктор класса Coords объявлены в одном определении разделяемого класса, а член PrintCoords — в другом определении разделяемого класса.

В следующем примере показано, что можно также разработать разделяемые структуры и интерфейсы.

Разделяемые методы

Разделяемый класс или структура могут содержать разделяемый метод. Одна часть класса содержит сигнатуру метода. В той же или в другой части можно определить реализацию. Если реализация не предоставлена, метод и все вызовы метода удаляются во время компиляции. Реализация может потребоваться в зависимости от сигнатуры метода. Разделяемый метод может не иметь реализацию в следующих случаях.

  • У него нет модификаторов доступа (включая private по умолчанию).
  • Он возвращает значение void.
  • У него нет параметров out.
  • У него нет ни одного из следующих модификаторов: virtual, override, sealed, new или extern.

Любой метод, не соответствующий всем этим ограничениям (например, метод public virtual partial void ), должен предоставлять реализацию. Эта реализация может быть предоставлена генератором источника.

Разделяемые методы позволяют разработчику одного элемента класса объявить метод. Разработчик другого элемента класса может определить этот метод. Существует два сценария, в которых выгодно использовать разделяемые методы: шаблоны, создающие стереотипный код и генераторы источника.

  • Код шаблона: шаблон резервирует имя и подпись метода, чтобы созданный код мог вызвать метод. Эти методы придерживаются ограничений, которые позволяют разработчику решить, следует ли реализовать этот метод. Если метод не реализован, то компилятор удаляет сигнатуру метода и все вызовы этого метода. Вызовы метода, включая любые результаты, которые могли бы произойти от оценки аргументов в вызовах, не имеют эффекта во время выполнения. Таким образом, любой код в разделяемом классе может свободно использовать разделяемый метод, даже если реализация не предоставлена. Во время компиляции и выполнения программы не возникнут никакие ошибки, если метод будет вызван, но не реализован.
  • Генераторы источников: генераторы источников предоставляют реализацию методов. Разработчик может добавить объявление метода (часто с атрибутами, считанными генератором источника). Разработчик может написать код, который вызывает эти методы. Генератор источника выполняется во время компиляции и обеспечивает реализацию. В этом сценарии не соблюдаются ограничения для разделяемых методов, которые не могут реализовываться часто.
  • Объявления разделяемого метода должны начинаться с контекстного ключевого слова partial.
  • Сигнатуры разделяемого метода в обеих частях разделяемого типа должны совпадать.
  • Разделяемые методы могут иметь модификаторы static и unsafe.
  • Разделяемые методы могут быть универсальными. Ограничения налагаются на ту часть объявления разделяемого метода, где находится определение, и могут дополнительно повторяться в разделе реализации. Имена параметров и типов параметров необязательно должны совпадать в объявлении реализации и в объявлении определения.
  • Можно использовать делегат в качестве определенного и реализованного разделяемого метода, но его нельзя использовать в качестве разделяемого метода, который только определен.

Вынести функцию в отдельный файл C/C++

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

Обычно это делают так:

    помещаются в заголовочный файл (.h)
  • Реализации (определения) функций помещаются в отдельный .cpp файл
  • В файле (с main()), где вызываются функции, подключается файл с прототипами (.h)

Теперь разберем как это сделать на примере.

Допустим у нас есть программа в которой мы запрашиваем X, а на экран должно вывестись X²

1. Заголовочный файл (.h)

Создаем заголовочный файл и помещаем туда прототип нашей функции:

Создание заголовочного файла (.h)

Обязательно используем конструкцию для защиты от множественных включений в один файл:

2. Реализация функции (.c/.cpp)

Создание отдельного файла под функцию

Создаем отдельный .cpp файл (function.cpp):

Пишем реализацию функции:

Конфигурация компьютера
Процессор: Core i5-2400
Материнская плата: Foxconn H61MXL
Память: 8Gb (2x4Gb) DDR3-1600
HDD: HDD x4 SATAII
Видеокарта: Palit Radeon HD5770
Звук: Интегрированная
Блок питания: HuntKey LW-6550HG 550W Куплен: 29.11.12.
CD/DVD: ASUS DVD-RW (Model No.: DRW-1608P3S)
Монитор: 21,5" Acer V226HQL
ОС: Windows 7 Ultimate x64 (от m0nkrus)
Прочее: В кейсе стоит два дополнительных вентилятора (12 см), один из которых имеет подсветку.

Проблема решена. Для этого потребовалось сделать следующее:
1) В файлах Two.h и Three.h заменил инклюд файла One.h на объявление: class One;
2) Учел особенности работы Visual Studio; проинклюдил все свои заголовочные файлы и файл iostream в файле stdafx.h, а из своих cpp-файлов эти инклюды убрал, заменив на инклюд stdafx.h.

Для отключения данного рекламного блока вам необходимо зарегистрироваться или войти с учетной записью социальной сети.

допустим есть 3 файла:

такой код не проглотится компилятором, потому что переопределяется переменная a. Чтобы не было таких подвохов, используют хитрость:

Описанием объекта является класс , а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке, у которого есть имя, возраст, какие-то другие характеристики. То есть некоторый шаблон - этот шаблон можно назвать классом. Конкретное воплощение этого шаблона может отличаться, например, одни люди имеют одно имя, другие - другое имя. И реально существующий человек (фактически экземпляр данного класса) будет представлять объект этого класса.

В принципе ранее уже использовались классы. Например, тип string , который представляет строку, фактически является классом. Или, например, класс Console , у которого метод WriteLine() выводит на консоль некоторую информацию. Теперь же посмотрим, как мы можем определять свои собственные классы.

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

После слова class идет имя класса и далее в фигурных скобках идет собственно содержимое класса. Например, определим в файле Program.cs класс Person, который будет представлять человека:

Однако такой класс не особо показателен, поэтому добавим в него некоторую функциональность.

Поля и методы класса

Класс может хранить некоторые данные. Для хранения данных в классе применяются поля . По сути поля класса - это переменные, определенные на уровне класса.

Кроме того, класс может определять некоторое поведение или выполняемые действия. Для определения поведения в классе применяются методы.

Итак, добавим в класс Person поля и методы:

В данном случае в классе Person определено поле name , которое хранит имя, и поле age , которое хранит возраст человека. В отличие от переменных, определенных в методах, поля класса могут иметь модификаторы, которые указываются перед полем. Так, в данном случае, чтобы все поля были доступны вне класса Person поля определены с модификатором public .

При определении полей мы можем присвоить им некоторые значения, как в примере выше в случае переменной name . Если поля класса не инициализированы, то они получают значения по умолчанию. Для переменных числовых типов это число 0.

Также в классе Person определен метод Print() . Методы класса имеют доступ к его поля, и в данном случае обращаемся к полям класса name и age для вывода их значения на консоль. И чтобы этот метод был виден вне класса, он также определен с модификатором public .

Создание объекта класса

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

Сначала идет оператор new , который выделяет память для объекта, а после него идет вызов конструктора .

Конструктор по умолчанию

Если в классе не определено ни одного конструктора (как в случае с нашим классом Person), то для этого класса автоматически создается пустой конструктор по умолчанию, который не принимает никаких параметров.

Теперь создадим объект класса Person:

Для создания объекта Person используется выражение new Person() . В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Person. А переменная tom получит ссылку на созданный объект, и через эту переменную мы можем использовать данный объект и обращаться к его функциональности.

Обращение к функциональности класса

Для обращения к функциональности класса - полям, методам (а также другим элементам класса) применяется точечная нотация точки - после объекта класса ставится точка, а затем элемент класса:

Например, обратимся к полям и методам объекта Person:

Консольный вывод данной программы:

Константы классы

Кроме полей класс может определять для хранения данных константы. В отличие от полей из значение устанавливается один раз непосредственно при их объявлении и впоследствии не может быть изменено. Кроме того, константы хранят некоторые данные, которые относятся не к одному объекту, а ко всему классу в целом. И для обращения к константам применяется не имя объекта, а имя класса:

Здесь в классе Person определена константа type , которая хранит название класса:

Название класса не зависит от объекта. Мы можем создать много объектов Person, но название класса от этого не должно измениться - оно относится ко всем объектам Person и не должно меняться. Поэтому название типа можно сохранить в виде константы.

Стоит отметить, что константе сразу при ее определении необходимо присвоить значение.

Подобно обычным полям мы можем обращаться к константам класса внутри этого класса. Например, в методе Print значение константы выводится на консоль.

Однако если мы хотим обратиться к константе вне ее класса, то для обращения необходимо использовались имя класса:

Таким образом, если необходимо хранить данные, которые относятся ко всему классу в целом

Добавление класса в Visual Studio

Обычно классы помещаются в отдельные файлы. Нередко для одного класса предназначен один файл. И Visual Studio предоставляет по умолчанию встроенные шаблоны для добвления класса.

Для добавления класса нажмем в Visual Studio правой кнопкой мыши на название проекта:

В появившемся контекстном меню выберем пункт Add -> New Item. (или Add -> Class. )

В открывшемся окне добавления нового элемента убедимся, что в центральной части с шаблонами элементов у нас выбран пункт Class . А внизу окна в поле Name введем название добавляемого класса - пусть он будет назваться Person :

В качестве названия класса можно вводить как Person, так и Person.cs. И после нажатия на кнопку добавления в проект будет добавлен новый класс:

В файле Person.cs определим следующий код:

Здесь определен класс Person с одним полем name и методом Print.

В файле Program.cs , который представляет основной файл программы используем класс Person:

Таким образом, мы можем определять классы в отдельных файлах и использовать их в программе.

Я получаю ошибки, пытаясь скомпилировать класс шаблона C++, который разделен между .hpp и :

стек.ГЭС:

стек.cpp:

main.cpp:

ld это конечно правильно: символы не в stack.o .

ответ этот вопрос тут не помочь, как я уже делаю, как он говорит.
этот может помочь, но я не хочу перемещать каждый метод в .hpp файл-я не должен, не так ли?

является единственным разумным решением для перемещения всего в до .hpp файл и просто включить все, а не ссылаться в качестве автономного объектного файла? Это кажется ужасно некрасиво! В этом случае я мог бы также вернуться к своему предыдущему состоянию и переименовать stack.cpp to stack.hpp и покончим с этим.

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

чтобы узнать, почему, давайте посмотрим на процесс компиляции. Заголовочные файлы не компилируются. Они только предварительно обрабатывать. Предварительно обработанный код затем объединяется с файлом cpp, который фактически компилируется. Теперь, если компилятор должен создать соответствующий макет памяти для объекта, он должен знать тип данных класса template.

на самом деле следует понимать, что шаблон класса-это вообще не класс, а шаблон класса, объявление и определение, который генерируется компилятором во время компиляции, после получения информации тип данных с аргумент. Пока макет памяти не может быть создан, инструкции для определения метода не могут быть сгенерированы. Помните, что первым аргументом метода класса является оператор "this". Все методы класса преобразуются в отдельные методы с именем mangling и первым параметром в качестве объекта, с которым он работает. Аргумент "this" фактически говорит о размере объекта, который в случае класса template недоступен для компилятора, если пользователь не создает экземпляр объекта с аргументом типа. В этом случае, если вы поместите определения методов в отдельный cpp-файл и попытаетесь его скомпилировать, сам объектный файл не будет сгенерирован с информацией о классе. Компиляция не завершится ошибкой, она создаст объектный файл, но не будет генерировать код для класса шаблона в объектном файле. Именно по этой причине компоновщик не может найти символы в объектных файлах, и сборка завершается неудачно.

теперь какова альтернатива скрытию важные детали реализации? Как мы все знаем, основная цель отделения интерфейса от реализации прячет детали реализации в двоичной форме. Здесь необходимо разделить структуры данных и алгоритмы. Классы шаблонов должны представлять только структуры данных, а не алгоритмы. Это позволяет скрыть более ценные сведения о реализации в отдельных библиотеках классов без шаблонов, классы внутри которых будут работать с классами шаблонов или просто использовать их для держите данные. Класс шаблона фактически будет содержать меньше кода для назначения, получения и установки данных. Остальная часть работы будет выполнена классами алгоритмов.

Я надеюсь, что это обсуждение будет полезным.

Это и возможно, пока вы знаете, какие экземпляры вам понадобятся.

добавьте следующий код в конце стека.cpp и он будет работать :

будут созданы все не шаблонные методы стека, и шаг связывания будет работать нормально.

вы можете сделать это таким образом

Это обсуждалось в Daniweb

и в часто задаваемые вопросы но используя ключевое слово экспорта C++.

нет, это невозможно. Не без export ключевое слово, которое для всех намерений и целей на самом деле не существует.

лучшее, что вы можете сделать, это поместить свои реализации функций в".ККТ" или ".файл ТЭС", и включать .TCC файл в конце вашего .файл ГЭС. Однако это просто косметика; это по-прежнему то же самое, что и реализация всего в заголовочных файлах. Это просто цена, которую вы платите за использование шаблонов.

Я считаю, что есть две основные причины для попытки разделить шаблонный код в заголовок и cpp:

для простой элегантности. Нам всем нравится писать код, который можно читать, управлять и повторно использовать позже.

другое сокращение времени компиляции.

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

иногда можно скрыть большую часть реализации в файле cpp, если вы можете извлечь общую функциональность для всех параметров шаблона в класс без шаблона (возможно, небезопасный тип). Затем header будет содержать вызовы перенаправления в этот класс. Аналогичный подход используется при борьбе с проблемой "раздувания шаблона".

Если вы знаете, с какими типами будет использоваться ваш стек, вы можете создать их неявно в файле cpp и сохранить там весь соответствующий код.

также можно экспортировать их через DLL (!) но довольно сложно получить правильный синтаксис(MS-специфические комбинации __declspec (dllexport) и ключевого слова export).

мы использовали это в Math / geom lib, который шаблонировал double / float, но имел довольно много кода. (В то время я искал его в гугле, не у этого кода сегодня есть.)

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

простой и естественный способ-поместить методы в файл заголовка. Но есть другой путь.

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

новый стек.cpp:

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

одна вещь, которую я, как правило, делаю, это попытаться разделить мои шаблоны на общую не шаблонную часть (которая может быть разделена между cpp/hpp) и специфичной для типа шаблонной частью, которая наследует не шаблонную класс.

Это довольно старый вопрос, но я думаю, что интересно посмотреть презентацию Артур О'Двайер на cppcon 2016. Хорошее объяснение, много темы, надо смотреть.

поскольку шаблоны компилируются при необходимости, это накладывает ограничение на проекты с несколькими файлами: реализация (определение) класса или функции шаблона должна находиться в том же файле, что и его объявление. Это означает, что мы не можем разделить интерфейс в отдельном файле заголовка и что мы должны включить интерфейс и реализацию в любой файл, который использует шаблоны.

еще одна возможность сделать что-то вроде:

ключевое слово "экспорт" - это способ отделить реализацию шаблона от объявления шаблона. Это было введено в стандарт C++ без использования существующей реализации. В свое время только несколько компиляторов фактически реализовали его. Читайте подробную информацию на сообщите ему статью об экспорте

1) Помните основную причину разделения .h и .cpp-файлы должны скрывать реализацию класса как отдельно скомпилированный Obj-код, который может быть связан с кодом пользователя, включающим a .h класса.

2) не шаблонные классы имеют все переменные конкретно и конкретно определенные.h и .cpp-файлы. Таким образом, компилятор будет иметь необходимую информацию обо всех типах данных, используемых в классе, перед компиляцией / переводом  создание объектного / машинного кода Класс шаблона не иметь сведений о конкретном типе данных до того, как пользователь класса создаст экземпляр объекта, передающего требуемый тип данных:

3) только после этого экземпляра complier генерирует определенную версию класса шаблона для соответствия переданному типу(типам) данных.

4) поэтому .cpp не может быть скомпилирован отдельно, не зная конкретного типа данных пользователей. Поэтому он должен оставаться исходным кодом внутри ".h", пока пользователь не укажет требуемый тип данных затем он может быть сгенерирован для определенного типа данных, а затем скомпилирован

Я работаю с Visual studio 2010, если вы хотите разделить свои файлы на .h и .cpp, включите заголовок cpp в конце .файл H

Читайте также: