Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Алгоритмические языки и программирование. Учебное пособие для СПО
.pdf
61
Абстрактный базовый класс
Наследование в совокупности с виртуальными функциями часто
используется для выделения общего интерфейса. Такой подход принят в
ситуации, когда требуется отделить интерфейс от реализации (например, есть
несколько возможных реализаций интерфейса).
clas
s B{
public:
virtual void Iam(){}
};
class D1 : public B{
public:
virtual void Iam(){
cout << "I am D1";
}
};
class D2 : public B{
public:
virtual void Iam(){
cout << "I am D2";
}
};
В данном примере в базовом классе присутствует пустая реализация метода
Iam. Если какой-либо производный класс не переопределит этот метод, то он
получит реализацию по умолчанию. Иногда такой подход невозможен и
требуется спроектировать базовый класс так, чтобы производные были обязаны
переопределить метод. В этом случае используются чисто виртуальные
функции, а класс
называется абстрактным.
class B{
public:
virtual void Iam()=0; //чисто виртуальная функция
};

62
class D1 : public B{
public:
virtual void Iam(){cout << "I am D1";}
};
class D2 : public B{
public:
virtual void Iam(){cout << "I am D2";}
};
Пример использования абстрактного класса:
class Person{
public:
virtual void Hello()=0;
virtual ~Person(){}
};
class Student : public Person{
public:
virtual void Hello(){
cout << "Hello! I am a Student.";
}
virtual ~Student(){}
};
class Professor : public Person{
public:
virtual void Hello(){
cout << "Hello! I am a Professor.";
}
virtual ~Professor(){}
};

63
class Worker : public Person{
public:
virtual void Hello(){
cout << "Hello! I am a Worker.";
}
virtual ~Worker(){}
};
int main(){
Person* Array[] = {new Student, new Professor, new Worker};
for(int i = 0; i < 3; ++i){
Array[i]->Hello();
cout << endl;
delete Array[i];
}
return 0;
}
Виртуальное наследование
Рассмотрим пример со множественным наследованием:
class B{
public:
void Hello(){
cout << "Hello!";
}
};
class D1 : public B{};
class D2 : public B{};
class C : public D1, public D2{};

64
int main(){
C c;
c.Hello();
return 0;
}
Как ни странно, при компиляции этой программы мы получим сообщение об
ошибке дублирования функции Hello. Дело в том, что каждый из классов D1,
D2 получает копию класса B с копией метода Hello, а класс C получает две
копии этого метода. При вызове компилятор не может определить, какую копию
мы хотим вызвать. Для решения проблемы применяется виртуальное
наследование:
class D1 : virtual public B{};
class D2 : virtual public B{};
При этом в классах D1, D2 не происходит копирование класса B, создается
только указатель на класс B.
Преобразование классов
Классы с виртуальными функциями допускают преобразования к базовому и
производному классам. При этом применяется оператор преобразования
dynamic_cast. Оператор принимает указатель на класс и возвращает
преобразованный класс, если преобразование допустимо, и NULL, если иначе.
Преобразование допустимо, когда указатель
на производный класс
преобразуется к базовому либо исходный и новый типы совпадают. Оператор
работает, если базовый класс содержит хотя бы одну виртуальную функцию.
class B{
public:
virtual void F(){} //нужен для оператора dynamic_cast
};

65
class D : public B{};
int main(){
B b;
D d;
D *PtrD = dynamic_cast<D*>(&b);
cout << PtrD << endl;
cout << endl;
B *PtrB = dynamic_cast<B*>(&d);
cout << PtrB << endl;
return 0;
}
Информация о типе
Когда требуется более полная информация о типе, можно использовать
оператор typeid. Оператор работает, если базовый класс содержит хотя бы одну
виртуальную функцию. Оператор возвращает const type_info&. Внимание:
оператор работает по-разному для указателей на статические и динамические
переменные.
class B{
public:
virtual void F(){} //нужно для оператора typeid
};
class D : public B{};
int main(){
D *PtrD;
B *PtrB;

66
cout << typeid(PtrD).name() << ' ' << typeid(PtrB).name();
return 0;
}
Стандартная библиотека
Архитектура
Библиотека разработана программистами Александром Степановым и
Менгом Ли из фирмы Hewlett Packard (США). Основными сущностями
библиотеки являются контейнеры, алгоритмы и итераторы. Все они
реализованы при помощи шаблонов, поэтому библиотека называется
библиотекой шаблонов.
Контейнер – способ организации хранения данных. Примеры контейнеров –
стек, связанный список.
Под алгоритмами понимаются процедуры, применяемые к контейнерам для
обработки их
данных различными способами. Например, есть алгоритмы
поиска, сортировки, объединения. Алгоритмы представлены в библиотеке в
виде свободных шаблонов функций. Благодаря этому достигается
универсальность алгоритмов. Алгоритмы могут применяться к произвольным
контейнерам, в том числе к массивам и пользовательским контейнерам.
Итераторы – это обобщение понятия указателя, они ссылаются на элементы
контейнера. Их задача – связать
контейнер с алгоритмом, поскольку они
позволяют осуществить обход контейнера.
Контейнеры
Контейнеры подразделяются на две категории: последовательные (векторы,
списки, деки) и ассоциативные (множество, мультимножество, отображение,
мультиотображение).

