Принадлежность и наследование

Принадлежность и наследование

Беря во внимание сложность значимость отношений наследования, нет ничегоудивительного в том, что нередко их некорректно понимают и используютсверх меры. Если класс D описан как общий производный откласса B, то нередко молвят, что D есть B: class B { /* ... */ ; class D : public B /* ... */ }; // D сорта B По другому это можно сконструировать Принадлежность и наследование так: наследование - этоотношение "есть", либо, более точно для классов D и B, наследование- это отношение D сорта B. В отличие от этого, если класс Dсодержит в качестве члена другой класс B, то молвят, чтоD "имеет" B: class D { // D имеет B // ... public: B b; // ... }; Другими словами, принадлежность - это отношение "иметь" илидля классов Принадлежность и наследование D и B просто: D содержит B. Имея два класса B и D, как выбирать меж наследованием ипринадлежностью? Разглядим классы самолет и мотор.Новенькие обычноспрашивают: будет ли неплохим решением сделать класс самолетпроизводным от класса мотор. Это нехорошее решение, посколькусамолет не "есть" мотор, самолет "имеет" мотор. Следует подойтик этому вопросу Принадлежность и наследование, рассмотрев, может ли самолет "иметь" два илибольше моторов. Так как это представляется полностью вероятным(даже если мы имеем дело с программкой, в какой все самолетыбудут с одним мотором), следует использовать принадлежность, ане наследование. Вопрос "Может ли он иметь два..?" оказываетсяудивительно полезным в почти всех непонятных случаях. Как обычно Принадлежность и наследование,наше изложение затрагивает неуловимую суть программирования.Если б все классы было так же просто представить, как самолет имотор, то было бы просто избежать и очевидных ошибок типа той,когда самолет определяется как производное от класса мотор. Но,такие ошибки довольно часты, в особенности у тех, ктосчитает наследование еще одним механизмом Принадлежность и наследование для сочетанияконструкций языка программирования. Невзирая на удобство илаконичность записи, которую предоставляет наследование, егонадо использовать только для выражения тех отношений,которые верно определены в проекте. Разглядим определения: class B { public: virtual void f(); void g(); }; class D1 { // D1 содержит B public: B b; void f(); // не переопределяет b.f Принадлежность и наследование() }; void h1(D1* pd) { B* pb = pd; // ошибка: нереально преобразование D1* в B* pb = &pd->b; pb->q(); // вызов B::q pd->q(); // ошибка: D1 не имеет член q() pd->b.q(); pb->f(); // вызов B::f (тут D1::f не переопределяет) pd->f(); // вызов D1::f Принадлежность и наследование } Направьте внимание, что в этом примере нет неявного преобразованиякласса к одному из его частей, и что класс, содержащий вкачестве члена другой класс, не переопределяет виртуальныефункции этого члена. Тут очевидное отличие от примера, приведенногониже: class D2 : public B { // D2 есть B public: void f(); // переопределение B::f() }; void h2(D2* pd) { B* pb Принадлежность и наследование = pd; // нормально: D2* неявно преобразуется в B* pb->q(); // вызов B::q pd->q(); // вызов B::q pb->f(); // вызов виртуальной функции: воззвание к D2::f pd->f(); // вызов D2::f } Удобство записи, продемонстрированное в примере с классом D2, посравнению с записью в примере с классом Принадлежность и наследование D1, является предпосылкой, покоторой таким наследованием злоупотребляют. Но следует держать в голове,что существует определенная плата за удобство записи в видевозросшей зависимости меж B и D2 (см. $$12.2.3). А именно,просто запамятовать о неявном преобразовании D2 в B. Если только такиепреобразования не относятся к семантике ваших классов, следуетизбегать описания производного класса Принадлежность и наследование в общей части. Если класспредставляет определенное понятие, а наследование используетсякак отношение "есть", то такие преобразования обычно как раз то,что необходимо. Но, бывают такие ситуации, когда желательноиметь наследование, но нельзя допускать преобразования. Рассмотримзадание класса cfield (controled field - управляемое поле), который,кроме всего остального, дает возможность держать под контролем на стадиивыполнения Принадлежность и наследование доступ к другому классу field. На 1-ый взор кажетсясовершенно правильным найти класс cfield как производный откласса field: class cfield : public field { // ... }; Это выражает тот факт, что cfield, вправду, есть сорта field,упрощает запись функции, которая употребляет член части field классаcfield, и, что самое главное, позволяет в классе cfieldпереопределять Принадлежность и наследование виртуальные функции из field. Загвоздка тут в том,что преобразование cfield* к field*, встречающееся в определениикласса cfield, позволяет обойти хоть какой контроль доступа к field: void q(cfield* p) { *p = "asdf"; // воззвание к field контролируется // функцией присваивания cfield: // p->cfield::operator=("asdf") field* q = p; // неявное преобразование cfield* в Принадлежность и наследование field* *q = "asdf"; // приехали! контроль обойден } Можно было бы найти класс cfield так, чтоб field был его членом,но тогда cfield не может переопределять виртуальные функции field.Наилучшим решением тут будет внедрение наследования со спецификациейprivate (личное наследование): class cfield : private field { /* ... */ } С позиции проектирования, если не учесть (время от Принадлежность и наследование времени принципиальные) вопросыпереопределения, личное наследование эквивалентно принадлежности.В данном случае применяется способ, при котором класс определяетсяв общей части как производный от абстрактного базисного класса заданиемего интерфейса, также определяется при помощи личного наследования отконкретного класса, задающего реализацию ($$13.3). Посколькунаследование, применяемое как личное, являетсяспецификой реализации, и оно не отражается в типе Принадлежность и наследование производного класса,то его время от времени именуют "наследованием по реализации", и оноявляется контрастом для наследования в общей части, когда наследуетсяинтерфейс базисного класса и допустимы неявные преобразования кбазовому типу. Последнее наследование время от времени именуют определениемподтипа либо "интерфейсным наследованием". Для предстоящего обсуждения способности выбора наследованияили принадлежности разглядим, как представить в диалоговойграфической Принадлежность и наследование системе свиток (область для прокручивания в нейинформации), и как привязать свиток к окну на дисплее. Потребуютсясвитки 2-ух видов: горизонтальные и вертикальные. Это можнопредставить при помощи 2-ух типов horizontal_scrollbar иvertical_scrollbar либо при помощи 1-го типа scrollbar, которыйимеет аргумент, определяющий, является размещение вертикальнымили горизонтальным. 1-ое решение подразумевает, что есть Принадлежность и наследование ещетретий тип, задающий просто свиток - scrollbar, и этот типявляется базисным классом для 2-ух определенных свитков. Второерешение подразумевает дополнительный аргумент у типа scrollbar иналичие значений, задающих вид свитка. К примеру, так: enum orientation { horizontal, vertical }; Как мы остановимся на одном из решений, определитсяобъем конфигураций, которые придется внести в систему. Допустим Принадлежность и наследование, вэтом примере нам будет нужно ввести свитки третьего вида. Вначалепредполагалось, что могут быть свитки только 2-ух видов (ведьвсякое окно имеет только два измерения), но в этом примере,как и в почти всех других, вероятны расширения, которые возникаюткак вопросы перепроектирования. К примеру, может появитьсяжелание использовать "управляющую кнопку" (типа мыши) заместо свитковдвух Принадлежность и наследование видов. Такая кнопка задавала бы прокрутку в различныхнаправлениях зависимо от того, в какой части окна нажалее юзер. Нажатие посреди верхней строки должновызывать "прокручивание ввысь", нажатие посреди левого столбца -"прокручивание на лево", нажатие в левом верхнем углу -"прокручивание ввысь и на лево". Такая кнопка не являетсячем-то Принадлежность и наследование необыкновенным, и ее можно рассматривать как уточнение понятиясвитка, которое в особенности подходит для тех областей приложения,которые связаны не с обыкновенными текстами, а с более сложнойинформацией. Для прибавления управляющей кнопки к программке, использующейиерархию из 3-х свитков, требуется добавить очередной класс, ноне необходимо поменять программку, работающую со старенькыми свитками: свитокгоризонтальный Принадлежность и наследование_свиток вертикальный_свиток управляющая_кнопкаЭто положительная сторона "иерархического решения". Задание ориентации свитка в качестве параметра приводит кзаданию полей типа в объектах свитка и использованию переключателейв теле функций-членов свитка. Другими словами, пред нами обычнаядилемма: выразить данный нюанс структуры системы с помощьюопределений либо воплотить его в операторной части программки.1-ое решение наращивает Принадлежность и наследование объем статических проверок и объеминформации, над которой могут работать различные вспомогательныесредства. 2-ое решение откладывает проверки на стадию выполненияи разрешает поменять тела отдельных функций, не изменяя общуюструктуру системы, какой она представляется с точки зрениястатического контроля либо вспомогательных средств. В большинствеслучаев, лучше 1-ое решение. Положительной стороной решения с единым типом свитка Принадлежность и наследование будет то,что просто передавать информацию о виде подходящего нам свитка другойфункции: void helper(orientation oo) { //... p = new scrollbar(oo); //... } void me() { helper(horizontal); } Таковой подход позволяет на стадии выполнения просто перенастроить свитокна другую ориентацию. Навряд ли это очень принципиально в примере со свитками,но это возможно окажется Принадлежность и наследование значимым в схожих примерах. Сущность в том,что всегда нужно делать определенный выбор, а это нередко тяжело. Сейчас разглядим как привязать свиток к окну. Если считатьwindow_with_scrollbar (окно_со_свитком) как нечто, что являетсяwindow и scrollbar, мы получим схожее: class window_with_scrollbar : public window, public scrollbar Принадлежность и наследование { // ... }; Это позволяет хоть какому объекту типа window_with_scrollbar выступатьи как window, и как scrollbar, но от нас требуется решениеиспользовать только единственный тип scrollbar. Если, с другой стороны, считать window_with_scrollbar объектомтипа window, который имеет scrollbar, мы получим такое определение: class window_with_scrollbar : public window { // ... scrollbar* sb; public Принадлежность и наследование: window_with_scrollbar(scrollbar* p, /* ... */) : window(/* ... */), sb(p) { // ... } // ... }; Тут мы можем использовать решение со свитками 3-х типов. Передачасамого свитка в качестве параметра позволяет окну (window) незапоминать тип его свитка. Если будет нужно, чтоб объект типаwindow_with_scrollbar действовал как scrollbar, можно добавитьоперацию преобразования: window_with_scrollbar :: operator scrollbar&() { return *sb; }

