В программировании С++ поощряется разделение класса на отдельные единицы. В понимании новичков эти единицы — это файлы. В литературе такие файлы называют единицами трансляции. Один класс разбивается на два файла и потом легко используется из других файлов путём подключения. В дальнейшем я буду писать "файлы", чтобы читателю было проще понять, о чем идет речь. В целом, разбитая на файлы программа общепринятым подходом представляет собой не менее трёх файлов исходного кода:
1. — файл интерфейса.
2. — файл реализации.
3. — любой файл, к которому подключается файл интерфейса
Файл интерфейса представляет собой подсказчика любому файлу, в котором подключается, помогающий в файле, в котором подключен, использовать все функции, описанные в файле реализации, описанном для себя.
Файл интерфейса — это заголовочный файл, который содержит описание класса. Это такое подобие прототипов функций, только получается что-то похожее на прототип класса. Пример:
C++
1
2
3
4
5
classMyClass{
int;//Здесь должно быть имя переменной
public:
intfoo();//Прототип функции foo для класса MyClass
};
В обычном понимании прототипов достаточно указывать только типы. Но у нас не прототип как таковой, а просто нечто напоминающее прототип и выглядящее как класс. Имена переменным в интерфейсе класса задавать нужно, поэтому надо так:
C++
1
2
3
4
5
classMyClass{
intvalue;//у переменной должно быть имя
public:
intfoo();
};
При ручном компилировании многофайловые проекты компилировать немного проще, чем при использовании интегрированных сред (Visual Studio, CodeBlocks и др.). В интегрированных средах с помощью меню создаётся проект, в проект добавляются все файлы реализаций, которые имеют отношение к создаваемой программе. Файл интерфейса в проект добавлять не нужно, потому что он подключается с помощью директивы #include
C++
1
#include "SomeFile.h" //самописные файлы в кавычки, а не в угловые скобки
Внутри подключаемого заголовочного файла описывают сам класс, но не что он делает. Что делает класс выносится в файл реализации. Поскольку файл реализации добавляется в проект, а класс описывается в подключаемом файле — система организации программы работает. Только не забывайте ни добавлять файлы реализации в проект, ни подключать заголовочный файл в нужное место.
Вкратце, сейчас вы должны были усвоить, что один класс делят на две составляющие (интерфейс, реализация). В совокупности, обе составляющие и есть класс. Объекты такого класса можно использовать из любого другого исходного кода программы, хранимого в третьих файлах.
Файл интерфейса по некоторому соглашению сохраняют с расширением *.h
Файл реализации по некоторому соглашению сохраняют с расширением *.cpp
Файлы реализаций добавляются в проект.
Файлы интерфейса подключаются с помощью #include
Когда создаём файл интерфейса и реализации, можно давать непохожие названия файлам, но это запутает нас самих, поэтому, чтобы сразу видеть отношении реализации к конкретному заголовочному файлу мы прежде всего даём одинаковые названия файлам, сохраняя разные расширения. Расширения помогают понять, заголовочный ли файл или файл представляет собой код реализации.
Напишем программу, представляющую собой обычный журнал. Напишем эту программу как многофайловый проект.
Любая программа требует планирования решения. Сначала надо прикинуть как решать, только потом приступать к написанию кода. Делаем анализ, продумываем будущий класс. Что нам нужно?
1. Обычно журнал имеет своё фирменное название, значит, создаваемый класс должен иметь такие особенности:
— хранить название предмета
— сохранять/устанавливать название предмета.
— должна быть возможность узнать название.
Вот такой минимум функциональности исходит из название предмета (хранение, установка, чтение). Можно описать интерфейс такого класса. Создается файл со следующим содержимым:
C++
1
2
3
4
5
6
7
8
9
10
11
//Листинг #1
#include <string> //Подключаем string для того, чтобы можно было использовать std::string
usingstd::string;//как using namespace std, только отдельно для string
classJournal{
public:
voidget_name();//функция-член, получить название журнала
voidset_name();//функция-член, записать название журнала
private:
stringname;//непосредственно название журнала
}
Должно быть видно, что в листинге #1 не описано поведение функций, есть голый класс, это и есть интерфейс класса. По описанию класса хорошо видно, какие ему предполагаются задачи. Этот файл сохраняется как заголовочный файл, т. е. с расширением *.h. Назовём этот файл Journal.h
Таким образом, сейчас должен иметься один сохраненный файл с описанием интерфейса класса. Следующий шаг — описание поведения функций. Создается ещё один файл, с таким же именем, но другим расширением: Journal.cpp, там пишутся основные функции класса. В файле Journal.cpp подключается заголовочный файл, описывающий класс. Тот заголовочный файл был нами написан в листинге #1. Дальше коды будут скрыты под спойлеры, нужно кликнуть по спойлеру, чтобы раскрыть.
Нужно это для того, чтобы файл реализации знал о строении класса, ради которого, собственно, и затеян. Здесь работает тот же принцип, который заложен в прототипы функций: когда прототип описан, компилятору проще ориентироваться по коду.
#include "Journal.h" //<-- там сокрыто описание класса GradeBook
voidGradeBook::get_name(){
//Здесь что-то описывают
}
На этой стадии (разумеется, по причине неопытности и сомнений) легко проверить, правильно ли всё было сделано. Достаточно в файл реализации временно добавить функцию main(), внутри которой объявить объект класса и попробовать заюзать описанный в классе какой-нибудь метод.
intmain(){//Временно добавили для проверки правильности
GradeBook journal;//объявляем объект
journal.get_name();//тестируем метод
}
Если всё сделано правильно, то при попытке скомпилировать и запустить проект ошибок не возникнет и выполнится программа, которая внешне ничего не будет делать. Но то, что она запускается, это уже факт того, что сделано всё верно. После того, как убедитесь, что всё работает, надо будет функцию main() убрать, мы её вписывали временно, только для проверки, что двигаемся правильно. Если у вас не скомпилировалось, первое, что нужно проверить, это точку с запятой у закрывающей класс в файле заголовка фигурной скобки. Забыть поставить закрывающую класс точку с запятой очень распространённая ошибка у новичков. В случае разбиения программы на файлы она, ошибка та, отслеживается немного сложнее, чем если бы всё писалось в одном файле. Если точка с запятой стоит, внимательно проверьте всё ещё раз. Тут нужно добиться того, чтобы показанное сработало.
Когда класс разделён на две части, убранные в файлы, такой класс можно использовать из разных программ. Сейчас будут произведены некоторые корректировки кода: будут написаны функции. Функция, задача которой обозначение названия журнала, будет принимать один параметр: название. Добавлю новый файл, из которого будет использоваться разбитый на две части написанный нами класс.
name=S;//В name из класса MyClass копируется значение из принятого параметра S
}
voidGradeBook::get_name(){
cout<<"Course name: "<<name<<"\n";//<--- Выводим name класса MyClass на экран
}
Корректировки внесены. Осталось добавить файл исходного кода, который это добро сможет использовать. Добавляем файл в проект. Название может быть любым.
#include "Journal.h" //<-- сокрыто описание класса
#include <string>
#include <iostream>
usingstd::string;
usingstd::cout;
intmain(){
//..........
stringS="ariphmetic";
GradeBook book;
book.set_name(S);
book.get_name();
cout<<'\n';
system("PAUSE");
}
Внутри файла реализации Journal.h можно описывать функции, которые могут иметь значение для программы, но классу не принадлежать. Такие функции можно будет использовать, но сами функции прямого доступа к внутренним закрытым данным класса доступа иметь не будут. Когда мы инклюдим какой-то файл, то в место, где написано #include, производится вставка текста подключаемого файла. Поэтому сразу можете представлять текст заголовочного файла в месте #include.
Некоторые программисты пишут классы, но скрывают детали реализации, давая только заголовочный файл и объектный файл. Объектный файл получается после компиляции файлов реализации и интерфейса. В таком случае получается, что пользователь (другой программист), использующий такие классы, может видеть общее представление класса и понимать, что делает класс, но не видеть деталей реализации. При этом подходе программист, который пользователь класса, должен подключить предоставленный заголовочный файл с помощью директивы #include и слинковать объектный файл, после чего при написании кода сможет полноценно использовать класс. Не нужно знать деталей реализации, чтобы успешно использовать класс.
На этом я заканчиваю статью. К сожалению, показанный пример очень сильно упрощён и многое, что можно было бы сюда написать, решено не писать, чтобы прочитавший смог усвоить показанную порцию информации. При наличии определённой фантазии вы можете попробовать развить проект. В простейшем случае добавьте в класс поле, обозначающее текст статьи, в более сложном попробуйте описать класс школьного журнала или что-то ещё. Задействуйте конструкторы, о которых знаете.
Добавить комментарий