246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 26
Текст из файла (страница 26)
при вызове функции GetAge())происходитизменениеобъектаCat.Примечание:Используйте спецификатор const везде в объявлениях функций-членов, еслиони не должны изменять объект. Это позволит компилятору лучше отслеживать ошибки ипоможетвамприотладкепрограммы.Использовать const в объявлениях методов, не изменяющих объект, считается хорошимстилем программирования. Это позволяет компилятору лучше отслеживать ошибки еще дозапускапрограммынавыполнение.ЧемотличаетсяинтерфейсотвыполненияклассаКак уже упоминалось, клиенты — это составные части программы, которые создают ииспользуют объекты вашего класса.
Открытый интерфейс класса (объявление класса) можнопредставить себе в виде соглашения с этими клиентами, в котором указываются способывзаимодействияклиентовсклассом.Например, в объявлении класса Cat указывается, что программа-клиент можетинициализироватьлюбойвозрастобъектаэтогоклассаспомощьюфункциидоступаSetAge()ивозвратить это значение с помощью функции доступа GetAge(). При этом гарантируется, чтокаждый объект класса Cat сможет вывести сообщение Meow на экран с помощью функциичлена Meow(). Обратите внимание, что в открытом интерфейсе класса ничего не говорится озакрытой переменной-члене itsAge, которая используется при выполнении класса и не должнаинтересоватьклиентов.ЗначениевозрастаможновозвратитьизобъектаспомощьюGetAge()иустановить с помощью SetAge(), но сама переменная itsAge, в которой хранится это значение,скрытаотклиентов.Если объявить функцию GetAge() со спецификатором const, а именно этого требуютправила хорошего тона программирования, в соглашение также будет внесен пункт о том, чтофункциюGetAge()нельзяиспользоватьдляизменениязначенийобъектаклассаCat.В языке C++ осуществляется строгий контроль за типами данных, поэтому подобноесоглашение между классом и клиентами будет законом для компилятора, который сгенерируетошибку компиляции в случае нарушения этого соглашения.
В листинге 6.5 показан примерпрограммы,которуюнеудастсяскомпилироватьиз-занарушенияэтихсамыхсоглашений.Предупреждение:Листинг6.5некомпилируется!Листинг6.5.Примернарушениясоглашенийинтерфейса1://Примерошибкикомпиляции,связанной2://снарушениямисоглашенийинтерфейсакласса3:4:#include<iostream.h>//дляобъектаcout5:6:classCat7:{8:public:9:Cat(intinitialAge);10:~Cat();11:intGetAge()const;//методдоступаconst12:voidSetAge(intage);13:voidMeow();14:private:15:intitsAge;16:};17:18://конструкторклассаCat19:Cat::Cat(intinitialAge)20:{21:itsAge=initialAge;22:cout<<"Catconstructor\n";23:}24:Cat::~Cat()//деструктор,которыйневыполняетникакихдействий25:{26:cout<<"Catdestructor\n";27:}28://функцияGetAgeобъявленакакconst,29://номынарушаемэтоусловие!30:intCat::GetAge()const31:{32:return(itsAge++);//этонарушениесоглашенияинтерфейса!33:}34:35://определениефункцииSetAgeкакоткрытого36://методадоступакданнымкласса37:38:voidCat::SetAge(intage)39:{40://присваиваемпеременной-членуitsAge41://значениепереданногопарйметраage42:itsAge=age;43:}44:45://ОпределениеметодаMeow46://возвращаетvoid47://параметровнет48://используетсядлявыводанаэкрантекста"Meow"49:voidCat::Meow()50:{51:cout<<"Meow.\n";52:}53:54://демонстрируетразличныенарушения55://интерфейса,чтоприводиткошибкамкомпиляции56:intmain()57:{58:CatFrisky;//несоответствуетобьявлению59:Frisky.Meow();60:Frisky.Bark();//Нет,кошкинелают.61:Frisky.itsAge=7;//переменнаяitsAgeзакрыта62:return0;63:}Анализ:Какупоминалосьвыше,этапрограмманекомпилируется.Поэтомуиотсутствуютрезультатыееработы.Этупрограммубылозабавнописать,посколькувнееспециальнозакладывалисьошибки.Встроке11GetAge()объявляетсякакфункциядоступакданным-членамклассабезправаихизменения,начтоуказываетспецификаторconst.ОднаковтелефункцииGetAge(),аименновстроке32,выполняетсяприращениепеременной-членаitsAge.Апосколькуэтотметодобъявленкак const, он не имеет права изменять значение переменной itsAge.
Следовательно, во времякомпиляциипрограммынаэтойстрокебудетзафиксированаошибка.Встроке13объявляетсяметодMeow(),вэтотразбезиспользованияключевогословаconst.И хотя такое упущение не является ошибкой, это далеко не лучший стиль программирования.Еслиучесть,чтоэтотметоднедолженизменятьзначенияпеременных-членовклассаCat,тоегоследовалобыопределитьсоспецификаторомconst.В строке 58 показано определение объекта класса Cat с именем Frisky. В этом вариантепрограммы класс Cat имеет конструктор, который принимает в качестве параметрацелочисленное значение.
Это означает обязательность передачи параметра заданного типа.Посколькувстроке58никакойпараметрнепередается,компиляторзафиксируетошибку.Примечание:Если в классе объявляется какой-либо конструктор, компилятор в этомслучае не станет предлагать со своей стороны никакого другого конструктора даже еслиопределение объекта по форме не будет coответствовать объявленному конструктору. Вподобныхслучаяхкомпиляторпокажетсообщениеобошибке.В строке 60 вызывается метод Bark(). Этот метод вообще не был объявлен, следовательно,ниокакомегоиспользованиииречибытьнеможет.В строке 61 делается попытка присвоить переменной itsAge значение 7.
ПосколькупеременнаяitsAgeотноситсякчислузакрытыхданных-членов,топрикомпиляциипрограммыздесьбудетзафиксированопокушениеначастнуюсобственностькласса.ПочемудляотслеживанияошибоклучшеиспользоватькомпиляторКажется невероятным написать программу не допуская никаких ошибок. Тем не менеенекоторыепрограммистыспособнынаподобныечудеса,хотя,конечно,такихкудесниковоченьнемного.
Большинство из них, как и все нормальные люди делают ошибки. Поэтому нашлисьпрограммисты,которыеразработалисистему,способнуюпомочьвотслеживанииошибокпутемперехватаиисправленияихнараннейстадиисозданияпрограмм.Хотясообщенияобошибках,выявленныхкомпилятором,действуютнанервы,этонамноголучшевозникновенияошибокпривыполнениипрограммы.Еслибыкомпиляторбылменеедотошный,товеликавероятность,чтовашапрограммадалабысбойвсамыйнеподходящиймомент,напримервовремяпрезентации.Ошибки компиляции, т.е. ошибки, выявленные на стадии компиляции, гораздо безобиднееошибок выполнения, которые проявляются после запуска программы.
Компилятор будетдотошно и однотипно сообщать об обнаруженной им ошибке. Напротив, ошибка выполненияможетнеобнаруживатьсебядопорыдовремени,нопотомпроявитьсявсамыйнеподходящиймомент.Посколькуошибкикомпиляциизаявляютосебеприкаждомсеансекомпиляции,тоихлегко идентифицировать и исправить, чтобы больше о них не вспоминать.
Чтобы добитьсясоздания программ, которые не станут со временем выкидывать фокусы, программист долженпомочь компилятору в отслеживании ошибок, используя спецификаторы в объявлениях дляпредупреждениявозможныхсбоев.ГдеследуетраспологатьвпрограммеобъявленияклассовиопределенияметодовКаждая функция, объявленная в классе, должна иметь определение.
Определение такженазывается выполнением функции. Подобно другим функциям, определение метода классасостоитиззаголовкаителафункции.Определение должно находиться в файле, который компилятор может легко найти.Большинствокомпиляторов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 вамзахочетсявключитьневодин,авнесколькофайловпрограмм.Примечание:Объявление класса сообщает компилятору, что представляет собой этоткласс, какие данные он содержит и какими функциями располагает.