- •Учебное пособие
- •Введение
- •Объектно-ориентированный подход
- •Объектно-ориентированное программирование Абстрактные типы данных
- •Базовые принципы объектно-ориентированного программирования
- •Простейший ввод и вывод
- •Объект cout
- •Манипуляторы hex и oct
- •Другие манипуляторы
- •Объект cin
- •Операторы для динамического выделения и освобождения памяти (new и delete)
- •Базовые конструкции объектно-ориентированных программ Объекты
- •Понятие класса
- •Конструктор копирования
- •Конструктор explicit
- •Указатели на компоненты класса
- •Встроенные функции (спецификатор inline)
- •Организация внешнего доступа к локальным компонентам класса (спецификатор friend)
- •Вложенные классы
- •Static-члены (данные) класса
- •Указатель this
- •Компоненты-функции static и const
- •Proxi-классы
- •Параметры ссылки
- •Независимые ссылки
- •Практические приемы ограничения числа объектов класса
- •Наследование (производные классы)
- •Конструкторы и деструкторы при наследовании
- •Виртуальные функции
- •Абстрактные классы
- •Виртуальные деструкторы
- •Множественное наследование
- •Виртуальное наследование
- •Перегрузка функций
- •Перегрузка операторов
- •Перегрузка бинарного оператора
- •Перегрузка унарного оператора
- •Дружественная функция operator
- •Перегрузка оператора []
- •Перегрузка оператора ()
- •Перегрузка операторов new и delete
- •Преобразование типа
- •Явные преобразования типов
- •Преобразования типов, определенных в программе
- •Шаблоны Параметризированные классы
- •Передача в шаблон класса дополнительных параметров
- •Шаблоны функций
- •Совместное использование шаблонов и наследования
- •Шаблоны класса и friend
- •Некоторые примеры использования шаблона класса Реализация smart-указателя
- •Классы поддерживающие транзакции
- •Задание значений параметров класса по умолчанию
- •Пространства имен
- •Ключевое слово using как директива
- •Ключевое слово using как объявление
- •Псевдоним пространства имен
- •Организация ввода-вывода
- •Состояние потока
- •Строковые потоки
- •Организация работы с файлами
- •Организация файла последовательного доступа
- •Создание файла произвольного доступа
- •Основные функции классов ios, istream, ostream
- •Основы обработки исключительных ситуаций
- •Перенаправление исключительных ситуаций
- •Исключительная ситуация, генерируемая оператором new
- •Генерация исключений в конструкторах
- •Задание собственной функции завершения
- •Спецификации исключительных ситуаций
- •Задание собственного неожиданного обработчика
- •Иерархия исключений стандартной библиотеки
- •Стандартная библиотека шаблонов (stl) Общее понятие о контейнере
- •Общее понятие об итераторе
- •Категории итераторов
- •Основные итераторы
- •Вспомогательные итераторы
- •Операции с итераторами
- •Контейнерные классы Контейнеры последовательностей
- •Контейнер последовательностей vector
- •Контейнер последовательностей list
- •Контейнер последовательностей deque
- •Ассоциативные контейнеры
- •Ассоциативный контейнер multiset
- •Ассоциативный контейнер set
- •Ассоциативный контейнер multimap
- •Ассоциативный контейнер map
- •Адаптеры контейнеров
- •Адаптеры stack
- •Адаптеры queue
- •Адаптеры priority_queue
- •Пассивные и активные итераторы
- •Алгоритмы
- •Алгоритмы сортировки sort, partial_sort, sort_heap
- •Алгоритмы поиска find, find_if, find_end, binary_search
- •Алгоритмы fill, fill_n, generate и generate_n
- •Алгоритмы equal, mismatch и lexicographical_compare
- •Математические алгоритмы
- •Алгоритмы copy, copy_backward, merge, unique и reverse
- •Алгоритмы работы с множествами
- •Алгоритмы swap, iter_swap и swap_ranges
- •Примеры реализации контейнерных классов Связанные списки
- •Реализация односвязного списка
- •Реализация двусвязного списка
- •Реализация двоичного дерева
- •Литература
- •Вопросы по курсу ооп
- •220013, Минск, п.Бровки, 6.
Задание значений параметров класса по умолчанию
Если в функцию сортировки числовой информации, принадлежащей некоторому классу, передавать символьные строки (char *), то желаемый результат (отсортированные строки) не будет получен. Как известно, в этом случае произойдет сравнение указателей, а не строк.
#include "iostream.h"
#include "string.h"
#include "typeinfo.h"
class CompareNumb
{ public:
static bool sravn(int a, int b){return a<b;}
};
class CompareString
{ public:
static bool sravn(char *a, char *b){return strcmp(a,b)<0;}
};
template <class T,class Compare>
class vect
{ T *ms;
int size;
public:
vect(int SIZE):size(SIZE)
{ ms=new T[size];
const type_info & t=typeid(T); // получение ссылки t на
const char* s=t.name(); // объект класса type_info
for(int i=0;i<size;i++) // в описании типа
if(!strcmp(s,"char *"))
cin >> (*(ms+i)=(T)new char[20]); // ввод символьных строк
else cin >> *(ms+i); // ввод числовой информации
}
void sort_vec(vect<T,Compare> &);
};
template <class T,class Compare>
void vect<T,Compare>::sort_vec(vect<T,Compare> &vec)
{ for(int i=0;i<size-1;i++)
for(int j=i;j<size;j++)
if(Compare::sravn(ms[i],ms[j]))
{ T tmp=ms[i];
ms[i]=ms[j];
ms[j]=tmp;
}
for(i=0;i<size;i++) cout << *(ms+i) << endl;
};
Класс Compare должен содержать логическую функцию sravn(), сравнивающую два значения типа Т.
void main()
{ vect<int,CompareNumb> vec1(3);
vec1.sort_vec(vec1);
vect<char *,CompareString> vec2(3);
vec2.sort_vec(vec2);
}
Нетрудно заметить, что для всех типов, для которых операция меньше (<) имеет нужный смысл, можно написать следующий шаблон класса сравнения.
template<class T>
class Compare
{ public:
static bool sravn(T a, T b)
{ const type_info & t=typeid(T); // получение ссылки t на
const char* s=t.name(); // объект класса type_info
if(!strcmp(s,"char *"))
return strcmp((char *)a,(char *)b)<0;
else return a<b;
}
};
template <class T,class Compare>
class vect
{
// класс vect приведен выше
};
Чтобы сделать запись класса более простой, воспользуемся возможностью задания значений некоторых параметров класса по умолчанию.
template <class T,class C = Compare<T> >
void vect<T,C>::sort_vec(vect<T,C> &vec)
{ for(int i=0;i<size-1;i++)
for(int j=i;j<size;j++)
if(C::sravn(ms[i],ms[j]))
{ T tmp=ms[i];
ms[i]=ms[j];
ms[j]=tmp;
}
for(i=0;i<size;i++) cout << *(ms+i) << endl;
};
void main()
{ vect<int,Compare<int> > vec1(3);
vec1.sort_vec(vec1);
vect<char *,Compare<char *> > vec2(3);
vec2.sort_vec(vec2);
vect<long,Compare<long> > vec3(3);
vec3.sort_vec(vec3);
}
В инструкции vect<int,Compare<int> > vec1(3) содержится пробел между угловыми скобками. При его отсутствии компилятор спутает >> с операцией >> (сдвига).
Свойства в С++
В общем случае, свойство это пара функций (public), одна из которых отвечает за установку компонент-данных (private) объекта, а другая за их считывание. Такое решение позволяет обеспечить инкапсуляцию данных. Необходимость использования свойств возникает тогда, когда при изменении некоторого параметра требуется произвести ещё некоторые действия.
В языках программирования (таких как "Visual Basic" или "Delphi"), обращение к свойствам объекта производится оператором присваивания, как при обращении к компонентам-данным класса в C++.
оbj.data = value
производится неявный вызов функции.
Наиболее простой способ обеспечения инкапсуляции в C++ заключается в написании пары функций типа get_val() и put_val() для каждого параметра. Заметим, что именно так реализованы свойства в технологии Automation. Это можно продемонстрировать на следующем примере простого класса:
class cls
{ int m;
public:
int get_val()
{ return m; }
void put_val(int val)
{ m = val; }
};
В этом случае для обращения к такому свойству программист должен написать вызов соответствующей функции.
Разработчики Microsoft Visual C++ добавили в синтаксис языка несколько конструкций, позволяющих использовать свойства в операторах присваивания и вообще обращению с ними, как с компонентами-данными. В частности, модификатор _declspec получил дополнительный параметр "property". Это позволяет в классе объявить "виртуальную" переменную и связать её с соответствующими функциями. Теперь класс может выглядеть примерно так:
class cls
{ int m;
public:
_declspec(property(get=get_val, put=put_val)) int V;
int get_val()
{ return m; }
void put_val(int v)
{ m = v; }
};
В секции public содержится строка
_declspec(property(get=get_val, put=put_val)) int V;
в которой объявляется "виртуальная" переменная V типа int, при обращении к которой фактически будут вызваться функции get_val и put_val. Теперь доступ к данным объекта класса cls может быть выполнен следующим образом:
cls obj;
obj.V = 50; // при этом выполняется вызов put_val()
int k = obj.V; // при этом выполняется вызов get_val()
Модификатор _declspec(property) был введён для встроенной в компилятор поддержки технологии СОМ. Дело в том, что директива импорта библиотеки типа (что бы знать, что это такое, читайте книжки по COM) #import заставляет компилятор VC автоматически генерировать вспомогательные классы-обёртки для объектов СОМ. По аналогии с Visual Basic свойства сделаны индексными. Для этого, после объявления "виртуальной переменной" требуется поставить квадратные скобки:
_declspec(property(get=get_val, put=put_val)) int V [];
После этого свойство V может принимать один или несколько параметров-индексов, передаваемых в квадратных скобках. Так, например, вызов
obj.V["строка"] = 50;
Будет преобразован в вызов функции
obj.put_val( "строка", 50);
Основной недостаток описанного выше способа использования свойств в C++ является его зависимость от компилятора. Впрочем, другой, не менее известный компилятор "Borland C++ Builder" реализует концепцию свойств далёким от стандарта способом. В любом случае часто требуется (или хочется) достичь независимости от компилятора и соответствия кода программы стандарту C++. В тоже время язык C++ позволяет реализовать концепцию свойств. Для этого необходимо воспользоваться шаблонами и переопределить операторы присваивания и приведения типа.
template <class T1, class T2>
class property
{ typedef T1 (T2::*get)(); // get – синоним для указателя на функцию
// возвращающую T1
typedef void (T2::*set)(T1);
T2 * m_owner;
get m_get;
set m_set;
public:
// Оператор приведения типа. Реализует свойство для чтения.
operator T1()
{ // Здесь может быть проверка "m_owner" и "m_get" на NULL
return (m_owner->*m_get)();
}
// Оператор присваивания. Реализует свойство для записи.
void operator =(T1 data)
{ // Здесь может быть проверка "m_owner" и "m_set" на NULL
(m_owner->*m_set)(data);
}
// Конструктор по умолчанию.
property() :
m_owner(0),
m_get(0),
m_set(0)
{}
// Инициализация объекта property
void init(T2 * const owner, get getmethod, set setmethod)
{ m_owner = owner; // this указатель объекта класса Cls
m_get = getmethod; // указатель на метод get_val()
m_set = setmethod; // указатель на метод put_val()
}
};
Теперь класс, реализующий свойство можно написать так:
class cls
{ int m_val;
int get_val() //
{ return m_val;
}
void put_val(int val) //
{ m_val = val;
}
public:
property <int, cls> Val;
cls() // Конструктор по умолчанию
{ Val.init(this, get_val, put_val);
}
};
main()
{ cls obj;
// Далее вызывается оператор присваивания переменной-члена
// obj.Val, и, следовательно, функция val.put_val()
obj.Val = 50;
// Далее вызывается оператор приведения типа переменной-члена
// obj.Val, и, следовательно, функция val.get_val()
int z = obj.Val;
}
Как можно видеть, получились настоящие свойства средствами только стандартного синтаксиса С++. Однако, описанный метод не лишен недостатков:
- при каждом обращении к свойству происходит два вызова функции.
- использование таких свойств требует дополнительных затрат памяти из-за того, что на каждое свойство требуется 3 дополнительных указателя.
- использование шаблонов приводит к увеличению размеров исполняемого кода, поскольку компилятор будет генерировать отдельный класс для каждой пары T1 и T2.
для каждого свойства необходимо не забыть произвести инициализацию в конструкторе класса-владельца.
*** ВСТАВИТЬ ЗАГОЛОВОК ***
В рассматриваемом ниже примере вычисления факториала производится на этапе компиляции.
#include <iostream>
using namespace std;
template<int n> // описание шаблона структуры Factorial
struct Factorial
{
enum { val = Factorial<n-1>::val * n }; // вычисление факториала
};
template<> // инициализация первого экземпляра
struct Factorial<0> // шаблона структуры Factorial (при n==0)
{
enum { val = 1}; // исходное значение val равное 1
};
int main()
{ int ms[Factorial<4>::val]; // описание массива ms типа int
// размерностью val
cout << Factorial<4>::val << endl; // вывод значения факториала
return 0;
}
В результате выполнения программы получим:
24
В этом примере при компиляции производится генерация экземпляров шаблона структуры в выражении val = Factorial<n-1>::val * n. Кроме того приводится пример иописаения массива ms размерность val которого также определяется при компиляции.