Дела Принадлежность и наследование использования

Для составления и осознания проекта нередко следует знать,какие классы и каким методом употребляет данный класс.Такие дела классовна С++ выражаются неявно. Класс может использовать только теимена, которые кое-где определены, но нет таковой части в программена С++, которая содержала бы перечень всех применяемых имен.Для получения такового Принадлежность и наследование перечня необходимывспомогательные средства (либо, при их отсутствии, внимательноечтение). Можно последующим образом систематизировать те методы,при помощи которых класс X может использовать класс Y: - X употребляет имя Y - X употребляет Y - X вызывает функцию-член Y - X читает член Y - X пишет в член Y - X делает Y - X располагает auto Принадлежность и наследование либо static переменную из Y - X делает Y при помощи new - X употребляет размер YМы отнесли внедрение размера объекта к его созданию, посколькудля этого требуется познание полного определения класса. С другойстороны, мы выделили в отдельное отношение внедрение имени Y,так как, указывая его в описании Y* либо Принадлежность и наследование в описаниивнешней функции, мы совсем не нуждаемся в доступе к определению Y: class Y; // Y - имя класса Y* p; extern Y f(const Y&); Мы отделили создание Y при помощи new от варианта описанияпеременной, так как вероятна такая реализация С++, при которойдля сотворения Y при помощи new необязательно Принадлежность и наследование знатьразмер Y. Это может быть значительно для ограничения всех зависимостейв проекте и сведения к минимуму перетрансляции после внесения конфигураций. Язык С++ не просит, чтоб создатель классов точно определял,какие классы и как он будет использовать. Одна из обстоятельств этогозаключена в том, что важнейшие классы зависят от настолько большогоколичества других классов, что Принадлежность и наследование для придания наилучшего вида программенужна сокращенная форма записи перечня применяемых классов, к примеру,при помощи команды #include. Другая причина в том, что классификацияэтих зависимостей и, а именно, обЪединение неких зависимостейне является обязанностью языка программирования. Напротив, целиразработчика, программера либо вспомогательного средства определяют то,как конкретно следует рассматривать дела использования. В Принадлежность и наследование конце концов, то,какие зависимости представляют больший энтузиазм, может зависеть отспецифики реализации языка.

