Почему для отслеживания ошибок лучше использовать компилятор
Кажется невероятным написать программу не допуская никаких ошибок. Тем не менее некоторые программисты способны на подобные чудеса, хотя, конечно, таких кудесников очень немного. Большинство из них, как и все нормальные люди делают ошибки. Поэтому нашлись программисты, которые разработали систему, способную помочь в отслеживании ошибок путем перехвата и исправления их на ранней стадии создания программ. Хотя сообщения об ошибках, выявленных компилятором, действуют на нервы, это намного лучше возникновения ошибок при выполнении программы. Если бы компилятор был менее дотошный, то велика вероятность, что ваша программа дала бы сбой в самый неподходящий момент, например во время презентации.
Ошибки компиляции, т.е. ошибки, выявленные на стадии компиляции, гораздо безобиднее ошибок выполнения, которые проявляются после запуска программы. Компилятор будет дотошно и однотипно сообщать об обнаруженной им ошибке. Напротив, ошибка выполнения может не обнаруживать себя до поры до времени, но потом проявиться в самый неподходящий момент. Поскольку ошибки компиляции заявляют о себе при каждом сеансе компиляции, то их легко идентифицировать и исправить, чтобы больше о них не вспоминать. Чтобы добиться создания программ, которые не станут со временем выкидывать фокусы, программист должен помочь компилятору в отслеживании ошибок, используя спецификаторы в объявлениях для предупреждения возможных сбоев.
Где следует распологать в программе объявления классов и определения методов
Каждая функция, объявленная в классе, должна иметь определение. Определение также называется выполнением функции. Подобно другим функциям, определение метода класса состоит из заголовка и тела функции.
Определение должно находиться в файле, который компилятор может легко найти. Большинство компиляторов C++ предпочитают, чтобы такой файл имел расширение .c или .cpp. В этой книге используется расширение .cpp, но вам стоит выяснить предпочтения собственного компилятора.
Примечание: Многие компиляторы полагают, что файлы с расширением .c содержат программы, написанные на языке С, а файлы с расширением .cpp — программы на C++. Вы можете использовать любое расширение, но именно .cpp сведет к минимуму возможные недоразумения в программах на C++.
Объявления классов можно поместить в один файл с программой, но это не считается хорошим стилем программирования. В соглашении, которого придерживаются многие программисты, рекомендуется помещать объявление в файл заголовка, имя которого обычно совпадает с именем файла программы, но в качестве расширения используются такие варианты, как .h, .hp или .hpp. В этой книге для имен файлов заголовков используется расширение .hpp, но вам стоит выяснить предпочтения собственного компилятора.
Например, можно поместить объявление класса Cat в файл с именем CAT, hpp, а определение методов класса — в файл с именем CAT .cpp. Затем нужно включить файл заголовка в код файла с расширением .cpp. Для этого в начале программного кода в файле CAT.cpp используется следующая команда:
#include "Cat.hpp"
Эта команда дает указание компилятору ввести содержимое файла CAT.hpp в данном месте программы. Результат выполнения команды include такой же, как если бы вы переписали с клавиатуры в это место программы полное содержимое соответствующего файла заголовка. Имейте в виду, что некоторые компиляторы чувствительны к регистру букв и требуют точного соответствия написания имен файла в директиве #include и на диске.
Зачем же нужно отделять файл заголовка с расширением .hpp от файла программы с расширением cpp, если мы все равно собираемся вводить содержимое файла заголовка назад в файл программы? Как показывает практика, большую часть времени клиентов вашего класса не волнуют подробности его выполнения. При чтении небольшого файла заголовка они получают всю необходимую информацию и могут игнорировать файл с подробностями выполнения этого класса. Кроме того, не исключено, что содержимое файла заголовка с расширением .hpp вам захочется включить не в один, а в несколько файлов программ.
Примечание: Объявление класса сообщает компилятору, что представляет собой этот класс, какие данные он содержит и какими функциями располагает. Объявление класса называется его интерфейсом, поскольку оно сообщает пользователю, как взаимодействовать с классом. Интерфейс обычно хранится в файле с расширением .hpp, который называется файлом заголовка.
Из определения функции компилятор узнает, как она работает. Определение функции называется выполнением метода класса и хранится в файле с расширением .cpp. Подробности выполнения класса касаются только автора класса. Клиентам же класса, т.е. частям программы, использующим этот класс, не нужно знать, как выполняются функции.
Выполнение с подстановкой
Можно выполнять подстановку методов с помощью ключевого слова inline точно так же, как это делалось с обычными функциями. Для этого ключевое слово inline нужно разместить перед типом возвращаемого значения. Например, определение подставляемой функции-члена GetWeight() имеет следующий вид:
inline intCat::GetWeight()
{
return itsweight; // возвращает переменную-член Weight
}
Можно также поместить определение функции в объявление класса, что автоматически делает такую функцию подставляемой:
class Cat
{
public:
int GetWeight() { return itsWeight; } // подставляемая функция
void SetWeight(int aWeight);
};
Обратите внимание на синтаксис определения функции GetWeight(). Тело подставляемой функции начинается сразу же после объявления метода класса, причем после круглых скобок нет никакой точки с запятой. Подобно определению обычной функции, определение метода начинается с открывающей фигурной скобки и оканчивается закрывающей фигурной скобкой. Как обычно, пробелы значения не имеют, и то же самое определение можно записать несколько иначе:
class Cat
{
public:
int GetWeight() const
{
return itsWeight;
} // подставляемая функция
void SetWeight(int aWeight);
};
В листингах 6.6 и 6.7 вновь создается класс Cat, но в данном случае объявление класса содержится в файле CAT.hpp, а выполнение — в файле CAT.cpp. Кроме того, в листинге 6.7 метод доступа к данным класса и метод Meow() являются подставляемыми.
Листинг 6.6. Объявление класса CAT в файле CAT.hpp
1: #include <iostream.h>
2: class Cat
3; {
4: public:
5: Cat (int initialAge);
6: ~Cat();
7: int GetAge() const { return itsAge;) // подставляемая функция!
8: void SetAge (int age) { itsAge = age;} // подставляемая функция!
9: void Meow() const { cout << "Мяу.\n";} // подставляемая функция!
10: private:
11: int itsAge;
12: };
Листинг 6.7. Выполнение масса Cat в файле CAT.cpp
1: // Пример использования подставляемых функций
2: // и включения файла заголовка
3:
4: #include "cat.hpp" // не забудьте включить файл заголовка!
5:
6:
7: Cat::Cat(int initialAge) //конструктор
8: {
9: itsAge = initialAge;
10: }
11:
12: Cat::~Cat() // деструктор, не выполняет никаких действий
13: {
14: }
15:
16: // Создаем виртуальную кошку, устанавливаем ее возраст, разрешаем
17: // ей мяукнуть, сообщаем ее возраст, затем снова "мяукаем" и изменяем возраст кошки.
18: int main()
19: {
20: Cat Frisky(5);
21: Frisky.Meow();
22: cout << "Frisky is а cat who is ";
23: cout << Frisky.QetAge() << " years old.\n";
24: Frisky.Meow();
25: Frisky.SetAge(7);
26: cout << "Now Frisky is " ;
27: cout << Frisky.GetAge() << " years old.\n";
28: return 0;
29: }
Результат:
Meow.
Frisky is а cat who is 5 years old.
Meow.
Now Frisky is 7 years old.
Анализ: Программа, представленная в листингах 6.6 и 6.7, аналогична программе из листинга 6.4 за исключением того, что три метода класса объявляются подставляемыми, а само объявление класса вынесено в файл заголовка CAT.hpp.
В строке 7 объявляется функция GetAge() и тут же следует определение ее выполнения. Строки 8 и 9 занимают объявления еще двух встроенных функций, но их определения содержатся в другом файле.
В строке 4 листинга 6.7 с помощью команды #include "cat.hpp" в программу включается содержимое файла CAT.hpp. Компилятор получает команду считать содержимое файла cat .hpp и ввести его в данный файл, начиная со строки 5.
Возможность встраивания файлов в другие файлы позволяет хранить объявления классов отдельно от их выполнения и использовать тогда, когда в этом возникает необходимость. Это стандартный прием при создании программ на языке C++. Обычно объявления классов хранятся в файле с расширением .hpp, который затем включается в соответствующий файл .cpp с помощью директивы #include.
В строках 18—29 в точности повторяется тело функции main() из листинга 6.4. Цель этого повторения — показать, что применение подставляемых вариантов функций не внесло изменений в использование этих функций.