Здравствуйте.Помогите решить задачу минимум 4 чисел.Условие такое:"Напишите функцию int min (int a, int b, int c, int d) (C/C++)"находящую наименьшее из четырех данных чисел."Заранее спасибо! |
Наследование. Шаблоны классов
Наследование
Презентацию к лекции Вы можете скачать здесь.
При большом количестве никак не связанных классов управлять ими становится невозможным. Наследование позволяет справиться с этой проблемой путем упорядочивания и ранжирования классов, то есть объединения общих для нескольких классов свойств в одном классе и использования его в качестве базового.
Механизм наследования классов позволяет строить иерархии, в которых производные классы получают элементы родительских, или базовых, классов и могут дополнять их или изменять их свойства.
Классы, находящиеся ближе к началу иерархии, объединяют в себе наиболее общие черты для всех нижележащих классов. По мере продвижения вниз по иерархии классы приобретают все больше конкретных черт. Множественное наследование позволяет одному классу обладать свойствами двух и более родительских классов.
Виды наследования
При описании класса в его заголовке перечисляются все классы, являющиеся для него базовыми. Возможность обращения к элементам этих классов регулируется с помощью модификаторов наследования private, protected и public:
class имя : [private | protected | public] базовый_класс{тело класса};
Если базовых классов несколько, они перечисляются через запятую. Перед каждым может стоять свой модификатор наследования. По умолчанию для классов он private, а для структур - public.
Если задан модификатор наследования public, оно называется открытым. Использование модификатора protected делает наследование защищенным, а модификатора private - закрытым. Это не просто названия: в зависимости от вида наследования классы ведут себя по-разному. Класс может наследовать от структуры, и наоборот.
До сих пор мы рассматривали только спецификаторы доступа private и public, применяемые к элементам класса. Для любого элемента класса может также использоваться спецификатор protected, который для одиночных классов, не входящих в иерархию, равносилен private. Разница между ними проявляется при наследовании. Возможные сочетания модификаторов и спецификаторов доступа приведены в таблице:
Как видно из таблицы, private элементы базового класса в производном классе недоступны вне зависимости от ключа. Обращение к ним может осуществляться только через методы базового класса.
Элементы protected при наследовании с ключом private становятся в производном классе private, в остальных случаях права доступа к ним не изменяются.
Доступ к элементам public при наследовании становится соответствующим ключу доступа.
Если базовый класс наследуется с ключом private, можно выборочно сделать некоторые его элементы доступными в производном классе, объявив их в секции public производного класса с помощью операции доступа к области видимости:
class Base{... public: void f();}; class Derived : private Base{... public: Base::void f();};
Простое наследование
Простым называется наследование, при котором производный класс имеет одного родителя. Для различных элементов класса существуют разные правила наследования. Рассмотрим наследование классов на примере.
Создадим производный от класса monster класс daemon, добавив полезную в некоторых случаях способность думать:
enum color {red, green, blue}; // ------------- Класс monster ------------- class monster { // ------------- Скрытые поля класса: int health, ammo; color skin; char *name; public: // ------------- Конструкторы: monster(int he = 100, int am = 10); monster(color sk); monster(char * nam); monster(monster &M); // ------------- Деструктор: ~monster() {delete [] name;} // ------------- Операции: monster& operator ++(){++health; return *this;} monster operator ++(int) {monster M(*this); health++; return M;} operator int(){return health;} bool operator >(monster &M) { if( health > M.get_health()) return true; return false; } monster& operator = (monster &M) { if (&M == this) return *this; if (name) delete [] name; if (M.name) { name = new char [strlen(M.name) + 1]; strcpy(name, M.name); } else name = 0; health = M.health; ammo = M.ammo; skin = M.skin; return *this; } // ------------- Методы доступа к полям: int get_health() const {return health;} int get_ammo() const {return ammo;} // ------------- Методы, изменяющие значения полей: void set_health(int he){ health = he;} void draw(int x, int y, int scale, int position); }; // ------------- Реализация класса monster ------------- monster::monster(int he, int am): health (he), ammo (am), skin (red), name (0){} monster::monster(monster &M) { if (M.name) { name = new char [strlen(M.name) + 1]; strcpy(name, M.name); } else name = 0; health = M.health; ammo = M.ammo; skin = M.skin; } monster::monster(color sk) { switch (sk) { case red:health = 100; ammo = 10; skin = red; name = 0; break; case green:health = 100;ammo = 20;skin = green; name = 0; break; case blue: health = 100; ammo = 40; skin = blue; name = 0;break; } } monster::monster(char * nam) { name = new char [strlen(nam)+1]; strcpy(name, nam); health = 100; ammo = 10; skin = red; } void monster::draw(int x, int y, int scale, int position) { /* ... Отрисовка monster */ } // ------------- Класс daemon ------------- class daemon : public monster { int brain; public: // ------------- Конструкторы: daemon(int br = 10){brain = br;}; daemon(color sk) : monster (sk) {brain = 10;} daemon(char * nam) : monster (nam) {brain = 10;} daemon(daemon &M) : monster (M) {brain = M.brain;} // ------------- Операции: daemon& operator = (daemon &M) { if (&M == this) return *this; brain = M.brain; monster::operator = (M); return *this; } // ------------- Методы, изменяющие значения полей: void draw(int x, int y, int scale, int position); void think(); }; // ------------- Реализация класса daemon ------------- void daemon::draw(int x, int y, int scale, int position) { /* ... Отрисовка daemon */ } void daemon:: think(){ /* ... */ }
В классе daemon введено поле brain и метод think, определены собственные конструкторы и операция присваивания, а также переопределен метод отрисовки draw. Все поля класса monster, операции (кроме присваивания) и методы get_health, get_ammo и set_health наследуются в классе daemon, а деструктор формируется по умолчанию.
Рассмотрим правила наследования различных методов.
Конструкторы не наследуются, поэтому производный класс должен иметь собственные конструкторы.
- Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса по умолчанию (то есть тот, который можно вызвать без параметров). Это использовано в первом из конструкторов класса daemon.
- Для иерархии, состоящей из нескольких уровней, конструкторы базовых классов вызываются начиная с самого верхнего уровня. После этого выполняются конструкторы тех элементов класса, которые являются объектами, в порядке их объявления в классе, а затем исполняется конструктор класса.
- В случае нескольких базовых классов их конструкторы вызываются в порядке объявления.
ВНИМАНИЕ
Если конструктор базового класса требует указания параметров, он должен быть явным образом вызван в конструкторе производного класса в списке инициализации (это продемонстрировано в трех последних конструкторах).
Не наследуется и операция присваивания, поэтому ее также требуется явно определить в классе daemon. Обратите внимание на запись функции-операции: в ее теле применен явный вызов функции-операции присваивания из базового класса. Чтобы лучше представить себе синтаксис вызова, ключевое слово operator вместе со знаком операции можно интерпретировать как имя функции-операции.
Вызов функций базового класса предпочтительнее копирования фрагментов кода из функций базового класса в функции производного. Кроме сокращения объема кода, этим достигается упрощение модификации программы: изменения требуется вносить только в одну точку программы, что сокращает количество возможных ошибок.
Правила для деструкторов при наследовании:
- Деструкторы не наследуются, и если программист не описал в производном классе деструктор, он формируется по умолчанию и вызывает деструкторы всех базовых классов.
- В отличие от конструкторов, при написании деструктора производного класса в нем не требуется явно вызывать деструкторы базовых классов, поскольку это будет сделано автоматически.
- Для иерархии классов, состоящей из нескольких уровней, деструкторы вызываются в порядке, строго обратном вызову конструкторов: сначала вызывается деструктор класса, затем - деструкторы элементов класса, а потом деструктор базового класса.
Поля, унаследованные из класса monster, недоступны функциям производного класса, поскольку они определены в базовом классе как private. Если функциям, определенным в daemon, требуется работать с этими полями, можно либо описать их в базовом классе как protected, либо обращаться к ним с помощью функций из monster, либо явно переопределить их в daemon так, как было показано в предыдущем разделе.
Добавляемые поля в наследнике могут совпадать и по имени, и по типу с полями базового класса. При этом поле предка будет скрыто.
Статические поля, объявленные в базовом классе, наследуются обычным образом. Все объекты базового класса и всех его наследников разделяют единственную копию статических полей базового класса.
Рассматривая наследование методов, обратите внимание на то, что в классе daemon описан метод draw, переопределяющий метод с тем же именем в классе monster (поскольку отрисовка различных персонажей, естественно, выполняется по-разному). Таким образом, производный класс может не только дополнять, но и корректировать поведение базового класса. Доступ к переопределенному методу базового класса для производного класса выполняется через уточненное с помощью операции доступа к области видимости имя.
Класс-потомок наследует все методы базового класса, кроме конструкторов, деструктора и операции присваивания. Не наследуются ни дружественные функции, ни дружественные отношения классов.
В классе-наследнике можно определять новые методы. В них разрешается вызывать любые доступные методы базового класса. Если имя метода в наследнике совпадает с именем метода базового класса, то метод производного класса скрывает все методы базового класса с таким именем. При этом прототипы методов могут не совпадать. Если в методе-наследнике требуется вызвать одноименный метод родительского класса, нужно задать его с префиксом класса. Это же касается и статических методов.