Заголовочные файлы C++, разделение кода
Я новичок в C++, и у меня было несколько общих вопросов о разделении кода. В настоящее время я создал небольшое приложение, все в одном файле. Теперь я хочу преобразовать это в отдельные файлы, чтобы они содержали похожий код или еще много чего. Мой настоящий вопрос сейчас, как я знаю, как разделить вещи? Какова невидимая граница, на которой код должен быть отделен?
Кроме того, какой смысл в заголовочных файлах? Можно ли пересылать объявления методов и классов, чтобы я мог использовать их в своем коде, прежде чем они будут включены компоновщиком во время компиляции?
Любое понимание методов или лучших практик было бы здорово, спасибо!
4 ответа
Заголовочные файлы должны содержать объявления классов и функций.
Исходные файлы содержат определения классов и функций.
Это стандартная практика (т. Е. Читать проще) иметь одно объявление для заголовочного файла и одно определение для исходного файла, хотя для небольших (читай проще вспомогательных) объектов вы иногда группируете их со связанными более существенными объектами.
Пример: меню класса
Menu.h: Contains the Menu declaration.
Menu.cpp: Contains the Menu definition.
Заголовочные файлы содержат объявления, так что вы можете включать их из нескольких исходных файлов, и, следовательно, каждый исходный файл имеет одно и то же определение каждого класса и функции.
Рассмотрим это так:
Если у вас не было заголовочных файлов, то вам нужно было бы иметь определения классов и / или функций (без) определений в каждом исходном файле, это означает копию одной и той же декларации в каждом файле. Таким образом, если вы изменяете класс, вам нужно вносить одинаковые изменения в каждый файл. Используя заголовочный файл, вы получаете объявление в одном месте и, следовательно, можете изменить только один объект.
Во-первых, вы не должны помещать в заголовки ничего, что не нужно было бы видеть другим файлом, кроме того, который ему нужен. Затем давайте определим что-то, что нам нужно ниже.
Отдел переводов
Единица перевода - это текущий код, который компилируется, и весь код, включенный в него, прямо или косвенно. Один переводчик переводит в один файл.o /.obj.
программа
Это все ваши файлы.o /.obj, связанные вместе в один двоичный файл, который может быть выполнен для формирования процесса.
Каковы основные моменты наличия разных единиц перевода?
- Сократите зависимости, чтобы при изменении одного метода одного класса вам не приходилось перекомпилировать весь код вашей программы, а только затронутый модуль перевода.
- Уменьшите возможные конфликты имен, используя локальные имена единиц перевода, которые не видны другим единицам перевода при их связывании.
Теперь, как вы можете разбить свой код на разные единицы перевода? Ответ таков: "Так что делай!", Но ты должен учитывать это в каждом конкретном случае. Это часто понятно, поскольку у вас есть разные классы, которые можно и нужно размещать в разных единицах перевода:
foo.hpp:
/* Only declaration of class foo we define below. Note that a declaration
* is not a definition. But a definition is always also a declaration */
class foo;
/* definition of a class foo. the same class definition can appear
in multiple translation units provided that each definition is the same
basicially, but only once per translation unit. This too is called the
"One Definition Rule" (ODR). */
class foo {
/* declaration of a member function doit */
void doit();
/* definition of an data-member age */
int age;
};
Объявите несколько свободных функций и объектов:
/* if you have translation unit non-local (with so-called extern linkage)
names, you declare them here, so other translation units can include
your file "foo.hpp" and use them. */
void getTheAnswer();
/* to avoid that the following is a definition of a object, you put "extern"
in front of it. */
extern int answerCheat;
foo.cpp:
/* include the header of it */
#include "foo.hpp"
/* definition of the member function doit */
void foo::doit() {
/* ... */
}
/* definition of a translation unit local name. preferred way in c++. */
namespace {
void help() {
/* ... */
}
}
void getTheAnswer() {
/* let's call our helper function */
help();
/* ... */
}
/* define answerCheat. non-const objects are translation unit nonlocal
by default */
int answerCheat = 42;
bar.hpp:
/* so, this is the same as above, just with other classes/files... */
class bar {
public:
bar(); /* constructor */
};
bar.cpp:
/* we need the foo.hpp file, which declares getTheAnswer() */
#include "foo.hpp"
#include "bar.hpp"
bar::bar() {
/* make use of getTheAnswer() */
getTheAnswer();
}
Обратите внимание, что имена в анонимном пространстве имен (как указано выше) не конфликтуют, так как они кажутся локальными единицами перевода. на самом деле это не так, у них просто уникальные имена, чтобы они не конфликтовали. если вы действительно хотите (есть небольшая причина) переводить локальные имена модулей (например, из-за совместимости с c, чтобы код C мог вызывать вашу функцию), вы можете сделать это следующим образом:
static void help() {
/* .... */
}
В ODR также говорится, что вы не можете иметь более одного определения любого объекта или не встроенной функции в одной программе (классы являются типами, а не объектами, поэтому к ним это не относится). Таким образом, вы должны следить за тем, чтобы не помещать не встроенные функции в заголовки или не помещать объекты типа "int foo;" в заголовках. Это приведет к ошибкам компоновщика тогда, когда компоновщик попытается связать блоки перевода, включая эти заголовки вместе.
Я надеюсь, что смогу немного помочь тебе. Теперь это был длинный ответ, где-то действительно есть ошибки. Я знаю, что единица перевода строго определяется другим способом (вывод препроцессора). Но я думаю, что это не добавило бы большого значения, чтобы включить это в вышеупомянутое, и это запутало бы вопрос. Пожалуйста, не стесняйтесь бить меня, если вы найдете настоящие ошибки:)
Решение о том, как разделить ваш код на разные классы / функции, является одной из основных задач программирования. Есть много разных рекомендаций о том, как это сделать, и я бы порекомендовал прочитать некоторые учебники по C++ и объектно-ориентированному дизайну, чтобы начать работу.
Некоторые основные рекомендации будут
- Соберите вещи, которые используются вместе
- Создание классов для объектов домена (например, файлов, коллекций и т. Д.)
Заголовочные файлы позволяют вам объявить класс или функцию, а затем использовать их в нескольких различных исходных файлах. Например, если вы объявляете класс в заголовочном файле
// A.h
class A
{
public:
int fn();
};
Затем вы можете использовать этот класс в нескольких исходных файлах:
// A.cpp
#include "A.h"
int A::fn() {/* implementation of fn */}
//B.cpp
#include "A.h"
void OtherFunction() {
A a;
a.fn();
}
Таким образом, заголовочные файлы позволяют вам отделить объявление от реализации. Если вы должны поместить все (объявление и реализацию) в исходный файл (например, A.cpp), попробуйте включить это во второй файл, например
// B.cpp
#include "A.cpp" //DON'T do this!
Затем вы можете скомпилировать B.cpp, но при попытке связать вашу программу компоновщик будет жаловаться, что у вас есть несколько определенных объектов - это потому, что у вас есть несколько копий реализации A.
Предложение: 1. Подготовьте дизайн для вашего приложения. 2. На основе дизайна создайте необходимые объекты, которые взаимодействуют друг с другом. 3. Рефакторинг или полное изменение существующего кода в соответствии с вновь созданным дизайном.
Заголовочные файлы предоставляют интерфейс для других классов, которые могут использовать его функциональность.