Здравствуйте.Помогите решить задачу минимум 4 чисел.Условие такое:"Напишите функцию int min (int a, int b, int c, int d) (C/C++)"находящую наименьшее из четырех данных чисел."Заранее спасибо! |
Наследование. Шаблоны классов
Шаблоны классов
Шаблон класса позволяет задать класс, параметризованный типом данных. Передача классу различных типов данных в качестве параметра создает семейство родственных классов. Наиболее широкое применение шаблоны находят при создании контейнерных классов. Контейнерным называется класс, который предназначен для хранения каким-либо образом организованных данных и работы с ними. Преимущество использования шаблонов состоит в том, что как только алгоритм работы с данными определен и отлажен, он может применяться к любым типам данных без переписывания кода.
Создание шаблонов классов
Рассмотрим процесс создания шаблона класса на примере двусвязного списка. Поскольку списки часто применяются для организации данных, удобно описать список в виде класса, а так как может потребоваться хранить данные различных типов, этот класс должен быть параметризованным.
Сначала рассмотрим непараметризованную версию класса. Список состоит из узлов, связанных между собой с помощью указателей. Каждый узел хранит целое число, являющееся ключом списка. Опишем вспомогательный класс для представления одного узла списка:
class Node { public: int d; // Данные Node *next, *prev; //Указатели на предыдущий и последующий узлы Node(int dat = 0) { d = dat; next = 0; prev = 0; } // Конструктор };
Поскольку этот класс будет описан внутри класса, представляющего список, поля для простоты доступа из внешнего класса сделаны доступными ( public ). Это позволяет обойтись без функций доступа и изменения полей. Назовем класс списка List:
class List { class Node{ ... }; Node *pbeg, *pend; // Указатели на начало и конец списка public: List() { pbeg = 0; pend = 0; } // Конструктор ~List(); // Деструктор void add(int d); // Добавление узла в конец списка Node * find(int i); // Поиск узла по ключу Node * insert(int key, int d); /* Вставка узла d после узла с ключом key */ bool remove(int key); // Удаление узла void print(); // Печать списка в прямом направлении void print_back(); // Печать списка в обратном направлении };
Рассмотрим реализацию методов класса. Метод add выделяет память под новый объект типа Node и присоединяет его к списку, обновляя указатели на его начало и конец:
void List::add(int d) { Node *pv = new Node(d); // Выделение памяти под новый узел if (pbeg == 0)pbeg = pend = pv; // Первый узел списка else { pv->prev = pend; // Связывание нового узла с предыдущим pend->next = pv; pend = pv; } // Обновление указателя на конец списка }
Метод find выполняет поиск узла с заданным ключом и возвращает указатель на него в случае успешного поиска и 0 в случае отсутствия такого узла в списке:
Node * List::find( int d ) { Node *pv = pbeg; while (pv) { if(pv->d == d)break; pv=pv->next; } return pv; }
Метод insert вставляет в список узел после узла с ключом key и возвращает указатель на вставленный узел. Если такого узла в списке нет, вставка не выполняется и возвращается значение 0:
Node * List::insert(int key, int d) { if(Node *pkey = find(key)) { // Поиск узла с ключом key /* Выделение памяти под новый узел и его инициализация */ Node *pv = new Node(d); /* Установление связи нового узла с последующим */ pv->next = pkey->next; // Установление связи нового узла с предыдущим pv->prev = pkey; // Установление связи предыдущего узла с новым pkey->next = pv; if( pkey != pend) (pv->next)->prev = pv; /* Установление связи последующего узла с новым */ /* Обновление указателя на конец списка, если узел вставляется в конец */ else pend = pv; return pv; } return 0; }
Метод remove удаляет узел с заданным ключом из списка и возвращает значение true в случае успешного удаления и false, если узел с таким ключом в списке не найден:
bool List::remove(int key) { if(Node *pkey = find(key)) { if (pkey == pbeg) { // Удаление из начала списка pbeg = pbeg->next; pbeg->prev = 0; } else if (pkey == pend) { // Удаление из конца списка pend = pend->prev; pend->next = 0; } else { // Удаление из середины списка (pkey->prev)->next = pkey->next; (pkey->next)->prev = pkey->prev; } delete pkey; return true;} return false;}
Методы печати списка в прямом и обратном направлении поэлементно просматривают список, переходя по соответствующим ссылкам:
void List::print(){ Node *pv = pbeg; cout << endl << "list: "; while (pv){ cout << pv->d << ' '; pv=pv->next;} cout << endl; } void List::print_back(){ Node *pv = pend; cout << endl << " list back: "; while (pv){ cout << pv->d << ' '; pv=pv->prev;} cout << endl;}
Деструктор списка освобождает память из-под всех его элементов:
List::~List(){ if (pbeg != 0){ Node *pv = pbeg; while (pv) {pv = pv->next; delete pbeg; pbeg = pv;} }}
Ниже приведен пример программы, использующей класс List. Программа формирует список из 5 чисел, выводит его на экран, добавляет число в список, удаляет число из списка и снова выводит его на экран:
int main() { List L; for (int i = 2; i<6; i++) L.add(i); L.print(); L.print_back(); L.insert(2,200); if (!L.remove(5))cout << "not found"; L.print(); L.print_back();}
Класс List предназначен для хранения целых чисел. Чтобы хранить в нем данные любого типа, требуется описать этот класс как шаблон и передать тип в качестве параметра.
Синтаксис описания шаблона:
template <описание_параметров_шаблона> class имя { /* определение класса */ };
Шаблон класса начинается с ключевого слова template. В угловых скобках записывают параметры шаблона. При использовании шаблона на место этих параметров шаблону передаются аргументы: типы и константы, перечисленные через запятую.
Типы могут быть как стандартными, так и определенными пользователем. Для их описания в списке параметров используется ключевое слово class. В простейшем случае одного параметра это выглядит как <class T>. Здесь T является параметром-типом. Имя параметра может быть любым, но принято начинать его с префикса T. Внутри класса-шаблона параметр может появляться в тех местах, где разрешается указывать конкретный тип, например:
template <class TData> class List { class Node{ public: TData d; Node *next; Node *prev; Node(TData dat = 0){d = dat; next = 0; prev = 0;} }; ... }
Класс TData можно рассматривать как параметр, на место которого при компиляции будет подставлен конкретный тип данных. Получившийся шаблонный класс имеет тип List<TData>.
Методы шаблона класса автоматически становятся шаблонами функций. Если метод описывается вне шаблона, его заголовок должен иметь следующие элементы:
template <описание_параметров_шаблона>
возвр_тип имя_класса <параметры_шаблона >:: имя_функции (список_параметров функции)
Проще рассмотреть синтаксис описания методов шаблона на примере:
template <class Data> void List <Data>::print() { /* тело функции */ }
- Описание параметров шаблона в заголовке функции должно соответствовать шаблону класса.
- Локальные классы не могут иметь шаблоны в качестве своих элементов.
- Шаблоны методов не могут быть виртуальными.
- Шаблоны классов могут содержать статические элементы, дружественные функции и классы.
- Шаблоны могут быть производными как от шаблонов, так и от обычных классов, а также являться базовыми и для шаблонов, и для обычных классов.
- Внутри шаблона нельзя определять friend -шаблоны.
Если у шаблона несколько параметров, они перечисляются через запятую. Ключевое слово class требуется записывать перед каждым параметром, например:
template <class T1, class T2> struct Pair { T1 first; T2 second; };
Параметрам шаблонного класса можно присваивать значения по умолчанию, они записываются после знака "=". Как и для обычных функций, задавать значения по умолчанию следует, начиная с правых параметров.
Ниже приведено полное описание параметризованного класса двусвязного списка List.
template <class TData> class List { class Node { public: TData d; Node *next, *prev; Node(TData dat = 0){d = dat; next = 0; prev = 0;} }; Node *pbeg, *pend; public: List(){pbeg = 0; pend = 0;} ~List(); void add(TData d); Node * find(TData i); Node * insert(TData key, TData d); bool remove(TData key); void print(); void print_back();}; //------------------------- template <class TData> List <TData>::~List() { if (pbeg !=0) { Node *pv = pbeg; while (pv) {pv = pv->next; delete pbeg; pbeg = pv;} } } //------------------------- template <class TData> void List <TData>::print() { Node *pv = pbeg; cout << endl << "list: "; while (pv) { cout << pv->d << ' '; pv = pv->next; } cout << endl; } //------------------------- template <class TData> void List <TData>::print_back() { Node *pv = pend; cout << endl << " list back: "; while (pv) { cout << pv->d << ' '; pv = pv->prev; } cout << endl; } //------------------------- template <class TData> void List <TData>::add(TData d) { Node *pv = new Node(d); if (pbeg == 0)pbeg = pend = pv; else { pv->prev = pend; pend->next = pv; pend = pv; } } //------------------------- template <class TData> Node * List <TData>::find( TData d) { Node *pv = pbeg; while (pv) { if(pv->d == d)break; pv = pv->next; } return pv; } //------------------------- template <class TData> Node * List <TData>::insert(TData key, TData d) { if(Node *pkey = find(key)) { Node *pv = new Node(d); pv->next = pkey->next; pv->prev = pkey; pkey->next = pv; if( pkey != pend)(pv->next)->prev = pv; else pend = pv; return pv; } return 0; } //------------------------- template <class TData> bool List <TData>::remove(TData key) { if(Node *pkey = find(key)) { if (pkey == pbeg) { pbeg = pbeg->next; pbeg->prev = 0; } else if (pkey == pend) { pend = pend->prev; pend->next = 0; } else { (pkey->prev)->next = pkey->next; (pkey->next)->prev = pkey->prev; } delete pkey; return true; } return false; }
Если требуется использовать шаблон List для хранения данных не встроенного, а определенного пользователем типа, в его описание необходимо добавить перегрузку операции вывода в поток и сравнения на равенство, а если для его полей используется динамическое выделение памяти, то и операцию присваивания.
При определении синтаксиса шаблона было сказано, что в него, кроме типов, могут передаваться константы. Соответствующим параметром шаблона может быть:
- переменная целого, символьного, булевского или перечислимого типа;
- указатель на объект или указатель на функцию;
- ссылка на объект или ссылка на функцию;
- указатель на элемент класса.
В теле шаблона такие параметры могут применяться в любом месте, где допустимо использовать константное выражение.
В качестве примера создадим шаблон класса, содержащего блок памяти определенной длины и типа:
template <class Type, int kol> class Block { public: Block(){p = new Type [kol];} ~Block(){delete [] p;} operator Type *(); protected: Type * p; }; template <class Type, int kol> Block <Type, kol>:: operator Type *() { return p; }
У класса-шаблона могут быть друзья, и шаблоны тоже могут быть друзьями. Класс может быть объявлен внутри шаблона, а шаблон - внутри как класса, так и шаблона. Единственным ограничением является то, что шаблонный класс нельзя объявлять внутри функции. В любом классе, как в обычном, так и в шаблоне, можно объявить метод-шаблон. После создания и отладки шаблоны классов удобно помещать в заголовочные файлы.