Множественное наследование классов




 

Рассмотренная выше иерерхия классов графических объектов представ­ляет простое (одиночное) наследование, когда производный класс на­следует од­ному непосредственному базовому классу, расширяя его возможно­сти. Как было показано выше (см. пример 37), класс (потомок) может иметь бо­лее одно­го базового класса (родителей).

В общем случае, класс создается из решетки базовых классов. Поскольку исторически совокупности связей между классами образовывали деревья, ре­шетку классов часто также называют иерерхией классов. Классы пытаются проектировать так, чтобы пользователя без необходимости не интересовало, ка­ким образом класс составляется из других классов. В частности, механизм вир­туальных вызовов гарантирует, что когда мы вызываем функцию f() для некото­рого объекта, вызывается одна и та же функция, независимо от того, какой класс в иерархии содержит объявление f(), использованное для вызова. Рассмот­рим особенности множественного наследования более подробно на следующем модельном примере.

Пусть класс Task (задача) определяет решаемые задачи:

class Task

{ //...

public:

delay(int);

void wait();

void check(Task*);

};

а класс Display (отображение) – вывод данных:

class Display

{ //...

public:

void draw();

void write (Display*);

};

На их основе определим производный класс Device (прибор):

class Device: public Task, public Display

{ //...

public:

void transmit();

};

Кроме операций, определенных для Device, можно воспользоваться объединением операций Task и Display. Например:

void f (Device& s)

{ s.draw(); // Display::draw()

s.delay(10); // Task::delay()

s.transmit(); // Device::transmit()

}

Виртуальные функции работают как обычно. Например:

class Task

{ //...

virtual void wait()=0;

};

class Display

{ //...

virtual void draw()=0;

};

class Device: public Task, public Display

{ //...

void wait(); // замещение Task::wait()

void draw(); // замещение Display::draw()

}

 

Такой подход гарантирует, что функции Device::draw() и Device::wait() будут вызваны для класса Device, интерпретированного как Display и Task со­ответственно.

 

Разрешение неоднозначности.

 

Два базовых класса могут иметь методы с одинаковым именем. Напри­мер:

class Task

{ //...

virtual info* debug();

};

class Display

{ //...

virtual info* debug();

};

 

При использовании Device неоднозначности для этих функций должны быть устранены использованием квалифицированного имени функции:

void f (Device* pd)

{ info* d;

d=pd->debug(); // ошибка: неоднозначно

d=pd->Task::debug(); // правильно

d=pd->Display::debug(); // правильно

}

 

Повторяющиеся базовые классы.

 

При задании более чем одного класса возникает вероятность того, что ка­кой-либо класс дважды окажется базовым для другого класса. Например, если бы каждый из классов Task и Display был производным от класса Link, у Device было бы два Link:

class Link

{ //...

Link* next; // указатель на список

};

class Task: public Link

{ // Link используется для хранения списка задач Task

//...

};

class Display: public Link

{ // Link используется для хранения списка объектов Display

//...

};

Это не вызывает никаких проблем. Используются два отдельных объекта Link для представления связей, и эти списки не взаимодействуют друг с другом. Естественно, обращаясь к элементам класса Link, возможна неоднозначность, требующая разрешения, как показано выше. Объект Device можно представить в графическом виде следующим образом:

 

Link Link

 

 

Task Display

 

Device

 

Как правило, базовый класс, который повторяется (как Link) является де­талью реализации, которую не следует использовать вне непосредственно производных от него классов. Если к такому базовому классу нужен доступ из места, где видна более чем одна копия базового класса, во избежание неодно­значности ссылка должна быть явно квалифицирована. Например:

void links (Device* p)

{ p->next=0; // ошибка: неоднозначно, какой Link?

p->Link::next=0; // ошибка: неоднозначно, какой Link?

p->Task::Link::next=0; // правильно

p->Display::Link::next=0; // правильно

};

Это в точности тот же механизм, который используется для разрешения неоднозначности при обращении к элементам классов.

 

Виртуальные базовые классы.