Дела снутри класса

До сего времени мы обсуждали только классы, и хотя операции упоминались,если не считать обсуждения шагов процесса развития программногообеспечения ($$11.3.3.2), то они были на втором плане, объекты жепрактически вообщем не упоминались. Осознать это просто Принадлежность и наследование: в С++класс, а не функция либо объект, является главным понятиеморганизации системы. Класс может скрывать внутри себя всякую специфику реализации,вровень с "запятанными" приемами программирования, а время от времени онвынужден это делать. В то же время объекты большинства классовсами образуют регулярную структуру и употребляются такими методами,что их довольно легко Принадлежность и наследование обрисовать. Объект класса может бытьсовокупностью других вложенных объектов (их нередко именуют членами),многие из которых, в свою очередь, являются указателями либо ссылкамина другие объекты. Потому отдельный объект можно рассматривать каккорень дерева объектов, а все входящие в него объекты как "иерархиюобъектов", которая дополняет иерархию классов, рассмотренную в $$12.2.4.Разглядим в Принадлежность и наследование качестве примера класс строк из $$7.6: class String { int sz; char* p; public: String(const char* q); ~String(); //... }; Объект типа String можно изобразить так:

12.2.7.1 Инварианты

Значение членов либо объектов, доступных при помощи членов класса,именуется состоянием объекта (либо просто значением объекта).Главное при построении класса - это: привести объект в полностьюопределенное Принадлежность и наследование состояние (инициализация), сохранять вполне определенноесостояние обЪекта в процессе выполнения над ним разных операций,и в конце работы убить объект без всяких последствий. Свойство,которое делает состояние объекта на сто процентов определенным, называетсяинвариантом. Потому предназначение инициализации - задать определенные значения,при которых производится инвариант объекта. Для каждой операции классапредполагается, что инвариант Принадлежность и наследование обязан иметь место перед выполнениемоперации и должен сохраниться после операции. В конце работыдеструктор нарушает инвариант, уничтожая объект. К примеру,конструктор String::String(const char*) гарантирует,что p показывает на массив из, по последней мере, sz частей, причемsz имеет осмысленное значение и v[sz-1]==0. Неважно какая строковая операцияне должна нарушать Принадлежность и наследование это утверждение. При проектировании класса требуется огромное искусство, чтобысделать реализацию класса довольно обычной и допускающейналичие нужных инвариантов, которые нетрудно задать. Легкотребовать, чтоб класс имел инвариант, сложнее предложить полезныйинвариант, который понятен и не накладывает жестких ограниченийна деяния разработчика класса либо на эффективность реализации.Тут "инвариант" понимается как программный кусок,выполнив Принадлежность и наследование который, можно проверить состояние объекта. Полностью возможнодать более серьезное и даже математическое определение инварианта, и внекоторых ситуациях оно возможно окажется более подходящим. Тут жепод инвариантом понимается практическая, а означает, обычно экономичная,но неполная проверка состояния объекта. Понятие инварианта появилось в работах Флойда, Наура и Хора,посвященных пред- и пост-условиям Принадлежность и наследование, оно встречается во всех важныхстатьях по абстрактным типам данных и верификации программ запоследние 20 лет. Оно же является главным предметом отладки в C++. Обычно, в течение работы функции-члена инвариант не сохраняется.Потому функции, которые могут вызываться в те моменты, когдаинвариант не действует, не должны заходить в общий интерфейс класса.Такие функции Принадлежность и наследование должны быть личными либо защищенными. Как можно выразить инвариант в программке на С++? Обычное решение -определить функцию, проверяющую инвариант, и воткнуть вызовы этойфункции в общие операции. К примеру: class String { int sz; int* p; public: class Range {}; class Invariant {}; void check(); String(const char* q); ~String Принадлежность и наследование(); char& operator[](int i); int size() { return sz; } //... }; void String::check() TOO_LARGE<=sz char& String::operator[](int i) iИнкапсуляция


primernoj-programmi-disciplini-osnovnoj-vostochnij-yazik-specialnij-kurs-ch-1-cel-disciplini.html
primerom-obstoyatelstva-pri-kotorom-mozhet-vozniknut-ugroza-lichnoj-zainteresovannosti-v-sootvetstvii-s-kodeksom-etiki-auditorov-rossii-yavlyaetsya.html
primesnaya-provodimost-poluprovodnikov.html