Как разделить программу на несколько файлов c
Если программа содержит много кода, то более оптимально было бы разнести отдельные части кода по отдельным файлам. Например, одни функции могут храниться в одном файле исходного кода, другие функции — в другом файле.
Например, определим файл sum.cpp , который будет иметь следующий код:
int sum(int a, int b)
Это функция вычисления суммы чисел.
Добавим еще один файл — , который будет содержать объявление функции sum:
int sum(int, int);
И также определим главный файл, который назовем app.cpp :
#include #include "sum.h" // подключаем файл sum.h int main() < int result < sum(5, 4)>; std::cout #include "sum.h"
Файл sum.h еще называется заголовочным файлом (header file), так как содержит объявление, заголовок функции. ПРичем в данном случае предполагается что все файлы располагаются в одном каталоге:

Можно было бы и не подключать файл sum.h и вообще не создавать его, а объявление функции поместить непосредственно в файл app.cpp. Но при изменении функции может потребоваться изменить и ее объявление. И если функция sum используется в нескольких файлах с исходным кодом, то в каждом из этих файлов придется менять ее объявление. В данном же случае достаточно изменить объявление функции в одном файле — sum.h.
При компиляции через g++ необходимо передать все файлы через пробел компилятору:
g++ app.cpp sum.cpp -o app
То же самое верно и для компиляции через Clang::
clang++ app.cpp sum.cpp -o app.exe
На выходе будет сгенерирован единый файл app.
При работе в Visual Studio заголовочные файлы обычны помещаются в каталог «Headers»:

А при компиляции все файлы автоматически компилируются в один.
Разделить код на несколько файлов C++
Только маленькие программы удобно хранить в одном файле. Разбиение кода на несколько файлов может упростить разработку и ускорить компиляцию.
Вынос части кода в соседний .cpp файл
Файл можно разделить на несколько частей и положить эти .cpp файлы в одну директорию.
Назовём главный файл, где Вы вызываете main() Main.cpp
Создайте файл Functions.cpp и перенесите туда все функции.
В Main.cpp укажите названия функций, которые вы перенесли и аргументы, которые они принимают.
#include using namespace std; double add(double x, double y); int main()
Создайте новый файл Functions.cpp который будет содержат только функции:
double add( double x, double y)
Header Files
Чтобы не перечислять все функции которые вы хотите использовать в главном файле можно упомянуть их в специальном Header файле с расширением .h
Назовём его Functions.h и подключим к главному с помощью директивы #include
#include #include «Functions.h» int main()
Файл Functions.h
#pragma once double add(double x, double y);
нужны, чтобы один и тот же .h файл не подгружался многократно. После первого подключения все остальные попытки буду проигнорированы.
Файл Functions.cpp остаётся без изменений.