67
Алгоритмы позволяют выполнять сложные операции с контейнерами. Для
более простых операций, специфичных для данного контейнера, используются
методы.
Имя Назначение
size() Возвращает количе ство элементов в
контейнере
empty() Возвращает true, если контейнер пуст
max_size() Возвращает максимально допустимый размер
контейнера
begin() Возвращает итератор на начало контейнера
(итерации будут производиться в прямом
направлении)
end() Возвращает итератор на последнюю позицию
в контейнере (итерации в прямом
направлении будут закончены)
rbegin() Возвращает обратный итератор на конец
контейнера (итерации будут производиться в
обратном направлении)
rend() Возвращает обратный итератор на начало
контейнера (итерации в обратном
направлении будут закончены)
Последовательные контейнеры
В последовательных контейнерах элементы хранятся один за другим,
каждый элемент, кроме конечных, имеет по два соседа. Примером
последовательного контейнера является обычный массив.
Проблема с обычным массивом заключается в том, что его размер
необходимо указывать на этапе компиляции. В STL для решения этой проблемы
существует контейнер vector, который автоматически меняет свой размер
при
добавлении новых элементов. Для работы с вектором нужно подключить
соответствующую библиотеку: #include <vector>.

68
int main(){
vector<int> v; // create a vector of ints
v.push_back(10); // put values at end of array
v.push_back(11);
v.push_back(12);
v.push_back(13);
v[0] = 20; // replace with new values
v[3] = 23;
for(int j=0; j<v.size(); j++) // display vector contents
cout << v[j] << ' '; // 20 11 12 23
cout << endl;
return 0;
}
Метод push_back добавляет элементы в ко нец контейнера, метод size
возвращает текущее коли че ство элементов. Для обращения к созданным
элементам используется перегруженный оператор индексации [].
int main(){ // an array of doubles
double arr[] = { 1.1, 2.2, 3.3, 4.4 };
vector<double> v1(arr, arr+4); // initialize vector to array
vector<double> v2(4); // empty vector of size 4
v1.swap(v2); // swap contents of v1 and v2
while (!v2.empty()){ // until vector is empty,
cout << v2.back() << ' '; // display the last element
v2.pop_back(); // remove the last element
} // output: 4.4 3.3 2.2 1.1
cout << endl;
return 0;
}

69
Сначала мы инициализируем контейнер обычным массивом, при этом
передаем начало массива и конец (указатель на элемент за последним). Метод
swap обменивает содержимое двух контейнеров, порядок следования при этом
не меняется. Метод empty проверяет пустоту контейнера, метод back
возвращает последний элемент, метод pop_back уда л я ет последний элемент.
int main(){
int arr[] = { 100, 110, 120, 130 }; //an array of ints
vector<int> v (arr, arr+4); // initialize vector to array
cout << "\nBefore insertion: ";
for (unsigned int j = 0; j < v.size(); ++j){ // display all elements
cout << v[j] << ' ';
}
v.insert (v.begin()+2, 115); // insert 115 at element 2
cout << "\nAfter insertion: ";
for(unsigned int j = 0; j < v.size(); ++j){ // display all elements
cout << v[j] << ' ';
}
v.erase (v.begin()+2); // erase element 2
cout << "\nAfter erasure: ";
for (unsigned int j = 0; j < v.size(); ++j){ // display all elements
cout << v[j] << ' ';
}
return 0;
}
Метод insert добавляет в указанную позицию новый элемент, размер
контейнера автоматически увеличивается на единицу. Метод erase удаляет
элемент из указанной позиции, размер уменьшается на единицу. Методы insert и
erase для контейнера vector неэффективны, при добавлении и удален и и
элементов происходит сдвиг крайных элементов.

70
Если существует необходимость добавлять элементы в середину контейнера,
можно использовать контейнер list, который основан на связном списке.
Необходимо подключить библиотеку #include <list>.
int main(){
list<int> List;
List.push_back(30); // push items on back
List.push_back(40);
List.push_front(20); // push items on front
List.push_front(10);
int Size = List.size(); // number of items
for (int j = 0; j < Size; ++j){
cout << List.front() << ' '; // read item from front
List.pop_front(); // pop item off front
}
return 0;
}
Методы push_back, back, push_front, front добавляют и возвращают элементы
в конец и в начало соответственно. Для последовательного доступа к элементам
используется метод pop_front, поскольку оператор индексирования для списков
не определен.
int main(){
list<int> List1, List2;
int Arr1[] = { 40, 30, 20, 10 };
int Arr2[] = { 15, 20, 25, 30, 35 };
for (unsigned int j = 0; j < 4; ++j){
List1.push_back (Arr1[j]); // List1: 40, 30, 20, 10
}
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
