Полиморфизм Прикладное программирование кафедра прикладной и компьютерной оптики 2 Полиморфизм Полиморфизм – возможность принимать множество форм, иметь разный смысл в зависимости от ситуации Полиморфизм в языке С++ позволяет программисту: создавать функции, имеющие одинаковые имена, но разные наборы аргументов (перегрузка функций) определять действие операторов для новых АТД (перегрузка операторов) 3 Перегрузка функций Перегрузка функций – использование одного имени для функций, выполняющих действия с аргументами разных типов void print(double d); void print(Lens l); void print(double x, double y); Если бы не было перегрузки функций print_int(int i); print_char(char c); print_Lens(Lens l); 4 Поиск подходящей перегруженной функции Последовательность поиска проверка точного соответствия типов попытка “повышения типов” (short -> int, float -> double ...) попытка стандартных преобразований (int -> double, double -> int, ...) преобразование явно задаваемое программистом Автоматическое преобразование типов bool, char повышается до int int < long < float < double Явное преобразование типов (тип) выражение тип (выражение) Возвращаемые типы не участвуют в определении какую из перегруженных функций вызвать. float sqrt(float); float sqrt(double); double sqrt(double); 5 Преобразование типов при помощи конструктора // преобразование из double в Complex Complex::Complex(double x) { m_re=x; m_im=0.; } // пример использования Complex x; x=Complex(3.14); // оператор explicit запрещает неявный вызов конструктора для преобразования типа class Matrix { public: explicit Matrix(int size); } Matrix::Matrix(int size) { m_data=new double[size*size]; } 6 Аргументы функции по умолчанию описание полного конструктора с параметрами по умолчанию Lens(double r1, double r2, double D=0., double d=0., double n=1.); реализация полного конструктора с параметрами по умолчанию Lens::Lens(double r1, double r2, double D, double d, double n) : m_r1(r1) , m_r2(r2) , m_d(d) , m_D(D) , m_n(n) { } аргументы по умолчанию должны быть указаны в конце списка аргументов и подряд Lens Lens Lens Lens Lens lens(100.,-100.); // r1=100 r2=-100 D=0 d=0 lens(100.,-100.,50.); // r1=100 r2=-100 D=50 d=0 lens(100.,-100.,50.,10.); // r1=100 r2=-100 D=50 d=10 lens(100.,-100.,50.,10.,1.5);// r1=100 r2=-100 D=0 d=10 lens(100.,-100.,1.5); // r1=100 r2=-100 D=1.5 d=0 n=1 n=1 n=1 n=1.5 n=1 7 Перегрузка операторов Для абстрактного типа данных Complex: * – комплексное умножение + – комплексное сложение ~ – комплексное сопряжение Complex x,y,z; z=x*y; z=x.operator*(y); // явный вызов оператора class Complex { … Complex operator*(const Complex& other) const; … } 8 Перегрузка бинарных операторов // оператор умножения Complex Complex::operator*(const Complex& other) const { return Complex(m_re*other.m_re-m_im*other.m_im, m_re*other.m_im-m_im*other.m_re); } // смешанная арифметика Complex Complex::operator*(const double& other) const { return Complex(m_re*other, m_im*other); } // пример использования Complex x, z; double y; z=x*y; 9 Перегрузка унарных операторов // унарный минус Complex Complex::operator-() const { return Complex(-m_re, -m_im); } // сопряжение Complex Complex::operator~() const { return Complex(m_re, -m_im); } // пример использования Complex x, y; y=-x; y=~x; 10 Перегрузка логических операторов // оператор равенства bool Complex::operator== (const Complex& other) const { return (m_re == other.m_re && m_im == other.m_im); } // пример использования Complex x, y; if(x==y) { … } 11 Перегрузка оператора присваивания Правила перегрузки оператора присваивания аргументом должна быть неизменяемая ссылка на экземпляр данного класса перед присваиванием необходимо сделать проверку на присваивание самому себе присваивание должно быть поэлементное оператор должен возвращать ссылку на самого себя // оператор присваивания Complex& Complex::operator=(const Complex& other) { if(this != &other) { m_re=other.m_re; m_im=other.m_im; } return *this; } // пример использования Complex x, y, z; x=y=z=1; 12 Перегрузка операторов с присваиванием Правила перегрузки с присваиванием аргументом должна быть неизменяемая ссылка на экземпляр данного класса оператор должен возвращать ссылку на самого себя // умножение с присваиванием Complex& Complex::operator*=(const Complex& other) { Complex temp(*this); m_re=temp.m_re*other.m_re - temp.m_im*other.m_im; m_im=temp.m_re*other.m_im + temp.m_im*other.m_re; return *this; } // пример использования Complex x, y; x*=y; 13 Перегрузка преобразования типов // преобразование типа Complex в double Complex::operator double() const { return (m_re*m_re-m_im*m_im); } // пример использования Complex x; double y; y=double(x); 14 Перегрузка индексирования // индексирование double& matrix::operator() (int i, int j) { return (p[i][j]); // или p[i*size+j]; } // пример использования matrix x; double y; y=matrix(1,1); // доступ к элементу (1,1) 15 Перегрузка операторов ввода/вывода Оформляются как дружественные функции класса // описание friend ostream& operator<< (ostream& out, const Complex& x); friend istream& operator>> (istream& out, Complex& x); // объявление ostream& operator<< (ostream& out, const Complex& x) { return (out<<”(“<<x.m_re<<”,”<<x.m_im<<”)”); } // пример использования Complex x; cout<<x<<endl; 16 Неперегружаемые операторы Оператор :: левый и правый операнд являются не значениями, а именем Оператор . правый операнд является именем Оператор .* правый операнд является именем Оператор ? : арифметический оператор имеет специфическую семантику Оператор new операнд является именем, кроме того выполняет небезопасную процедуру Оператор delete не используется без new, кроме того выполняет небезопасную процедуру Нельзя определить новые операторы См. пример программы 17 Параметрический полиморфизм Параметрический полиморфизм позволяет многократно использовать один и тот же код применительно к разным типам тип указывается как параметр функции или класса Шаблоны (templates) – средство для реализаций параметризированных классов и функций на языке С++ 18 Шаблоны функций void swap(int& x, int& y) { int temp; temp=x; x=y; y=temp; } template<class TYPE> void swap(TYPE& x, TYPE& y) { TYPE temp; temp=x; x=y; y=temp; } // использование шаблона double x=1, y=5; swap<double> (x, y); // TYPE заменяется на double Инстанцирование – генерация функции по шаблону и ее аргументу См. пример программы 19 Шаблоны функций с несколькими параметрами // пример 1 template<class PAR1, class PAR2> bool transform(PAR1 x, PAR2& y) { if(sizeof(y) < sizeof(x)) { return false; } y=(PAR2)x; return true; } // пример 2 template<class PAR, int n> PAR factorial() { PAR sum=1; i=1; while(i<=n) { sum*=i; i++; } return sum; } 20 Пример шаблона класса template <class PAR> class Complex { protected: PAR m_re, m_im; //вещественная и мнимая часть public: Complex(); // конструктор по умолчанию Complex(PAR re, PAR im=PAR(0)); // полный конструктор Complex(const Complex<PAR>& other); // конструктор копирования // получение параметров комплексного числа PAR GetRe() const; PAR GetIm() const; // перегруженные операторы Complex<PAR> operator*(const Complex<PAR>& other) const; Complex<PAR>& operator=(const Complex<PAR>& other); Complex<PAR> operator~() const; }; 21 Пример шаблона класса /////////////////////////////////////////////// // оператор сопряжения template <class PAR> Complex<PAR> Complex<PAR>::operator~() const { return Complex<PAR>(m_re, -m_im); } /////////////////////////////////////////////// template <class PAR> ostream& operator<< (ostream& out, const Complex<PAR>& other); /////////////////////////////////////////////// // вывод на экран template <class PAR> ostream& operator<< (ostream& out, const Complex<PAR>& other) { return (out<<”(“<<other.GetRe()<<”,”<<other.GetIm()<<”)”); } /////////////////////////////////////////////// 22 Инстанцирование шаблона Инстанцирование шаблона – процесс генерации объявления класса по шаблону и аргументу Complex<int> a(5), b(3,3); Complex<double> x(1.144, -0.155); См. пример программы 23 Объекты-функции Объекты-функции – объекты, у которых перегружен оператор вызова функций operator() Объекты-функции в <functional> : plus – сложение minus – вычитание multiplies – умножение divides – деление modulus – деление по модулю negate – отрицание 24 Стандартный объект-функция negate vector<int> v; vector<int>::iterator it=v.begin(); while(it != v.end()) { *it = -(*it); it++; } // то же самое с использование объекта-функции negate transform(v.begin(),v.end(), v.begin(), negate<int>()); // вывод на экран copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); // чтение из стандартного потока copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(v)); 25 Создание объекта-функции Rand template <class PAR> // параметры – тип данных и возвращаемое значение оператора () class Rand : public unary_function<PAR, void> { PAR m_min, m_max; public: Rand(PAR min, PAR max) : m_min(min), m_max(max) { } void operator() (PAR& i) { i=(PAR)(rand()*(m_max-m_min))/RAND_MAX+m_min; } }; // использование объекта-функции Rand for_each(v.begin(), v.end(), Rand<int>(-10,10)); 26 Преобразование бинарной функции в унарную Унарная функция – участвует один элемент (отрицание) Бинарная функция – участвуют два элемента (сложение, умножение, ...) Умножение каждого элемента на 2 – как? transform(v.begin(), v.end(), v.begin(), multiplies<int>()); Функция binder2nd – преобразует бинарную функцию в унарную, и принимает второй аргумент как параметр бинарной функции (<functional>) // умножение каждого элемента на 2 transform (v.begin(), v.end(), v.begin(), bind2nd(multiplies<int>(), 2)); См. пример программы 27 Предикаты Предикаты позволяют без изменения шаблона изменять критерии сравнения элементов контейнера и другие подобные действия. объект-функция возвращает значение bool Объекты-функции в <functional> equal_to бинарный предикат равенства not_equal_to бинарный предикат неравенства greater бинарный предикат > less бинарный предикат < (используется по умолчанию) greater_equal бинарный предикат >= less_equal бинарный предикат <= logical_and бинарный предикат И logical_or бинарный предикат ИЛИ logical_not унарный предикат НЕ 28 Стандартный объект-функция greater void main() { vector<int> v(10); for_each(v.begin(), v.end(), Rand<int>(-5,5)); // sort(v.begin(), v.end()); // reverse(v.begin(), v.end()); // сортировка в порядке убывания sort(v.begin(), v.end(), greater<int>()); } 29 Создание предиката InRange // параметры – тип данных и возвращаемое значение оператора () class InRange : public unary_function<int, bool> { int m_left, m_right; public: InRange(int left, int right) : m_left(left), m_right(right) {} bool operator() (const int& i) { return (i>m_left && i<m_right); } }; // использование InRange vector<int> v(10); for_each(v.begin(), v.end(), Rand<int>(0,10000)); cout << count_if(v.begin(), v.end(), InRange(1000,10000)); См. пример программы