Компиляция и запуск
Чтобы скомпилировать проект нужно перечислить все нужные .cpp файлы.
В данном примере это
g++ main.cpp Functions.cpp -o main
./main
Пример с перегрузкой
Добавим функций разного типа, чтобы убедиться в работоспособности кода
Если вы разди эксперимента хотите перегрузить функцию add() ничего в описанном выше подходе менять не нужно.
Просто добавьте перегруженную функцию в Functions.cpp и Functions.h а потом вызовите из main.cpp
Functions.cpp
bool test(bool x) < return x; >bool test(double x) < return x >0; > double add( double x, double y) < return x + y; >double add( double a, double b, double c)
#pragma once double add(double x, double y); double add(double a, double b, double c); bool test(bool x); bool test(double x); void test( int y);
g++ main.cpp Functions.cpp -o main
./main
1.3 + 4.5 is 5.8
1 + 2 + 3 is 6
Если файлов становится много — бывает полезно создать Makefile
Подробнее читайте в статьях
Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых
Как разделить программу на несколько файлов c
В статье вы научитесь создавать программу из нескольких файлов
Объявления и определения
Рассмотрим задачу: надо вывести числа от N до 1, при этом для нечётных чисел надо писать odd $ , а для чётных even $ . Вывод должен выглядеть так:
odd 11 even 10 odd 9 even 8 odd 7 even 6 odd 5 even 4 odd 3 even 2 odd 1
Ради интереса решим задачу с помощью рекурсии:
#include void printEvenRecursive(unsigned value) std::cout <"even " <value <std::endl; if (value > 1) printOddRecursive(value - 1); > > void printOddRecursive(unsigned value) std::cout <"odd " <value <std::endl; if (value > 1) printEvenRecursive(value - 1); > > int main() printOddRecursive(11); >
Увы программа не компилируется. В C++ каждая функция должна быть объявлена или определена до первого использования, но в нашем случае printEvenRecursive использует printOddRecursive, а printOddRecursive использует printEvenRecursive! Мы не можем поместить определение одной функции выше другой так, чтобы каждая функция была объявлена перед использованием.
Но кроме определений функций в C++ есть объявления функций
- Объявление (declaration) — это конструкция, которая зарезервирует идентификатор и опишет для компилятора его тип, но не раскроет деталей работы объявленной сущности.
- Определение (definition) — это конструкция, которая не только зарезервирует идентификатор, но и раскроет реализацию связанной с ним сущности
Объявление функции похоже на определение функции, только вместо тела стоит точка с запятой:
// Объявление функции sum int sum(); // Определение функции sum int sum(/* объявление параметра a*/ int a, /* объявление параметра b*/ int b) // Объявление переменной sum, // эта переменная инициализируется при объявлении const int sum = a + b; return sum; >
Теперь мы можем переписать рекурсивный пример, просто разместив определения обеих функций в начале файла.
Хорошим стилем считается заранее и в одном месте писать объявления всех функций, кроме main. Вы можете писать объявления в начале cpp-файла или в заголовке. О заголовках читайте ниже.
#include void printEvenRecursive(unsigned value); void printOddRecursive(unsigned value); void printEvenRecursive(unsigned value) std::cout <"even " <value <std::endl; if (value > 1) printOddRecursive(value - 1); > > void printOddRecursive(unsigned value) std::cout <"odd " <value <std::endl; if (value > 1) printEvenRecursive(value - 1); > > int main() printOddRecursive(11); >
Пишем свой заголовок
В C/C++ заголовок — это файл с расширением *.h или *.hpp , который включается в другие файлы директивой #include .
- Заголовок может иметь и другое расширение файла, но не стоит нарушать джентльменских соглашений: используйте h или hpp
- В заголовке пишут только объявления функций, а все определения можно и нужно помещать в cpp-файл
Создайте каталог, и разместите в нём файл print.h , в котором будут объявления функций. Скопируйте туда код, приведённый ниже.
Мы могли бы убрать из заголовка printEvenRecursive и printOddRecursive, сделав его чище, но сейчас для примера оставим.
#pragma once // Печатает числа от value до 1, добавляет слово // odd к нечётным числам и even к чётным void printDownTo1(unsigned value); // Для внутреннего использования void printEvenRecursive(unsigned value); // Для внутреннего использования void printOddRecursive(unsigned value);
Запомните правила хорошего тона:
- в начале заголовка пишите #pragma once , чтобы не получить ошибки компиляции при ромбовидном include, когда одни и те же сущности объявляются несколько раз (подробнее о pragma once и define guards…)
- заголовок должен быть чистым, ведь он повторно используется многими файлами проекта
- поэтому не пишите в заголовке using namespace . ; , иначе вы замусорите глобальное пространство имён целого проекта
Теперь создайте файл print.cpp , в котором будут реализованы функции из заголовка print.h . Скопируйте туда код, расположенный ниже.
Обратите внимание: #include находится в cpp-файле, а не в заголовке, потому что экспортированные из iostream сущности нужны только в реализации функций, но не нужны в объявлении.
#include "print.h" #include void printDownTo1(unsigned value) if (value % 2 == 0) printEvenRecursive(value); > else printOddRecursive(value); > > void printEvenRecursive(unsigned value) std::cout <"even " <value <std::endl; if (value > 1) printOddRecursive(value - 1); > > void printOddRecursive(unsigned value) std::cout <"odd " <value <std::endl; if (value > 1) printEvenRecursive(value - 1); > >
В конце создайте файл main.cpp и скопируйте в него код:
#include "print.h" int main() printDownTo1(11); >
Компоновка программы из нескольких файлов
Собирая программу из одного файла с помощью g++, вы на деле выполняли одним махом два действия:
- компиляцию, в ходе которой исходный текст файла превращается в логическую модель (AST) и затем превращается в объектный код, в котором машинные коды смешаны со ссылками на внешние функции
- компоновку, в ходе все внешние ссылки на функции заменяются на машинный код либо превращаются в ссылки на динамические библиотеки (dll/so, также известны как shared libraries)
Сейчас эти же действия мы выполним раздельно. Отройте терминал и введите последовательно две команды:
g++ -c main.cpp g++ -c print.cpp
Если код в обоих cpp-файлах синтаксически правилен, то компилятор создаст два файла: main.o и print.o . Эти файлы называют объектными файлами (object files). Именно они содержат машинный код, смешанный со ссылками на внешние функции.
Вы можете дизассемблировать эти файлы, чтобы посмотреть, во что компилятор превратил ваш код. Для этого выполните команду objdump -d main.o .
Теперь мы вызовем g++ для компоновки объектных файлов. На выходе мы получим исполняемый файл print_executable.exe
g++ main.o print.o -o print_recursive.exe
На деле компилятор не будет компоновать: он передаст эту задачу утилите ld. Вызывать утилиту ld вручную мы не станем, потому что потребуются дополнительные флаги, которые включают компоновку со стандартной библиотекой C++.
Компоновка программы в CMake
CMake прячет факт раздельной компиляции файлов. Чтобы в CMake скомпоновать программу, состоящую из нескольких частей, просто перечислите эти файлы в add_executable.
Удалите файл print_recursive.exe :
del print_recursive.exe
Создайте файл CMakeFiles.txt с одной строкой:
add_executable(print_recursive main.cpp print.h print.cpp)
Теперь выполните конфигурирование и сборку программы:
cmake -G "MinGW Makefiles" . cmake --build .
Мы почти закончили! Остался только один вопрос: почему в add_executable мы указали заголовок print.h , если он всё равно не компилируется сам по себе? Дело в том, что при любых изменениях в коде заголовка print.h вся программа должна быть пересобрана, но файл print.h сам по себе не компилируется. Добавление print.h в список исходников в CMake позволяет CMake следить за датой и временем модификации заголовка, чтобы решить, надо ли повторно собирать проект из-за изменений в заголовках.
PS-Group
- PS-Group
- sshambir@gmail.com
- ps-group
- image/svg+xml sshambir
Материалы для курсов в Институте Программных Систем и в Волгатехе
Как правильно разделить код с++ на несколько файлов
День добрый, возможно задам глупый вопрос, но интересует момент как правильно разбить код, с выносом в отдельный файл класса. В моем случае есть работаюшее консольное приложение которое выводит индефикаторы пк. Как правильно разбить его так чтобы в главном файле остался только вызов main() . Все что пробывал самостоятельно — не работает:) Сам код:
#include #include #include // SMBIOS Table Type numbers #define SMB_TABLE_BIOS 0 #define SMB_TABLE_SYSTEM 1 #define SMB_TABLE_BASEBOARD 2 #define SMB_TABLE_CHASSIS 3 #define SMB_TABLE_PROCESSOR 4 #define SMB_TABLE_MEMCTRL 5 #define SMB_TABLE_MEMMODULES 6 #define SMB_TABLE_PORTS 8 #define SMB_TABLE_SLOTS 9 #define SMB_TABLE_OEM_STRINGS 11 #define SMB_TABLE_SYS_CFG_OPTIONS 12 #define SMB_TABLE_MEM_ARRAY 16 #define SMB_TABLE_MEM_DEVICE 17 #define SMB_TABLE_END_OF_TABLE 127 // 64bit Word type typedef unsigned long long QWORD; /* * Structures */ typedef struct _RawSmbiosData < BYTE Used20CallingMethod; BYTE SMBIOSMajorVersion; BYTE SMBIOSMinorVersion; BYTE DmiRevision; DWORD Length; BYTE SMBIOSTableData[1]; >RAW_SMBIOS_DATA, * PRAW_SMBIOS_DATA; typedef struct _SmbiosStructHeader < BYTE Type; BYTE Length; WORD Handle; >SMBIOS_STRUCT_HEADER, * PSMBIOS_STRUCT_HEADER; // Structures typedef struct _NODE < wchar_t* Name; // Name of the node struct _NODE_ATT_LINK* Attributes; // Array of attributes linked to the node struct _NODE* Parent; // Parent node struct _NODE_LINK* Children; // Array of linked child nodes int Flags; // Node configuration flags >NODE, * PNODE; typedef struct _NODE_LINK < struct _NODE* LinkedNode; // Node attached to this node >NODE_LINK, * PNODE_LINK; typedef struct _NODE_ATT < wchar_t* Key; // Attribute name wchar_t* Value; // Attribute value string (may be null separated multistring if NAFLG_ARRAY is set) int Flags; // Attribute configuration flags >NODE_ATT, * PNODE_ATT; typedef struct _NODE_ATT_LINK < struct _NODE_ATT* LinkedAttribute; // Attribute linked to this node >NODE_ATT_LINK, * PNODE_ATT_LINK; /********************************************************************/ PRAW_SMBIOS_DATA GetSmbiosData() < DWORD bufferSize = 0; PRAW_SMBIOS_DATA smbios = NULL; // Get required buffer size bufferSize = GetSystemFirmwareTable('RSMB', 0, NULL, 0); if (bufferSize) < smbios = (PRAW_SMBIOS_DATA)LocalAlloc(LPTR, bufferSize); bufferSize = GetSystemFirmwareTable('RSMB', 0, (PVOID)smbios, bufferSize); >return smbios; > PSMBIOS_STRUCT_HEADER GetNextStructure(PRAW_SMBIOS_DATA smbios, PSMBIOS_STRUCT_HEADER previous) < PSMBIOS_STRUCT_HEADER next = NULL; PBYTE c = NULL; // Return NULL is no data found if (NULL == smbios) return NULL; // Return first table if previous was NULL if (NULL == previous) return (PSMBIOS_STRUCT_HEADER)(&smbios->SMBIOSTableData[0]); // Move to the end of the formatted structure c = ((PBYTE)previous) + previous->Length; // Search for the end of the unformatted structure (\0\0) while (true) < if ('\0' == *c && '\0' == *(c + 1)) < /* Make sure next table is not beyond end of SMBIOS data * (Thankyou Microsoft for ommitting the structure count * in GetSystemFirmwareTable */ if ((c + 2) < ((PBYTE)smbios->SMBIOSTableData + smbios->Length)) return (PSMBIOS_STRUCT_HEADER)(c + 2); else return NULL; // We reached the end > c++; > return NULL; > PSMBIOS_STRUCT_HEADER GetNextStructureOfType(PRAW_SMBIOS_DATA smbios, PSMBIOS_STRUCT_HEADER previous, DWORD type) < PSMBIOS_STRUCT_HEADER next = previous; while (NULL != (next = GetNextStructure(smbios, next))) < if (type == next->Type) return next; > return NULL; > PSMBIOS_STRUCT_HEADER GetStructureByHandle(PRAW_SMBIOS_DATA smbios, WORD handle) < PSMBIOS_STRUCT_HEADER header = NULL; while (NULL != (header = GetNextStructure(smbios, header))) if (handle == header->Handle) return header; return NULL; > void GetSmbiosString(PSMBIOS_STRUCT_HEADER table, BYTE index, LPWSTR output, int cchOutput) < DWORD i = 0; DWORD len = 0; #pragma warning(disable : 4996) wcscpy(output, L""); #pragma warning(enable : 4996) if (0 == index) return; char* c = NULL; for (i = 1, c = (char*)table + table->Length; '\0' != *c; c += strlen(c) + 1, i++) < if (i == index) < len = MultiByteToWideChar(CP_UTF8, 0, c, -1, output, cchOutput); break; >> > //вывод значения числового параметра таблицы SMBIOS по указанному смещению void PrintBiosValue(PRAW_SMBIOS_DATA smbios, DWORD type, DWORD offset, DWORD size) < PSMBIOS_STRUCT_HEADER head = NULL; PBYTE cursor = NULL; head = GetNextStructureOfType(smbios, head, type); if (NULL == head) < printf("PrintBiosValue Error!\n"); return; >cursor = ((PBYTE)head + offset); //value for (int i = 0; i < size; i++) < printf("%02x", (unsigned int)*cursor); cursor++; >printf("\n"); > //вывод значения строкового параметра таблицы SMBIOS по указанному смещению void PrintBiosString(PRAW_SMBIOS_DATA smbios, DWORD type, DWORD offset) < PSMBIOS_STRUCT_HEADER head; head = NULL; PBYTE cursor = NULL; WCHAR buf[1024]; head = GetNextStructureOfType(smbios, head, type); if (NULL == head) < printf("PrintString Error!\n"); return; >cursor = ((PBYTE)head + offset); BYTE val = *cursor; GetSmbiosString((head), *cursor, buf, 1024); // value wprintf(L"%s\n", buf); > int main() < PRAW_SMBIOS_DATA data = GetSmbiosData(); if (data == NULL) < printf("Can't get SMBIOS data!"); return 1; >printf("System UUID: "); PrintBiosValue(data, SMB_TABLE_SYSTEM, 8, 16); printf("Chassis serial: "); PrintBiosString(data, SMB_TABLE_CHASSIS, 7); printf("Motherboard serial: "); PrintBiosString(data, SMB_TABLE_BASEBOARD, 7); printf("CPUID: "); PrintBiosValue(data, SMB_TABLE_PROCESSOR, 8, 8); //Таблица SMBIOS содержит только 2 DWORD-значения CPUID из 4, но этого обычно достаточно getchar(); >