246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 62
Текст из файла (страница 62)
Массив представляет собой коллекциюобъектоводинаковоготипасфиксированнымчисломэлементов.Массивы никак не контролируют свой размер. Поэтому вполне возможно в программезаносить данные за пределы массива, что часто является причиной ошибок. Отсчет индексовмассиваначинаетсяс0.Частодопускаемойошибкойявляетсяуказаниеиндексаnдлямассивасразмеромn.Массивы могут быть одномерными или многомерными. Независимо от размерности, всемассивы базовых типов (например, int) или массивы объектов классов с конструкторами,заданнымипоумолчанию,могутбытьинициализированыприобъявлении.Объекты массивов или все массивы целиком можно сохранять как в стековой областипамяти,такивобластидинамическогообмена.Еслиудаляетсяобъектизобластидинамическойпамяти,незабудьтеустановитьквадратныескобкипослеключевогословаdelete[].Имена массивов представляют собой константные указатели на первый элемент массива.Чтобы получить доступ к другим элементам, имена массивов можно использовать вматематическихоперациях,какприработесобычнымиуказателями.Если размер коллекции объектов не известен во время компиляции программы, то дляподдержаниятакихколлекцийможноиспользоватьсвязанныесписки.Взявсвязанныйсписокзаоснову, можно разработать много других видов массивов и структур, автоматическивыполняющихсложныеоперации.Строки представляют собой массивы символов.
В C++ существуют дополнительныесредстваманипулированиятекстовымистроками,включаявозможностьвводавмассивстроки,взятойвдвойныекавычки.ВопросыиответыЧтопроизойдет,есливмассивиз24-хчленоввписатьзначениедля25-гоэлемента?Значениебудетдобавленовячейкупамяти,непринадлежащуюмассиву,чтоможетвызватьсерьезнуюошибкувработепрограммы.Чтопредставляютсобойэлементынеинициализированногомассива?Ячейки памяти, отведенные массиву но не инициализированные, могут содержать любуюинформацию,ранеесохраненнуювэтихячейках.Результатобращениявпрограммекэлементумассива,которыйнебылинициализирован,непредсказуем.Можнолисоздаватькомбинациимассивов?Да.Массивможетсодержатьуказательнадругой,болеекрупныймассив.Вслучаеработысо строками можно использовать некоторые стандартные функции, такие как strcat, чтобысоздаватькомбинациимассивовсимволов.Чемсвязанныеспискилучшемассивов?Массивывсегдаимеютфиксированныйразмер,тогдакакразмерсвязанногоспискаможетизменятьсядинамическивовремявыполненияпрограммы.Всегдалинужновклассестрокиспользоватьуказательchar*длясохранениясодержимогостроки?Нет.
Можно использовать любую область памяти, которая больше подходит для решенияконкретныхзадач.КоллоквиумВэтомразделепредлагаютсявопросыдлясамоконтроляиукрепленияполученныхзнанийи приводится несколько упражнений, которые помогут закрепить ваши практические навыки.Попытайтесьсамостоятельноответитьнавопросытестаивыполнитьзадания,апотомсверьтеполученные результаты с ответами в приложении Г. Не приступайте к изучению материаласледующей главы, если для вас остались неясными хотя бы некоторые из предложенных нижевопросов.Контрольныевопросы1.КакобратитьсякпервомуипоследнемуэлементаммассиваSomeArray[25]?2.Какобъявитьмногомерныймассив?3.Выполнитеинициализациюэлементовмногомерногомассива,созданногоприответенавопрос2.4.СколькоэлементовсодержитмассивSomeArray[10][5][20]?5.Каковомаксимальноечислоэлементов,которыеможнодобавитьвсвязанныйсписок?6.Можноливсвязанномспискеиспользоватьиндексы?7.Какимявляетсяпоследнийсимволвстроке"Сергей—хорошийпарень"?Упражнения1.Объявитедвухмерныймассив,которыйпредставляетполедляигрывкрестикиинолики.2.
Запишите программный код, инициализирующий значением 0 все элементы созданногопередэтиммассива.3.ОбъявитеклассузлаNode,поддерживающегоцелыечисла.4.Жучки:чтонеправильновследующейпрограмме?unsignedshortSomeArray[5][4];for(inti=0;i<4;i++)for(intj=0;j<5;]++)SomeArray[i][j]=i+j;5.Жучки:чтонеправильновследующейпрограмме?unsignedshortSomeArray[5][4];for(inti=0;i<=5;i++)for(intj=0;j<=4;j++)SomeArray[i][j]=0;День13-й.ПолиморфизмНапрошломзанятиивыузнали,каксоздаватьвиртуальныефункциивпроизводныхклассах.Наэтомзанятииречьпойдетобосновномсоставляющемядреполиморфизма—возможностиво время выполнения программы связывать специфические объекты производных классов суказателямибазовогокласса.Сегоднявыузнаете:•Чтотакоемножественноенаследованиеикакегоиспользовать•Чтопредставляетсобойвиртуальноенаследование•Чтотакоеабстрактныетипыданных•ЧтотакоечистыевиртуальныефункцииПроблемысодиночнымнаследованиемДавайтепродолжимработунадпрограммойоживотныхипредположим,чтовнейтеперьиспользуетсядвакласса,произведенныхоткакого-тообщегокласса.Один—Bird,посвященныйптицам, а другой — Mammals, посвященный млекопитающим.
Класс Bird содержит функциючлен Fly(), задающую возможность полета. Класс Mammals разбит на ряд подклассов, включаякласс лошадей — Horse. Класс содержит две функции- члена — Whinny() и Gallop(),объявляющихржаниеибеггалопомсоответственно.Но внезапно у вас возникает желание создать новый весьма интересный мифическийобъект—крылатогоПегаса(Pegasus),которыйбылбычем-товродегибридамеждуHorseиBird.Сразу предупредим, что, используя только одиночное наследование, вам сложно будетсправитьсясэтойзадачей.Если объявить объект Pegasus как член класса Bird, то для него станут недоступнымифункции Whinny() и Gallop().
Если Pegasus объявить как объект класса Horse, то ему станетнедоступнойфункцияFly().Первоерешениеможетсостоятьвтом,чтобыскопироватьметодFly()вклассHorse,послечеговэтомклассесоздатьобъектPegasus.Приэтомобакласса(BirdиHorse)будутсодержатьодинитотжеметодFly(),иприизмененииметодаводномклассенужнобудетнезабытьвнестисоответствующиеизменениявдругомклассе.Хорошо,еслитакихклассовбудеттолькодва.Есливам придется вносить изменения в программу через некоторое время после ее создания, будетсложновспомнить,вкакихещеклассахпредставленэтотметод.КогдавызахотитесоздатьспискиобъектовклассовBirdиHorse,передвамивозникнетещеодна проблема. Хотелось бы, чтобы объект Pegasus был представлен в обоих списках, но вданномслучаеэтоневозможно.Для решения возникшей проблемы можно использовать несколько подходов.
Haпример,можнопереименоватьслишком"лошадиный"методGallop()вболееобтекаемыйMove(),послечего заместить этот метод в объекте Pegasus таким образом, чтобы он выполнял функциюметодаFly().ВдругихобъектахклассаHorseметодMove()будетвыполнятьсятакже,какраньшевыполнялся метод Gallop(). Для объекта Pegasus можно даже определить, что короткиедистанцииондолженпреодолеватьметодомGallop(),адлинные—методомFly():Pegasus::Move(longdistance){if(distance>veryFar)fly(distance);elsegallop(distance);}Но и этот подход имеет ряд ограничений, поскольку объект уже не сможет летать накороткие дистанции и бегать на длинные. Может быть, все же просто перенести метод Fly() вклассHorse,какпоказановлистинге13.1?Проблемасостоитвтом,чтолошади,вбольшинствесвоем,летатьнеумеют,поэтомувовсехобъектахэтогокласса,заисключениемобъектаPegasus,данныйметоднедолженничеговыполнять.Листинг13.1.Умеютлилошадилетать...1://Листинг13.1.Умеютлилошадилетать...2://ФильтрацияметодаFly()вклассеHorse3:4:#include<iostream.h>5:6:classHorse7:{8:public:9:voidGallop(){cout<<"Galloping...\n";}10:virtualvoidFly(){cout<<"Horsescan'tfly.\n";}11:private:12:intitsAge;13:};14:15:classPegasus:publicHorse16:{17:public:18:virtualvoidFly(){cout<<"Icanfly!Icanfly!Icanfly!\n";}19:};20:21:constintNumberHorses=5;22:intmain()23:{24:Horse*Ranch[NumberHorses];25:Horse*pHorse;26:intchoice,i;27:for(i=0;i<NumberHorses;i++)28:{29:cout<<"(1)Horse(2)Pegasus:";30:cin>>choice;31:if(choice==2)32:pHorse=newPegasus;33:else34:pHorse=newHorse;35:Ranch[i]=pHorse;36:}37:cout<<"\n";38:for(i=0;i<NumberHorses;i++)39:{40:Ranch[i]->Fly();41:deleteRanch[i];42:}43:return0;44:}Результат:(1)Horse(2)Pegasus;1(1)Horse(2)Pegasus:2(1)Horse(2)Pegasus:1(1)Horse(2)Pegasus:2(1)Horse(2)Pegasus:1Horsescan'tfly.Icanfly!Icanfly!Icanfly!Horsescan'tfly.Icanfly!Icanfly!Icanfly!Horsescan'tfly.Анализ: Безусловно, эта программа будет работать ценой добавления в класс Horse редкоиспользуемогометодаFly().Этопроизошловстроке10.Дляобъектовданногоклассаэтотметодконстатируетфакт,чтолошадилетатьнеумеют.ИтолькодляобъектаPegasusметодзамещаетсявстроке18такимобразом,чтопривызовеегообъектзаявляет,чтоумеетлетать.Встроке24используетсямассивуказателейнаобъектыклассаHorse,спомощьюкоторогометод Fly() вызывается для разных объектов класса.
В зависимости от того, для какого изобъектоввданныймоментвызываетсяметод,программавыводитнаэкранразныесообщения.Примечание:Показанный выше пример программы был значительно сокращен, чтобывыделить именно те моменты, которые сейчас рассматриваются. Так, для простотыпрограммыизнеебылиудаленыконструкторивиртуальныедеструкторы.ПереносметодавверхпоиерархииклассовОчень часто для решения подобных проблем объявление метода переносят вверх поиерархическому списку классов, чтобы сделать его доступным большему числу производныхклассов. Но при этом есть угроза, что базовый класс уподобится кладовке, захламленнойстарыми вещами.
Такой подход делает программу громоздкой и нарушает саму идею иерархииклассов в C++, когда производные классы дополняют своими функциями небольшой наборобщихфункцийбазовогокласса.Противоречиесостоитвтом,чтоприпереносефункцииизпроизводныхклассоввверхпоиерархии в базовый класс трудно сохранить уникальность интерфейсов производных классов.Так,можнопредположить,чтоунашихдвухклассовBirdиHorseестьбазовыйклассAnimal,вкоторомсобраныфункции,общиедлявсехпроизводныхклассов,напримерфункцияпитания—Eat(). Перенеся метод Fly() в базовый класс, придется позаботиться о том, чтобы этот методвызывалсятольковнекоторыхпроизводныхклассах.ПриведениеуказателяктипупроизводногоклассаПродолжая держаться за одиночное наследование, эту проблему можно решить такимобразом, что метод Fly() будет вызываться только в случае, если указатель в данный моментсвязансобъектомPegasus.Дляэтогонеобходимоиметьвозможностьобратитьсякуказателюиопределить, на какой объект он указывает в текущий момент.
Такой подход известен как RTTI(RuntimeTypeIdentification—определениетипапривыполнении).НовозможностьвыполненияRTTIбыладобавленатольковпоследниеверсиикомпиляторовC++.Если ваш компилятор не поддерживает RTTI, можете реализовать его собственнымисилами, добавив в программу метод, который возвращает перечисление типов каждого класса.Возвращенное значение можно анализировать во время выполнения программы и допускатьвызовметодаFly()тольковтомслучае,есливозвращаетсязначениеPegasus.Примечание:НезлоупотребляйтеиспользованиемRTTIвсвоихпрограммах,таккакэтотподходрассматриваетсякакаварийныйисвидетельствуетотом,чтоструктурапрограммыизначальнобылаплохопродумана.Профессиональныйпрограммистпредпочтетиспользованиевиртуальных функций, шаблонов или множественного наследования, речь о котором пойдетнижевэтойглаве.Чтобы вызвать метод Fly(), необходимо во время выполнения изменить тип указателя,определив,чтоонсвязаннесобъектомHorse,асобъектомпроизводногоклассаPegasus.Этотспособ называют приведением вниз, поскольку объект базового класса Horse приводится кобъектупроизводногоклассаPegasus.Этот подход, хоть и с неохотой, теперь уже официально признан в C++, и для егореализациидобавленновыйоператор—dynamic_cast.ЕсливпрограммесоздануказательнаобъектыбазовогоклассаHorseиемуприсвоенадресобъекта производного класса Pegasus, то такой указатель можно использовать полиморфно.Чтобы обратиться к методу производного класса, нужно динамически подменить указательбазовогоклассауказателемпроизводногоклассаспомощьюоператораdynamic_cast.Вовремявыполненияпрограммыпроисходиттестированиеуказателябазовогокласса.Еслиустанавливается, что текущий объект, на который ссылается указатель базового класса, вдействительности является объектом производного класса, то с этим объектом связываетсяуказатель производного класса.