В тех случаях, когда общий базовый класс не должен быть представлен в виде двух отдельных объектов, нужно воспользоваться виртуальным базовым классом. Например:

class Link

{ //...

};

class Task: public virtual Link

{ //...

};

class Display: public virtual Link

{ //...

};

class Device: public Task, public Display

{ //...

};

 

Все части объекта Device могут использовать единственную копию Link.

Графическая схема наследования принимает вид:

Link

 

Task Display


Device

 

В графе наследования все виртуальные базовые классы с одним именем будут представлены одним единственным объектом этого класса. С другой сто­роны, каждый невиртуальный базовый класс будет иметь отдельный подобъект, представляющий его.

Программирование виртуальных базовых классов.

При определении функций класса с виртуальным базовым классом про­граммист, в общем случае, не может знать, будет ли базовый класс использо­ваться совместно с другими производными классами. Это может представлять некоторую проблему при реализации алгоритмов, которые требуют, чтобы функция базового класса вызывалась ровно один раз. Например, язык гаранти­рует, что конструктор виртуального базового класса вызывается только один раз. Конструктор виртуального базового класса вызывается (явно или неявно) из конструктора объекта (конструктора самого "нижнего" производного класса). Например:

class A

{ // нет конструктора };

class B

{ public: B(); // конструктор по умолчанию };

class С

{ public: С(int); // конструктор с параметром };

class D: public virtual A, public virtual B, public virtual C

{ D() {/*...*/} // ошибка: нет конструктора с параметром для С

D (int i):C(i) {/*...*/} // правильно

};

Конструктор виртуального базового класса вызывается до конструкторов производных классов. При необходимости программист может смоделировать эту схему, вызывая функцию виртуального базового класса только из самого "нижнего" производного класса. Рассмотрим следующий пример.

Пусть есть базовый класс Window (окно), который "знает", как рисовать свое содержание:

class Window

{ //...

virtual void draw(); // виртуальная функция изображения окна

};

Кроме того, есть классы с различными способами оформления окон и до­полнительными средствами представления данных:

class Winborder: public virtual Window // окно с рамкой

{ // код оформления окна рамкой

void owndraw(); // отобразить рамку

void draw(); // замещение виртуальной функции

};

class Winmenu: public virtual Window // окно с меню

{ // код представления меню

void owndraw(); // отобразить меню

void draw(); // замещение виртуальной функции

};

Функции owndraw() не обязаны быть виртуальными, поскольку предпо­лагается, что они будут вызваны из виртуальной функции draw(), которая "зна­ет" тип объекта, для которого она вызвана. Из этого можно создать новый класс Clock (часы):

class Clock: public Winborder, public Winmenu

{ // код, описывающий часы

void owndraw(); // отобразить циферблат и стрелки

void draw(); // замещение виртуальной функции

};

Связи классов можно представить следующим графом:

Window

Winborder Winmenu

Clock

Виртуальные функции draw() можно написать с использованием функций owndraw() так, чтобы код, вызывающий любую draw(), косвенно вызывал при этом Window::draw() ровно один раз независимо от типа окна:

void Winborder::draw()

{ Window::draw(); // отобразить окно

owndraw(); // отобразить рамку

};

void Winmenu::draw()

{ Window::draw(); // отобразить окно

owndraw(); // отобразить меню

};

void Clock: public Winborder::draw()

{ Window::draw(); // отобразить окно

Winborder::owndraw(); // отобразить рамку

Winmenu::owndraw(); // отобразить меню

owndraw(); // отобразить циферблат и стрелки

};

Замещение функций виртуальных базовых классов.

Производный класс может заместить виртуальную функцию своего непо­средственного или косвенного виртуального базового класса. В частности, два различных класса могут заместить различные виртуальные функции виртуаль­ного базового класса. Таким способом несколько производных классов могут внести свой вклад в реализацию интерфейса, представленного в виртуальном базовом классе.

Например, класс Window мог бы иметь функцию setcolor() (установить цвет) и prompt() (выдать приглашение на ввод). Тогда Winborder мог бы заме­стить функцию setcolor() для управления цветом, а Winmenu мог бы заместить функцию prompt() для взаимодействия с пользователем:

class Window // абстрактный класс

{ //...

virtual setcolor(Color)=0;

virtual void prompt()=0;

};

class Winborder: public virtual Window

{ //...

setcolor(Color); // управление цветом фона

};

class Winmenu: public virtual Window

{ //...

void prompt(); // взаимодействие с пользователем

};

class Mywindow: public Winborder, public Winmenu

{ //...

};

Что если различные производные классы заместят одну и ту же функцию? Тогда если мы порождаем от этих классов новый производный класс, мы долж­ны в нем заместить эту функцию. То есть, одна функция должна замещать все остальные. Например, Mywindow мог бы заместить prompt() для улучшения интерфейса Winmenu:

class Mywindow: public Winborder, public Winmenu

{ //...

void prompt(); // свое взаимодействие с пользователем

};

В графическом виде это выглядит так:

Window { setcolor(), prompt() }

 

Winborder{ setcolor()} Winmenu { prompt() }

Mywindow { prompt() }

Если два класса замещают функцию базового класса, то порождение от них производного класса, не замещающего эту функцию недопустимо. В этом случае не может быть создана таблица виртуальных функций, потому что вызов этой функции с завершенным объектом будет неоднозначен. Такой конфликт разрешается добавлением замещающей функции в самый "нижний" производ­ный класс.

Использование множественного наследования.

Простейшим и наиболее очевидным применением множественного насле­дования является "склеивание" двух никаким образом не связанных класов вме­сте в качестве реализации третьего класса. Такое использование множественно­го наследования достаточно эффективно. Оно позволяет програм­мисту избе­жать написания большого количества функций, которые переадресу­ют вы­зовы друг другу. Множественное наследование позволяет "братским" классам совместно использовать информацию без введения зависимости от единствен­ного общего базового класса в программе. Это является случаем так называемо­го ромбовидного наследования (например, см. выше классы Clock, My­window). Виртуальный базовый класс в противоположность обыкновенному ба­зовому классу требуется в тех случаях, когда базовый класс не должен повто­ряться.

Важным назначением использования абстрактных классов является предоставление интерфейса с полным отсутствием деталей реализации, то есть интерфейсный базовый класс не должен содержать данных. В этом случае не нужен конструктор, поскольку нет данных, которые требуется инициализиро­вать. Многие классы требуют некоторой формы очистки до уничтожения объек­та. Так как абстрактный класс не может знать, требуется ли в производном клас­се такая очистка, он должен предположить, что требуется. Можно объявить вир­туальный деструктор в базовом классе и заместить его в производных классах, чтобы гарантировать правильную очистку данных.

При проектировании на основе абстрактных классов почти весь пользова­тельский код защищен от изменений в реализации иерархии и не нуждается в перекомпиляции после таких изменений. Кроме того, пользователи иерархии абстрактных классов меньше рискуют попасть в зависимость от способа реали­зации внешней системы, чем пользователи классической иерархии. Пользовате­ли абстрактных классов не могут случайно воспользоваться механизмами реа­лизации, потому что им доступны только средства, явно указанные в иерархии, и ничто не наследуется неявно из зависящего от реализации базового класса.

Резюме.

Абстрактный класс является интерфейсом. Иерархия классов — сред­ством последовательного построения классов. Естественно, каждый класс предоставляет своим пользователям интерфейс, и некоторые абстрактные клас­сы обеспечивают полезную функциональность, но, тем не менее, "интерфейс" и "строительные блоки" — вот главные роли абстрактных классов и иерархии классов. Классические иерархии имеют тенденцию смешивать вопросы реали­зации с интерфейсами для пользователя. В этих случаях на помощь приходит абстрактные классы. Иерархии абстрактных классов предоставляют ясный и мощный способ выражения концепций, не загроможденных вопросами реализа­ции, и при этом не приводят к значительным накладным расходам. Результатом проектирования должна быть система, предоставляемая пользователю в виде иерархии абстрактных классов и реализуемая при помощи классической иерар­хии. Подводя итог, можно сказать, что абстрактные типы предназначены для того, чтобы:

1. Определить одно понятие таким образом, чтобы позволить сосуществовать в программе нескольким реализациям.

2. Обеспечить приемлемое быстродействие и затраты памяти благодаря использованию виртуальных функций.

3. Минимизировать зависимость каждой реализации от других классов.

Пример 44.

Рассмотрим пример построения иерархии множественного наследования классов на основе абстрактного класса. За основу возьмем пример 42 с иерархией простого наследования Figure<-Point<-Circle, но произведем в нем необходимые изменения и дополним новыми классами.. Класс Figure сделаем интерфейсным абстрактным базовым классом с чисто виртуальными функциями (Show, Hide), а его данные (X, Y) и функции с ними связанные (Getx, Gety) передадим в класс Point, где они кажутся более уместными, определяя местоположение точки. В классе Point замещены виртуальные функции Show, Point.

Разработаем новый класс для работы с текстовым объектом Message, который резко отличается от графических объектов. К его данным относятся указатель на текст (*Msg), и параметры текста: шифт (Font), размер поля текста (Field). В этом классе замещаются виртуальные функции Show, Hide.

Создадим еще один класс Msgcirc, который наследует свойства двух родительских базовых классов: Circle и Message, а также замещает виртуальные функции Show, Point.

Иерархию наследования (Figure<-Point<-(Circle, Message)<-Msgcirc) можно отразить следующим графом:

Figure

Point

Circle Message

Msgcirc

 

Каждый класс (кроме Figure) имеет свой конструктор.

В главной функции создаются три объекта класса Msgcirc (Small, Medium, Large). Демонстрируется появление и исчезновение разноцветных объектов в цикле (до нажатия любой клавиши для окончания цикла). Программа представлена единым файлом для большей ясности:

#include<iostream.h>

#include<conio.h>

#include<graphics.h>

#include<stdlib.h> // для random(), kbhit()

#include<dos.h> // для delay()

#include<string.h>

enum Boolean {false, true}; // логические значения

const char *path= "c:\\borlandc\\bgi"; // путь к функциям графики

 

class Figure // абстрактный интерфейсный класс

{ public:

virtual void Show()=0; // 2 чистые виртуальные функции

virtual void Hide()=0;

};

class Point: public Figure // производный класс Point

{ protected:

int X, Y; // координаты точки

Boolean Visible; // видимость точки

public:

Point (int Initx, int Inity); // конструктор Point

int Getx() { return X; } // текущий Х

int Gety() { return Y; } // текущий Y

// виртуальные методы:

void Show(); // показ точки

void Hide(); // стирание точки

// невиртуальный метод:

Boolean Isvisible() // определение видимости точки

{ return Visible; }

};

class Circle: public Point // производный класс Circle

{ protected:

int Radius; // радиус окружности

public:

Circle (int Initx, int Inity, int Initradius); // конструктор Circle

// виртуальные методы:

void Show (); // показ окружности

void Hide(); // стирание окружности

};

class Message: public Point // производный класс Message

{ private:

char *Msg; // указатель на текст

int Font; // шрифт

int Field; // размер

public:

// конструктор:

Message(int Msgx, int Msgy, int Msgfont, int Msgfield, char *Text);

// виртуальные методы:

void Show(); // показ текста

void Hide(); // стирание текста

};

// Производный класс от Circle и Message:

class Msgcirc: public Circle, public Message

{ public:

// конструктор:

Msgcirc(int Mcircx, int Mcircy, int Mcircrad, int Font, char *Msg);

// виртуальные методы:

void Show(); // показ текста в окружности

void Hide(); // стирание текста и окружности

};

// Методы класса Point:

Point:: Point(int Initx, int Inity) // конструктор точки

{ X=Initx; Y=Inity; Visible = false;

}

void Point:: Show() // показ точки

{ if(! Visible) // если точка невидима, то

{ Visible = true; // задать видимость

setcolor(RED); // цвет точки

putpixel (X, Y, getcolor()); // изображение точки на экране

}

}

void Point:: Hide() // стирание точки

{ if(Visible) // если точка видима, то

{ Visible = false; // задать невидимость точки

putpixel (X, Y, getbkcolor()); // стирание точки цветом фона

}

}

// Методы класса Circle:

Circle::Circle (int Initx, int Inity, int Initradius): // конструктор окружности

Point(Initx, Inity) // вызов конструктора точки

{ Radius=Initradius; // начальный радиус

}

void Circle::Show() // показ окружности

{ if(! Isvisible()) // если невидима, то

{ Visible=true; // задать видимость

circle(X, Y, Radius); // изображение окружности

}

}

void Circle::Hide() // стирание окружности

{ int Tempcolor; // переменная цвета

Tempcolor=getcolor(); // сохранение текущего цвета

setcolor(getbkcolor()); // задать цвет фона

Visible=false; // окружность невидима

circle(X, Y, Radius); // стирание окружности фоном

setcolor(Tempcolor); // востановление текущего цвета

}

// Методы класса Message:

// конструктор Message:

Message::Message(int Msgx, int Msgy, int Msgfont, int Msgfield, char *Text):

Point(Msgx,Msgy) // вызов конструктора точки

{ Font=Msgfont; // шрифт текста

Field=Msgfield; // размер текста

Msg=Text; // указатель на текст

}

void Message::Show() // показ теста

{ int size=Field / (8*strlen(Msg)); // размер текста (8 пикселей на символ)

settextjustify(CENTER_TEXT, CENTER_TEXT); // центр теста

settextstyle(Font, HORIZ_DIR, size); // стиль текста

outtextxy (X,Y,Msg); // вывод текста

}

void Message::Hide() // стирание текста

{ int Tempcolor;

Tempcolor=getcolor(); // сохранение цвета

setcolor(getbkcolor()); // цвет фона

Visible=false; // текст невидим

int size=Field / (8*strlen(Msg)); // размер текста

settextjustify(CENTER_TEXT, CENTER_TEXT); // центр текста

settextstyle(Font, HORIZ_DIR, size); // стиль текста

outtextxy (X,Y,Msg); // текст цветом фона

setcolor(Tempcolor); // текущий цвет

}

// Методы класса Msgcirc:

// конструктор Msgcirc:

Msgcirc::Msgcirc(int Mcircx, int Mcircy, int Mcircrad, int Font, char *Msg):

Circle(Mcircx,Mcircy,Mcircrad), // вызов конструкторов Circle

Message(Mcircx,Mcircy,Font,2*Mcircrad,Msg) // и Message

{ }

void Msgcirc::Show() // вывод окружности и текста

{ Circle::Show(); // вывод окружности

Message::Show(); // вывод текста

}

void Msgcirc::Hide() // стирание окружности и текста

{ Circle::Hide(); // стирание окружности

Message::Hide(); // стирание текста

}

 

void main() // главная функция

{ int gdriver = DETECT, gmode; // параметры графики

initgraph (&gdriver, &gmode, path); // инициирование графики

int col; // пременная цвета

// создать и инициировать три объекта:

Msgcirc Small(250,100, 25, SANS_SERIF_FONT, "You");

Msgcirc Medium(250,150,100, TRIPLEX_FONT, "World");

Msgcirc Large(250, 250, 225, GOTHIC_FONT, "Universe");

 

while(!kbhit()) // цикл показа объектов до нажатия любой клавиши

{ randomize(); // рандомизация

col = random(16); // случайный цвет

if (col == BLACK) col++; // исключение черного цвета

setcolor(col); // текущий цвет

 

Small.Show(); // вывод объектов с задержкой

delay(500);

Medium.Show();

delay(500);

Large.Show();

delay(500);

 

Large.Hide(); // стирание объектов с задержкой

delay(500);

Medium.Hide();

delay(500);

Small.Hide();

delay(500);

}

closegraph(); // закрытие графического режима

}

 

ШАБЛОНЫ

В С++ предусмотрена еще одна реализация идеи полиформизма – шаблоны функций (Function Templates) и шаблоны классов (Class Templates). Подобно тому, как класс представляет собой схематическое построение объекта (то есть его программную модель), так и шаблон представляет схематическое описание построения функций и классов.

С точки зрения проектирования шаблоны служат двум, мало связанным между собой целям:

· обобщенному программированию;

· политике параметризации.

На ранних стадиях проектирования программного продукта операции являются просто операциями. Затем, когда настает время описать типы операндов, шаблоны приобратают огромную важность. Без шаблонов определения функций стали бы повторяться. Операция, реализующая алгоритм для операндов разных типов, является кандидатом на реализацию в виде шаблона. Если все операнды вписываются в единую иерархию классов, и если есть необходимость добавлять новые типы операндов во время выполнения программы, тип операнда лучше всего представить классом – часто абстрактным классом. Если типы операнда не вписываются в единую иерархию, и особенно если критично быстродействие, эти операции лучше реализовать в виде шаблона.

Шаблоны являются средством универсализации программирования, так как обеспечивают простой способ введения разного рода общих концепций и простые методы их совместного использования. Алгоритмы становятся обобщенными по множеству параметров. Этот стиль использования шаблонов называется обобщенным программированием. Шаблоны обеспечивают непосредственную поддержку обобщенного программирования с использованием типов в качестве параметров при определении класса или функции, поэтому шаблоны иногда называют параметризованными типами (или генераторами типов), поскольку они указывают лишь спецификации для функций и классов, но не детали настоящей реализации. Шаблон зависит только от тех свойств параметра-типа, которые он явно использует, и не требует, чтобы различные типы, используемые в качестве аргументов, были связаны каким-либо другим образом. В частности, типы аргументов шаблона не должны принадлежать к одной иерархии наследования.

Шаблоны особенно полезны в библиотеках классов, которыми пользуются программисты. Методы, которые можно использовать при проектировании и реализации стандартной библиотеки, позволяют программисту спрятать сложную реализацию за простыми интерфейсами. Например, sort(v) может служить интерфейсом для множества разных алгоритмов сортировки элементов различных типов, хранящихся в самых разнообразных контейнерах (вектор, список, стек и т.д.). Функция сортировки, наиболее подходящая для конкретного v, будет выбрана автоматически. Стандартная сортировка является обобщенной по типам контейнеров, поскольку к ней можно обращаться для произвольных контейнеров, удовлетворяющих стандартам.

Шаблоны являются механизмом времени компиляции, поэтому их использование не влечет дополнительных накладных расходов во время исполнения по сравнению с "программированием вручную".

 

Шаблоны функций

Шаблонные функции описывают общие свойства функций, подобно рецепту приготовления пирога. Шаблоны функций, обычно объявляемые в заголовочном файле, имеют следующую форму:

template <class T>

void f (T param)

{ // тело функции

}

 

Префикс template <class T> указывает, что объявлен шаблон (template), и что в объявлении на месте Т будет указан фактический тип, определяемой пользователем функции. Т можно заменить на любое другое имя. Слово class обозначает любой тип данных, а не обязательно класс С++. Необходим, по крайней мере, один параметр типа Т в скобках для передачи функции данных для обработки. Можно задать значение (T param), указатель (T* param) или ссылку (T& param).

 

Пример 45.

Демонстрация работы шаблона функции для вычисления квадрата числа. В программе задаются прототипы действительных функций для обработки данных различных типов.

 

#include<iostream.h>

#include<conio.h>

// шаблон функции:

template <class T> T sqrt (T a)

{ return a*a; }

// прототипы реальных функций:

int sqrt (int x);

float sqrt (float x);

long sqrt (long x);

long double sqrt (long double x);

// главная функция:

void main ()

{ int i = 10; float f = 1.1; long l = =12345; long double ld = 12.3E12;

clrscr();

cout << "int i = " << sqrt (i) <<endl;

cout << "float f = " << sqrt (f) <<endl;

cout << "long l = " << sqrt (l) <<endl;

cout << "long double ld = " << sqrt (ld) <<endl;

getch();

}

 

В шаблоне можно объявлять несколько параметров и возвращать значение типа Т, например:

template <class T> T f (int a, T b)

{ // тело функции

}

В этой версии шаблонная функция f возвращает значения типа Т и имеет два параметра — целое с именем а и неопределенный объект типа Т. Пользователю нужно задать действительный тип данных для Т. Компилятор использует шаблон для применения к реальной функции, которую можно вызывать подобно другим функциям. Например, чтобы использовать этот шаблон, можно объявить следующий прототип:

double f (int a, double b);

Если бы f была обычной функцией, то пришлось бы обеспечить ее реализацию. Но поскольку f – шаблонная функция, компилятор сам реализует код функции, заменив Т на double. Конечно, над типом, для которого будет вызыватся шаблонная функция, должны быть определены операции над переменными типа Т, которые используются в теле функции.

Рассмотрим примеры, в которых проясняется, как с помощью шаблонов можно уменьшить размер и сложность программ.

 

Пример 46.

Объявляется два шаблона функций min() и max() для определения минимума или максимума из двух значений. В программе объявляются прототипы действительных функций для обработки данных различных типов.

 

#include<iostream.h>

#include<conio.h>

// шаблоны функций:

template <class T> T max (T a, T b)

{ if (a > b) return a;

return b;

}

template <class T> T min (T a, T b)

{ if (a < b) return a;

return b;

}

// прототипы реальных функций с данными разных типов:

int max (int a, int b);

double max (double a, double b);

char max (char a, char b);

int min (int a, int b);

double min (double a, double b);

char min (char a, char b);

// главная функция:

void main()

{ int i1=100, i2=200;

double d1=3.14, d2=9.8;

char c1= 'A', c2 = 'Z';

clrscr();

cout<<"max(i1=" << i1 << ", i2=" << i2 <<"): " << max(i1, i2) <<endl;

cout<<"max(d1="<<d1<<", d2="<< d2 <<"): " << max(d1, d2) <<endl;

cout<<"max(c1="<< c1<<", c2="<< c2 <<"): " << max(c1, c2) <<endl;

cout<<"min(i1=" << i1 << ", i2=" << i2 <<"): " << min(i1, i2) <<endl;

cout<<"min(d1="<< d1<<", d2=" << d2 <<"): " << min(d1, d2) <<endl;

cout<<"min(c1="<< c1<<", c2=" << c2 <<"): " << min(c1, c2) <<endl;

getch();

}

Результаты программы:

max(i1=100, i2=200): 200

max(d1=3.14, d2=9.8): 9.8

max(c1=A, c2=Z): Z

min(i1=100, i2=200): 100

min(d1=3.14, d2=9.8): 3.14

min(c1=A, c2=Z): A

 

В шаблоне функции можно использовать несколько параметров и не обязательно одинакового типа, например:

template <class T1, class T2>

T1 max (T1 a, T2 b)

{ if (a > b) return a;

return b;

}

 

При этом возникает вопрос, почему возвращаемый тип Т1, а не Т2?
В С++ нет механизма изменения типа возвращаемого значения, поэтому надо было сделать какой-либо выбор. Для того чтобы эти функции возвращали результат без потери значения, при вызове функции желательно переменную или константу "старшего" типа использовать в качестве первого параметра.

 

Пример 47.

Использование шаблона функции с двумя параметрами разных типов:

 

#include<iostream.h>

template <class T1, class T2>

T1 max (T1 a, T2 b)

{ if (a > b) return a;

return b;

}

void main()

{ int i=5; double d=3.23; long l=123456; long double ld=1.2E123;

cout<< "max(d, i)=" << max(d, i) << endl;

cout<< "max(i, d)=" << max(i, d) << endl;

cout<< "max(ld, l)=" << max(ld, l) << endl;

// В двух следующих вызовах будет неверный результат,

// это связано не с шаблоном, а преобразованием типов:

cout<< "max(i, l)=" << max(i, l) << endl;

cout<< "max(l, 5.5)=" << max(l, 5.5) << endl;

// А в этом вызове результат верный:

cout<< "max(5.5, l)=" << max(5.5, l) << endl;

}

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2018-01-08 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: