Зачем использовать #ifndef CLASS_H и #define CLASS_H в файле .h, но не в .cpp?

Я всегда видел, как люди пишут

class.h

#ifndef CLASS_H
#define CLASS_H

//blah blah blah

#endif

Вопрос в том, почему они также не делают этого для файла .cpp, который содержит определения для функций класса?

Скажем, у меня есть main.cpp, а main.cpp включает class.h. Файл class.h ничего не импортирует, так как main.cpp знает, что находится в class.cpp?

Ответ 1

Во-первых, чтобы обратиться к первому запросу:

Когда вы видите это в файле .h:

#ifndef FILE_H
#define FILE_H

/* ... Declarations etc here ... */

#endif

Это препроцессорный метод предотвращения включения файла заголовка несколько раз, что может быть проблематичным по разным причинам. Во время компиляции вашего проекта скомпилируется каждый .cpp файл (обычно). Проще говоря, это означает, что компилятор возьмет ваш файл .cpp, откройте все файлы #included, объедините их все в один массивный текстовый файл, а затем выполните анализ синтаксиса и, наконец, он преобразует его на некоторый промежуточный код, оптимизировать/выполнять другие задачи и, наконец, сгенерировать сборку для целевой архитектуры. Из-за этого, если файл #included несколько раз под одним файлом .cpp, компилятор будет дважды добавлять содержимое своего файла, поэтому, если в нем есть определения, вы получите ошибку компилятора сообщая вам, что вы переопределили переменную. Когда файл обрабатывается на этапе препроцессора в процессе компиляции, при первом достижении его содержимого первые две строки будут проверять, был ли FILE_H определен для препроцессора. Если нет, он определит FILE_H и продолжит обработку кода между ним и директивой #endif. В следующий раз, когда содержимое файла просматривается препроцессором, проверка на FILE_H будет ложной, поэтому она немедленно сканирует ее до #endif и продолжит после нее. Это предотвращает ошибки переопределения.

И для решения вашей второй проблемы:

В программировании на С++ в качестве общей практики мы разделяем разработку на два типа файлов. Один из них имеет расширение .h, и мы называем это "заголовочным файлом". Обычно они предоставляют декларацию функций, классов, структур, глобальных переменных, typedefs, макросов и определений предварительной обработки и т.д. В принципе, они просто предоставляют вам информацию о вашем коде. Затем мы имеем расширение .cpp, которое мы называем "файлом кода". Это даст определения для этих функций, членов класса, любых членов структуры, которым нужны определения, глобальных переменных и т.д. Таким образом, файл .h объявляет код и файл .cpp реализует эту декларацию. По этой причине мы обычно во время компиляции компилируем каждый файл .cpp в объект, а затем связываем эти объекты (потому что вы почти никогда не видите один файл .cpp, включая другой .cpp).

Как эти внешние решения разрешены, это работа для компоновщика. Когда ваш компилятор обрабатывает main.cpp, он получает объявления для кода class.cpp, включая class.h. Ему нужно только знать, как выглядят эти функции или переменные (что дает вам объявление). Поэтому он компилирует ваш файл main.cpp в некоторый объектный файл (назовите его main.obj). Аналогично, class.cpp скомпилируется в файл class.obj. Чтобы создать окончательный исполняемый файл, вызывается компоновщик, чтобы связать эти два объектных файла вместе. Для любых нерешенных внешних переменных или функций компилятор помещает заглушку, где происходит доступ. Затем компоновщик берет этот заглушку и ищет код или переменную в другом указанном объектном файле, и если он найден, он объединяет код из двух объектных файлов в выходной файл и заменяет заглушку конечным местоположением функции или переменная. Таким образом, ваш код в main.cpp может вызывать функции и использовать переменные в class.cpp ЕСЛИ И ТОЛЬКО ЕСЛИ ОНИ ЗАЯВЛЯТЬСЯ В class.h.

Надеюсь, это было полезно.

Ответ 2

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

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

Вы, кажется, интерпретировали включенных охранников как ту же функцию, что и выражения import на других языках (например, Java); это не случай, однако. Сама #include примерно эквивалентна import на других языках.

Ответ 3

Это не происходит - по крайней мере, во время фазы компиляции.

Перевод программы С++ из исходного кода в машинный код выполняется в три этапа:

  • Предварительная обработка. Препроцессор анализирует весь исходный код для строк, начинающихся С#, и выполняет директивы. В вашем случае содержимое вашего файла class.h вставляется вместо строки #include "class.h. Поскольку вы можете включить в свой файл заголовка в нескольких местах, предложения #ifndef избегают дублирования объявлений-ошибок, так как директива препроцессора undefined только при первом включении заголовочного файла.
  • Компиляция. Компилятор теперь переводит все предварительно обработанные файлы исходного кода в двоичные файлы объектов.
  • Связывание - ссылки Linker (отсюда и название) вместе с объектными файлами. Ссылка на ваш класс или один из его методов (который должен быть объявлен в class.h и определен в class.cpp) разрешен к соответствующему смещению в одном из объектных файлов. Я пишу "один из ваших объектных файлов", так как ваш класс не нужно определять в файле с именем class.cpp, он может быть в библиотеке, которая связана с вашим проектом.

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

Ответ 4

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

Чтобы использовать что-то, вам нужно только знать его декларацию, а не определение. Только линкер должен знать определение.

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

Также вы имеете в виду #include и не импортируете.

Ответ 5

main.cpp не должен знать, что находится в class.cpp. Он просто должен знать декларации функций/классов, которые он использует, и эти объявления находятся в классе .h.

Связующие ссылки между местами, в которых используются функции/классы, объявленные в class.h, и их реализации в class.cpp

Ответ 6

Это делается для файлов заголовков, чтобы содержимое отображалось только в каждом предварительно обработанном исходном файле, даже если оно включалось более одного раза (обычно из-за того, что оно включалось из других файлов заголовков). При первом включении символ CLASS_H (известный как защитник включения) еще не определен, поэтому все содержимое файла включено. Выполнение этого определяет символ, поэтому, если он включен снова, содержимое файла (внутри блока #ifndef/#endif) пропускается.

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

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

Ответ 7

.cpp файлы не включаются (используя #include) в другие файлы. Поэтому им не нужно включать охрану. Main.cpp будет знать имена и подписи класса, который вы реализовали в class.cpp, только потому, что вы указали все это в class.h - это цель файла заголовка. (Вы должны убедиться, что class.h точно описывает код, который вы реализуете в class.cpp.) Исполняемый код в class.cpp будет доступен для исполняемого кода в Main.cpp благодаря усилиям линкер.

Ответ 8

из-за того, что Headerfiles определяет, что класс содержит (Members, data-structure) и cpp файлы, реализующие его.

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

Ответ 9

Обычно предполагается, что модули кода, такие как .cpp файлы, компилируются один раз и связаны с несколькими проектами, чтобы избежать ненужной повторной компиляции логики. Например, g++ -o class.cpp создаст class.o, который затем можно связать с несколькими проектами с помощью g++ main.cpp class.o.

Мы могли бы использовать #include как наш компоновщик, как вы, кажется, подразумеваете, но это было бы просто глупо, когда мы знаем, как правильно связать наш компилятор с меньшими нажатиями клавиш и менее расточительным повторением компиляции, а не нашим кодом с большим количеством нажатий клавиш и более расточительным повторением компиляции...

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

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