Аннотация: В лекции рассматриваются вопросы сборки программы, состоящей из нескольких функций, расположенных в разных файлах, а также дополнительные обращения к функциям.
Теоретическая часть
Программа на языке С – это совокупность функций. Запуск любой программы начинается с запуска главной функции, содержащей в себе всю остальную часть программы. Внутри главной функции для реализации заданного алгоритма вызываются все другие необходимые функции. Часть функций создается самим программистом, другая часть – библиотечные функции – поставляется пользователю со средой программирования и используется в процессе разработки программ (например, printf() , sqrt() и др.).
Простейший метод использования нескольких функций требует их размещения в одном и том же файле. Затем выполняется компиляция этого файла, как если бы он содержал единственную функцию [17.1]. Другие подходы к решению этой проблемы существенно зависят от конкретной операционной системы (Unix-подобные системы, Windows , Macintosh).
Программа из нескольких файлов
Компиляторы операционных систем Windows и Macintosh представляют собой компиляторы, ориентированные на проекты [17.1]. Проект описывает ресурсы, используемые программой. Эти ресурсы включают файлы исходного программного кода.
Если поместить главную функцию main() в один файл , а определения собственной функции программиста – во второй файл , то первому файлу нужны прототипы функций. Для этого можно хранить прототипы функций в одном из заголовочных файлов.
Хороший тон в программировании рекомендует размещать прототипы функций и объявлять их константы в заголовочном файле [17.1]. Назначение отдельных задач отдельным функциям способствует улучшениям программы.
Функция может быть либо внешней ( по умолчанию), либо статической. К внешней функции доступ могут осуществлять функции из других файлов, в то же время статическая функция может использоваться только в файле, в котором она определена [17.1]. Например, возможны следующие объявления функций:
double gamma(); // внешняя функция по умолчанию static double beta(); extern double delta();
Функция gamma() и delta() могут использоваться функциями из других файлов, которые являются частью программы, тогда как beta() – нет. В силу этого применение функции beta() ограничено одним файлом, поэтому в других файлах можно использовать функции с тем же именем. Одна из причин использования класса статической памяти заключается в необходимости создания функций, приватных для конкретных модулей, благодаря чему во многих случаях удается избежать конфликта имен [17.1].
Обычная практика состоит в том, что при объявлении функции, определенной в другом файле, указывается ключевое слово extern . При этом просто достигается большая ясность , поскольку при объявлении функция и предполагается как extern , если только не задано ключевое слово static .
Одним из золотых правил для надежного программирования есть принцип «необходимости знать», или принцип минимально необходимой области видимости [17.1]. Рекомендуется держать всю внутреннюю работу каждой функции максимально закрытой по отношению к другим функциям, используя совместно только те переменные, без которых нельзя обойтись по логике программы. Другие классы памяти полезны, и ими можно воспользоваться. Однако всякий раз следует задать вопрос: а есть ли в этом необходимость?
Основы C++ — Лекция № 1 — Программа, состоящая из нескольких файлов. Компиляция и линковка
Память , использованная для хранения данных, которыми манипулирует программа , может быть охарактеризована продолжительностью хранения, областью видимости и связыванием [17.1]. Продолжительность хранения может быть статической, автоматической или распределенной.
Если продолжительность хранения статическая, память распределяется в начале выполнения программы и остается занятой на протяжении всего выполнения. Если продолжительность хранения автоматическая, то память под переменную выделяется в момент, когда выполнение программы входит в блок, в котором эта переменная определена, и освобождается, когда выполнение программы покидает этот блок. Если память выделяется, то она выделяется с помощью функции malloc() (или родственной функции) и освобождается посредством функции free() . Область видимости определяет, какая часть программы может получить доступ к данным. Переменные, определенные вне пределов функции , имеют область видимости в пределах файла и видимы в любой функции, определенной после объявления этой переменной. Переменная , определенная в блоке или как параметр функции, видима только в этом блоке и в любом из блоков, вложенных в этот блок.
Связывание описывает экстент (протяжение, пространство ), в пределах которого переменная , определенная в одной части программы, может быть привязана к любой другой части программы. Переменная с областью видимости в пределах блока, будучи локальной, не имеет связывания. Переменная с областью видимости в пределах файла имеет внутреннее или внешнее связывание . Внутреннее связывание означает, что переменная может быть использована в файле, содержащем ее определение . Внешнее связывание означает, что переменная может быть использована в других файлах.
Стандарт языка С поддерживает 4 спецификатора класса памяти [17.2]:
- extern
- static
- register
- auto
Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор класса памяти в объявлении всегда должен стоять первым [2].
Приведем характеристику спецификаторов классов памяти [17.2].
Спецификатор extern
В языке С при редактировании связей к переменной может применяться одно из трех связываний: внутреннее, внешнее или же не относящееся ни к одному из этих типов. В общем случае к именам функций и глобальных переменных применяется внешнее связывание. Это означает, что после компоновки они будут доступны во всех файлах, составляющих программу. К объектам, объявленным со спецификатором static и видимым на уровне файла, применяется внутреннее связывание, после компоновки они будут доступны только внутри файла, в котором они объявлены. К локальным переменным связывание не применяется и поэтому они доступны только внутри своих блоков.
Спецификатор extern указывает на то, что к объекту применяется внешнее связывание, именно поэтому они будут доступны во всей программе. Объявление ( декларация ) объявляет имя и тип объекта. Описание (определение, дефиниция) выделяет для объекта участок памяти, где он будет находиться. Один и тот же объект может быть объявлен неоднократно в разных местах, но описан он может быть только один раз.
Пример использования спецификатора extern при использовании глобальных переменных:
#include #include // Главная функция int main (void) < // объявление глобальных переменных extern int a, b; printf(«nt a = %d; b = %dn», a, b); printf(«n Press any key: «); _getch(); return 0; >// инициализация (описание) глобальных переменных int a = 33, b = 34;
Описание глобальных переменных дано за пределами главной функции main() . Если бы их объявление и инициализация встретились перед main() , то в объявлении со спецификатором extern не было бы необходимости.
При компиляции выполняются следующие правила:
- Если компилятор находит переменную, не объявленную внутри блока, он ищет ее объявление во внешних блоках.
- Если не находит ее там, то ищет среди объявлений глобальных переменных.
Спецификатор extern играет большую роль в программах, состоящих из многих файлов [17.3]. В языке С программа может быть записана в нескольких файлах, которые компилируются раздельно, а затем компонуются в одно целое. В этом случае необходимо как-то сообщить всем файлам о глобальных переменных программы. Самый лучший (и наиболее переносимый) способ сделать это – определить (описать) все глобальные переменные в одном файле и объявить их со спецификатором extern в остальных файлах, например, как это сделано в следующей программе:
#include #include #include «D:second.h» int x = 99, y = 77; char ch; void func1(void); int main(void) < ch = ‘Z’; func1(); printf(«n Press any key: «); _getch(); return 0; >void func1(void)
extern int x, y; extern char ch; void func22(void) < y = 100; >void func23(void) < x = y/10; ch = ‘R’; >
В программе первый файл – это основная часть программного проекта. Второй файл создан как текстовый файл (с помощью блокнота) с расширением *.h . Список глобальных переменных ( x, y, ch ) копируется из первого файла во второй, а затем добавляется спецификатор extern . Он сообщает компилятору, что имена и типы переменных, следующих далее, объявлены в другом месте. Все ссылки на внешние переменные распознаются в процессе редактирования связей. Подключение второго файла выполнено с указанием имени диска (D:), на котором расположен файл second.h.
Для подключения имени файла, созданного пользователем, его заключают в двойные кавычки.
Результат выполнения программы показан на рис. 17.1
Рис. 17.1. Результат выполнения программы, состоящей из двух файлов
В общем случае h -файл (например, second.h ) формируется редактором кода: надо создать заготовку обычным способом, очистить все поле редактора и записать в это поле необходимые данные (программный код созданной функции). Затем выполнить команду главного меню: File/Save as и выбрать для сохраняемого файла расширение .h в раскрывающемся списке типов сохраняемого файла: C++ Header Files (*.h; *.hh; *.hpp; *.hxx; *.inl; *.tlh; *.tli). Сохраненный файл с расширением .h следует подключить к проекту. Для этого потребуется в узле Solution Explorer навести курсор мыши к папке Header Files и правой кнопкой мыши выбрать Add – Existing Item сохраненный файл second.h. Затем с помощью оператора #include файл следует включить в основную программу.
Другой способ, реализуемый в Microsoft Visual Studio 2010, состоит в том, что сразу через пункт меню «File» выбрать «New» «File», и далее в списке Installed Templates выбрать Visual C++ Header File (.h). Откроется окно, показанное на рис. 17.2.
Рис. 17.2. Процесс создания нового файла с расширением .h
Далее в правом нижнем углу нажмем клавишу Open. Откроется пустое поле – заготовка для набора необходимого кода. По умолчанию этот файл имеет имя Header1.h. При повторном создании заголовочного файла это будет Header2.h и т.д. После написания кода можно сохранить этот заголовочный файл по желанию в любом каталоге с любым (допустимым) именем (а расширение остается .h ).
Источник: intuit.ru
C/C++ Многофайловые проекты
Рано или поздно, но проект разрастается и становится трудночитабельным, а также труднопереносимым в другие проекты, для упрощения работы используется разбиение «мега проекта» на несколько мелких, функционально законченных проектов.
Принцип в языках C/C++ примерно одинаков: создаются отдельные файлы с расширением .c/cpp для кода и файл заголовка .h для реализации.
Примечание 1: желательно все файлы с кодом хранить в папке src, а заголовочные в папке inc, можно отдельно прописать пути к файлам с исходниками, или указывать путь.
Для функций:
Создается файл sourse (файл с кодом/реализацией) имеющий расширение .c/.cpp
Например myfile.cpp
В нем напишем:
#include «incmyfile.h» //если прописали пути, то #include «myfile.h»
void PrintHello()
и отдельно файл с объявлениями функций (header файл),например myfile.h
Внутри него напишем:
#ifndef MYFILE_H #define MYFILE_H #include //библиотека, требуемая для работы функции. void PrintHello(); //прототип функции, ее реализация в файле myfile.cpp #endif // MYFILE_H
Теперь в файле main.cpp мы можем подключить header файл
#include «incmyfile.h»
И в функции main вызвать интересующую функцию:
int main()
Для классов:
В большинстве вред предусмотрено автоматическое создание файлов .cpp и h для классов + создание самого класса, что весьма удобно.
В случае с классом – в заголовочном файле нужно указывать его структуру, а реализацию методов выносить в .cpp файл
Например:
Header файл
#ifndef MYCLASS_H #define MYCLASS_H #include //библиотека, требуемая для работы функции. class MyClass < public: MyClass(); void PrintMsg(); >; #endif // MYCLASS_H
Файл с реализацией:
#include «myclass.h» MyClass::MyClass()<> void MyClass::PrintMsg()
Теперь в файле main.cpp мы можем подключить header файл, создать объект класса и вызвать метод.
int main()
Использование пространства имен:
Глава 16. Программы из нескольких файлов
Исходная программа на C++ может располагаться в одном файле, но это становится неудобными или даже невозможным для больших программ. В этом случае текст программ размещается в нескольких файлах. Это удобно, так как небольшой код можно легко понять и отладить. Кроме того, работу можно распределить между несколькими программистами.
Средством работы с программами, состоящими из нескольких файлов, являются проекты , объединяющие файлы, из которых состоит программа. В проект могут входить cpp -файлы с исходным кодом, obj — файлы, с откомпилированным кодом. Заголовочные h -файлы в проект не включаются, так как они вставляются в состав других файлов директивой препроцессора #include . При создании проекта необходимо обеспечить, чтобы функция или переменная, определенная в одном файле, была видима в другом. Это достигается с помощью объявлений переменных или функций перед их использованием. Переменные, определенные во внешнем файле, становятся видимыми в другом файле после их объявления с ключевым словом extern . При объявлении функций слово extern можно не использовать.
16.1. Работа с проектами
Проекты в Turbo C++ В среде TC новый проект создается командой меню Project, Open Project… , при этом появляется диалог Open Project File , рис.71. В поле Open Project File следует ввести имя создаваемого проекта, расширение PRJ к имени файла проекта будет добавлено автоматически при сохранении проекта. После нажатия кнопки OK откроется вначале пустое окно проекта, рис.72. Окно проекта можно закрыть, но сам проект при этом закрыт не будет. Проект закрывается командой Project, Close project . Окно проекта выводится на экран командой Window, Project . Когда активно окно, проекта клавишей Ins можно добавить в проект файл, клавишей Delete удалить файл из проекта. Те же действия можно выполнить соответствующими командами меню Project . Порядок
228 16 включения файлов в проект произволен. На рис.73 показано, что в проект SumNumb.PRJ включены два объектных файла ATOF.OBJ и GETLINE.OBJ , а также файл с исходным кодом SUMNUMB.CPP . Если в окне проекта выбрать файл и нажать Enter , данный файл будет открыт в окне редактирования. Рис.74.
Открытие и создание проекта Для открытия существующего проекта выполняется та же команда Project, Open , и выбирается имя проекта из списка Files , рис.75. Рис.76. Окно проекта Для создания из проекта рабочей программы нужно выполнить одну из команд меню Compile : Make или клавиша F9 – выполняется перекомпиляция измененных файлов и производится сборка программы; Link – производится сборка программы без перекомпиляции исходных файлов; Build all – перекомпилируются все файлы, входящие в проект. Рабочая программа будет иметь имя, совпадающее с именем проекта и расширение .exe .
Программы из нескольких файлов 229 Проекты в C++Builder Как уже говорилось, в C++Bulder проект создается для каждого приложения. Для управления составом проекта используются команды меню Project : Add to Project… – добавление в проект нового файла; Remove from Project… – удаление из проекта файла.
16.2. Область действия имен
Имена имеют переменные, функции, структуры, перечисления, классы, элементы классов и структур. Когда программы становятся большими, возрастает вероятность конфликта между именами. Зоной видимости глобальной переменной является файл. К такой переменной можно обратиться в любом месте программы от точки объявления до конца файла.
Зоной видимости локальной переменной является часть блока, в котором она определена от точки объявления до конца блока. Локальная переменная может перекрывать глобальную переменной с тем же именем. Пример такого перекрытия демонстрируется в приводимой ниже программе. Программа 50. Глобальные и локальные имена В программе использовано одно и то же имя i для двух переменных: глобальной и локальной.
// Файл Glob_Loc.cpp | ||
#include | ||
int i = 5; | // Глобальная переменная | |
int main() | ||
cout // Печать глобальной переменной | ||
int i = 1; | // Локальная переменная закрывает глобальную | |
cout // Печать локальной переменной | ||
cout // Печать глобальной переменной | ||
cin.get(); | ||
return 0; | ||
> |
Программа выводит:
i = 5 i = 1 ::i = 5
230 16 Для доступа к глобальной переменной, которая не видима из-за того, что перекрыта локальной переменной с таким же именем, можно использовать оператор разрешения зоны видимости (::) . Таким образом, ::i – это имя переменной из внешнего блока. В одной зоне видимости нельзя объявлять переменные с одинаковыми именами.
Единицей компиляции в языке C++ является файл. Файл может содержать различные объявления и определения, в том числе объявления и определения переменных и функций. В одном файле можно поместить определения нескольких функций. Запрещено помещать части одной функции в различных файлах.
В C++ запрещено определять функции внутри других функций, поэтому имя функции всегда глобально и может быть доступно в любой другой функции при соответствующем объявлении. Областью действия имени является часть программы, в которой это имя можно использовать. Для автоматических переменных, описанных в начале функции, областью действия является эта функция.
То же относится к параметрам функций, которые фактически являются локальными переменными. Имена функций и глобальных переменных видны от точки определения до конца файла без дополнительного объявления. При соответствующем объявлении функции и глобальные переменные могут быть видны в любом файле программы. Зону действия имен можно ограничить одним файлом, объявив имена статическими . Статические имена Рассмотрим схематический пример программы из двух файлов.
// Файл File_1.cpp | |
int N; | // Определение внешней переменной |
void f1() | // Определение функции f1 |
N = 1; | // Использование внешней переменной |
> | |
// Файл File_2.cpp | |
void f1(); | // Объявление (прототип) функции f1 |
extern int N; | // Объявление внешней переменной |
int f2() | // Определение функции f2 |
f1(); | // Обращение к функции f1 |
int k = N; | // Использование внешней переменной, k = 1 |
return k; | |
> |
Программы из нескольких файлов 231 Объявление функции f1 в файле File_2.cpp делает ее известной компилятору, который может сгенерировать ее правильный вызов. Объявление внешней переменной N позволяет использовать ее в любом месте файла File_2.cpp после точки объявления. Если в проект включены оба файла, редактор связей найдет код f1 , включит его в рабочую программу и сделает ссылку на него из f2 . Аналогично, редактору связей будет известен адрес N , по которому к ней можно обращаться. Недостаток данной программы состоит в том, что не так-то просто понять, почему значением k будет 1. При попытке определить еще одну внешнюю переменную с именем N в файле File_2.cpp возникает ошибка на этапе редактирования связей. Можно ограничить зону действия имен, сделав их статическими , для чего объявления нужно начинать словом static . Проиллюстрируем это примером.
// Файл File_1.cpp | |
static int N; | // Определение внешней переменной |
static void f1() | // Определение функции f1 |
N = 1; | // Использование внешней переменной |
> | |
// Файл File_2.cpp | |
void f1(); | // Объявление (прототип) функции f1 |
extern int N; | // Объявление внешней переменной |
int f2() | // Определение функции f2 |
f1(); | // Обращение к функции f1 |
int k = N; | // Использование внешней переменной, k = 1 |
return k; | |
> |
При компиляции файла File_2.cpp ошибок не будет, так как есть все необходимые объявления, но на этапе компоновки возникнут ошибки, так как компоновщик не найдет f1 и N , из-за ограниченности их зоны действия файлом File_1.cpp . Компоновщик выдаст сообщение об ошибке вида: Linker error… Программа 51. Сумматор чисел Напишем программу, которая вводит с клавиатуры числа с плавающей точкой и суммирует их.
Числа поступают с клавиатуры в виде строк, которые следует преобразовывать в числовые значения. Строки символов можно вводить стандартным образом с помощью cin >> , разделяя вводимые числа пробелами, но в данной программе
232 16 будем читать с клавиатуры строки символов, а затем преобразовывать строки в числа.
Для чтения строк напишем функцию int readline(), которая читает не более MAXLINE символов в массив line , извлекает из входного потока символ новой строки n , но не помещает его в формируемую строку, и возвращает длину прочитанной строки. Массив line и предельный размер строки MAXLINE зададим как внешние переменные. Функцию readline разместим в файле Readline.cpp . Для преобразования строк в числа с плавающей точкой напишем функцию strtof и поместим ее в отдельном файле StrTof.cpp . Главную функцию main , которая будет вводить и суммировать числа, поместим в файле SumNumb.cpp . Функция strtof преобразует строку цифр, в которой может быть знак и десятичная точка, отделяющая целую часть от дробной, в число двойной точности. Сначала устанавливается знак числа, затем вычисляется целая часть val . Если присутствует десятичная точка, продолжается вычисление val как целого и параллельно накапливается степень 10, на которую надо поделить val , чтобы сформировать правильное значение. // Файл Strtof.cpp #include // strtof: преобразование строки s в double double strtof(char s[]) <
double val, pow10; | |
int i, sign; | |
for(i = 0; isspace(s[i]); i++) | // Пропуск начальных пробелов |
; | |
sign = (s[i] == ‘-‘) ? -1: 1; | // Получение знака |
if(‘+’ == s[i] || ‘-‘ == s[i]) | // Если есть знак, |
i++; | // переходим к следующему символу |
for(val = 0.0; isdigit(s[i]); i++) | // Получаем целую часть |
val = val * 10 + s[i] — ‘0’; | |
if(‘.’ == s[i]) | // Если есть десятичная точка |
i++; | // Переход к дробной части |
for(pow10 = 1.0; isdigit(s[i]); i++) | |
val = val * 10 + s[i] — ‘0’; | |
pow10*=10.0; | // Степень 10 |
> | |
return sign * val / pow10; |
>
Программы из нескольких файлов 233 Для проверки того, что символ является пробельным, применяется функция isspace , а для проверки того, что символ есть цифра – isdigit . // Файл ReadLine.cpp #include // Объявление внешних переменных extern int MAXLINE; extern char line[]; /* readline: читает не более MAXLINE символов в массив line, возвращает длину строки */ int readline() < int c, i; for(i = 0; i < MAXLINE — 1 (c = cin.get()) != EOF c != ‘n’; ++i)
line[i] = c; |
line[i] = ‘ |