Методы программирования 2. ООП Л. 01.
Введение в ООП. Мееров И.Б.Методы программирования
(2 семестр)
Раздел 2. Лекция 09 Шаблоны
Нижегородский государственный университет
им. Н.И. Лобачевского
Факультет вычислительной математики и кибернетики
Методы программирования
(2 семестр)
Раздел 2. Лекция 09 Шаблоны
Нижегородский государственный университет
им. Н.И. Лобачевского
Факультет вычислительной математики и кибернетики
Краткое содержание предыдущей серии
Чисто виртуальные методы.
Абстрактные классы.
Множественное наследование.
Виртуальное наследование.
Чисто виртуальные методы.
Суть проблемы
Имея иерархию наследования, часто имеет смысл сосредоточить в базовом классе (вершине иерархии) основные функциональные элементы, которые впоследствии будут уточняться в потомках.
TBaseClass
Show()
Hide()
Save()
Load()
?
А что писать в базовом классе в качестве реализации?
Решение проблемы
Виртуальные методы – удобный механизм, которым нужно пользоваться.
Механизм подразумевает наличие в базовом классе методов, которые далее будут переопределяться.
В базовом классе неясно, что прописывать в качестве тел этих методов, более того, вызов этих методов для базового класса – ошибочная ситуация.
Решение проблемы: тело можно оставить пустым, но нужен универсальный механизм обработки ошибочных ситуаций (см. выше).
Понятие чисто виртуальной функции
Чисто виртуальная функция – метод, удовлетворяющий следующим параметрам:
Для него не указана реализация в классе, в котором он объявлен.
Вместо реализации (тела функции) указано =0.
Метод переопределяется далее в иерархии наследования.
Вид прототипа в объявлении класса
virtual <обычный прототип функции> = 0;
В данном случае мы имеем дело с фиктивной функцией. По-существу, мы отложили реализацию метода на потом.
Абстрактные классы
Класс, содержащий хотя бы одну чисто виртуальную функцию, называется абстрактным.
Абстрактные классы:
реализуют вершину в иерархии наследования;
не могут использоваться для создания объектов (попытка создания объекта порождает ошибку!);
могут (и используются) для объявления указателей, которые впоследствии используются для работы с объектами производных классов.
Абстрактные классы.
Смысл использования
Абстрактные классы представляют такие понятия предметной области, которые наделены всеми наиболее общими свойствами, которые предполагается переопределять в потомках (производных классах).
Абстрактные классы.
Пример использования
TShape
TPoint
TCircle
TSquare
схема выводимости
абстрактный класс
Множественное наследование
Идея множественного наследования – создание потомков при помощи нескольких предков.
Пример:
Труба – стальной цилиндр с физическими свойствами соответствующей стали и геометрическими свойствами цилиндра.
x
y
z
o
сталь XXX
Пример
материал
сталь
фигура
цилиндр
труба
материальная часть
геометрическая часть
Дальнейшее изучение
Сегодня мы изучим, как в C++ реализуется механизм шаблонов
Итак, за дело!
Вместо предисловия…
Оставим на время ООП и вернемся к обычным модульным программам.
Распространенное заблуждение:
C++ = C + ООП
С++ – это не только ООП. Одним из расширений языка является возможность
перегрузки функций.
Что это такое?
Перегрузка функций. Постановка задачи
В случае, когда разные функции осуществляют похожие действия над данными разных типов, удобно присвоить им одинаковое имя.
Пример: возведение в квадрат.
Язык C не позволяет создавать функции с одинаковым именем. Приходится писать так:
int SquareI(int x) { return x * x; };
double SquareD(double x) { return x*x; };
float SquareF(float x) { return x * x; };
…
Очевидно, что это неудобно и неестественно.
Перегрузка функций. Постановка задачи
В случае, когда разные функции осуществляют похожие действия над данными разных типов, удобно присвоить им одинаковое имя.
Пример: возведение в квадрат.
Язык C++ позволяет создавать функции с одинаковым именем. Можно написать так:
int Square(int x) { return x * x; };
double Square(double x) { return x*x; };
float Square(float x) { return x * x; };
…
Очевидно, что это удобно и естественно.
Перегрузка функций. Определение
Перегрузка функций – использование одного и того же идентификатора (имени) для именования разных функций, совершающих семантически сходные действия над аргументами разных типов.
Перегрузка функций. Как это работает?
Для того, чтобы определить, какую из перегруженных функций необходимо вызвать, производится проверка соответствия списка параметров при вызове функции и списка параметров при объявлении функции.
int SquareI(int x) { return x * x; };
double SquareD(double x) { return x*x; };
float SquareF(float x) { return x * x; };
…
double a = 1;
double d = Square(a);
Перегрузка функций. Как это работает?
Алгоритм выбора перегруженной функции:
для обнаружения соответствия используется понятие “сигнатура”.
Сигнатура – список типов параметров функции.
int SquareI(int x) { return x * x; };
// сигнатура “int”
double SquareD(double x) { return x * x; };
// сигнатура “double”
float SquareF(float x) { return x * x; };
// сигнатура “float”
Алгоритм выбора перегруженной функции
Алгоритм выбора перегруженной функции действует так:
ищет точное соответствие сигнатуре, если оно существует;
выполняет стандартные преобразования типов одного ранга (char->int, int, float->double);
выполняет стандартные преобразования типов
(расширения int -> double и т.д.);
выполняет преобразования типов, определенные пользователем (при перегрузке приведения типа);
работа со значениями по умолчанию.
Общая идеология
Необходимо избегать двусмысленностей.
Компилятор должен суметь установить соответствие между вызовом перегруженной функции и конкретной сигнатурой.
Тип возвращаемого значения не входит в сигнатуру и, следовательно, не влияет на процесс установления соответствия.
Реализации:
int Square(int x) { return x * x; };
double Square(int x) { return x * x; };
неотличимы
Еще немного терминологии
Перегрузка функций – специальный полиморфизм.
?
Шаблоны функций. Вместо вступления…
Вернемся к исходной задаче, но посмотрим на нее под другим углом зрения.
Мы научились давать одинаковые имена функциям, осуществляющим сходные действия над значениями разных типов.
А что, если действия не просто сходные, а абсолютно одинаковые? В примере с возведением в квадрат дело обстоит именно так.
Задача: не размножать один и тот же текст программы многократно для разных типов параметров.
Есть ли такие средства?
Разумеется!
Это макрос.
Вспомним о макросах
#define SUM(x1, x2) x1 + x2
…
int a = 1, b = 2, c = 3;
int d = SUM(a, b); // d = 3, все хорошо
int d = SUM(a, b) * с; // d = 7, вместо d=9
a + b * c
Вспомним о макросах
#define SUM(x1, x2) (x1 + x2)
Рисуем скобки
Вспомним о макросах - 2
#define SQUARE(x) (x * x)
…
int a = 1, b = 2, c = 3;
int d = SQUARE(c); // d = 9, все хорошо
int d = SQUARE(c + 1); // d = 7, все плохо
с + 1 * с + 1
Вспомним о макросах - 2
#define SQUARE(x) ((x) * (x))
Рисуем скобки
Вспомним о макросах - 3
#define SQUARE(x) ((x) * (x))
…
int c = SQUARE(x++);
((x++) * (x++))
Побочный эффект: двойное увеличение на 1. А вот такую проблему уже победить не удастся.
Шаблоны функций
Итак, макрос не годится.
Есть другое средство – шаблоны.
Шаблоны – “параметрический полиморфизм”.
Формат функции-шаблона:
template
…
}
Шаблоны функций. Пример
template
return x1 + x2;
}
int a = 1, b = 2, c = 3;
double d1 = 3.7;
int d = Sum(2, 4); // все хорошо
int f = Sum(a, c); // все хорошо
double dd = Sum(1.5, d1); // все хорошо
double dd = Sum(1.5, d1);
// Ошибка: нельзя определить, какую
// реализацию вызвать!
Подробнее о шаблонах
Шаблон может иметь несколько параметров.
template
void SomeFunc(T1 a, T2 b) { … }
Нельзя параметризовать возвращаемое значение.
При обнаружении шаблона функции компилятор не предпринимает никаких действий. При обнаружении попытки вызова функции компилятор порождает код функции, соответствующий реальным типам параметров – создается версия функции (процесс называется “инстанцирование” – instantiation).
Подробнее о шаблонах - 2
Шаблон может иметь в качестве параметра не только тип данных, но и обычную переменную.
Пример:
template
void SomeFunc(T1 a, T2 b) { … }
При использовании шаблона существует возможность явного указания типов аргументов:
template
void SomeFunc(T1 a, T2 b) { … }
…
SomeFunc
Подробнее о шаблонах - 3
Шаблоны функций могут быть перегружены как при помощи обычных функций, так и при помощи шаблонов.
Т.о., Вы имеете возможность создать функцию с тем же именем и другим списком параметров. В этом случае компилятор каждый раз пытается разобраться, что именно Вы пытаетесь вызвать.
Подробнее о шаблонах - 4
Пусть реализован шаблон функций, которая меняет местами значения параметров:
template
T c; c = a; a = b; b = c;
}
Для стандартных типов все работает. Если в качестве параметра шаблона выступает класс, то в этом классе должна быть реализована операция присваивания.
Подробнее о шаблонах - 5
Можно создать так называемую “специализацию” шаблона, реализовав особое поведение для некоторых типов параметров.
template
T c; c = a; a = b; b = c;
}
void MySwap
…
}
Шаблоны классов
Мы разобрались с параметризацией функций путем создания шаблонов функций.
Аналогичным образом можно создавать шаблоны классов.
Рассмотрим пример:
класс динамический массив.
class TVector {
private:
double *pVector;
int iSize;
public:
TVector(int s=10);
TVector(const TVector &v);
~TVector();
int GetSize() const { return iSize; }
double GetValue(int pos) const;
void SetValue(int pos, double value);
TVector Add(const TVector &v) const;
TVector AddEq(const TVector &v);
double Mult(const TVector &v) const;
void Print() const;
void Input();
TVector& operator-();
TVector& operator+=(const TVector& v);
TVector operator+(const TVector& v);
double operator*(const TVector& v);
TVector operator*(double d);
double& operator[](int i);
const TVector& operator=(const TVector& v); };
Реализуем шаблон по типу элемента
template
class TVector {
private:
TElement *pVector;
int iSize;
public:
TVector(int s=10);
TVector(const TVector &v);
~TVector();
…
Реализация методов
Внимание: метод шаблона есть шаблон функции.
Реализацию методов параметризованного класса необходимо писать в том же файле, где и объявление.
Пример:
template
TVector
iSize = v.GetSize();
pVector = new TElement[iSize];
for (int i = 0; i < iSize; i++)
pVector[i] = v.GetValue(i);
}
Использование шаблона
TVector
…
Теперь имя класса употребляется вместе с параметрами.
Использование шаблонов. Заключение
Для шаблонов классов действуют те же соображения по поводу инстанцирования и специализации.
Можно организовывать иерархии наследования как для обычных классов.
Шаблоны предоставляют средства контроля за типами (в отличие от макросов). Шаблоны позволяют проводить параметризацию, являя собой мощное средство языка программирования.
Не стоит переоценивать выразительную силу шаблонов – это всего лишь синтаксический механизм размножения кода и ничего более. Использование шаблонов приводит к увеличению размера исполняемого приложения.
Павловская Т.А. C/C++ Программирование на языке высокого уровня.
Ален И. Голуб. Правила программирования на C/C++.
Гради Буч. Объектно-ориентированный анализ и проектирование с примерами приложений на C++.
Из книг заимствованы различные идеи, элементы текста.
http://lye.upnet.ru
http://xaxa.h1.ru/jokes.html
Если не удалось найти и скачать доклад-презентацию, Вы можете заказать его на нашем сайте. Мы постараемся найти нужный Вам материал и отправим по электронной почте. Не стесняйтесь обращаться к нам, если у вас возникли вопросы или пожелания:
Email: Нажмите что бы посмотреть